Issues (4296)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-give-session.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Session
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Give_Session
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Class Give_Session
19
 */
20
class Give_Session {
0 ignored issues
show
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
21
	/**
22
	 * Instance.
23
	 *
24
	 * @since  2.2.0
25
	 * @access private
26
	 * @var Give_Session
27
	 */
28
	static private $instance;
29
30
	/**
31
	 * Holds our session data
32
	 *
33
	 * @since  1.0
34
	 * @access private
35
	 *
36
	 * @var    array
37
	 */
38
	private $session = array();
39
40
	/**
41
	 * Holds our session data
42
	 *
43
	 * @since  1.0
44
	 * @access private
45
	 *
46
	 * @var    string
47
	 */
48
	private $session_data_changed = false;
49
50
	/**
51
	 * Cookie Name
52
	 *
53
	 * @since  1.0
54
	 * @access private
55
	 *
56
	 * @var    string
57
	 */
58
	private $cookie_name = '';
59
60
	/**
61
	 * Donor Unique ID
62
	 *
63
	 * @since  1.0
64
	 * @access private
65
	 *
66
	 * @var    string
67
	 */
68
	private $donor_id = '';
69
70 2
	/**
71
	 * Session expiring time
72 2
	 *
73 2
	 * @since  2.2.0
74
	 * @access private
75
	 *
76 2
	 * @var    string
77
	 */
78
	private $session_expiring = false;
79
80
	/**
81
	 * Session expiration time
82
	 *
83
	 * @since  2.2.0
84
	 * @access private
85
	 *
86
	 * @var    string
87
	 */
88 2
	private $session_expiration = false;
89 2
90
	/**
91
	 * Flag to check if donor has cookie or not
92
	 *
93
	 * @since  2.2.0
94
	 * @access private
95
	 *
96
	 * @var    bool
97
	 */
98
	private $has_cookie = false;
99
100
	/**
101
	 * Expiration Time
102
	 *
103
	 * @since  1.0
104
	 * @access private
105
	 *
106
	 * @var    int
107
	 */
108
	private $exp_option = false;
109
110
	/**
111
	 * Expiration Time
112
	 *
113
	 * @since  2.2.0
114
	 * @access private
115
	 *
116
	 * @var    string
117
	 */
118
	private $nonce_cookie_name = '';
119
120
	/**
121
	 * Singleton pattern.
122
	 *
123
	 * @since  2.2.0
124
	 * @access private
125
	 */
126
	private function __construct() {
127
	}
128
129
130
	/**
131
	 * Get instance.
132
	 *
133
	 * @since  2.2.0
134
	 * @access public
135
	 * @return Give_Session
136
	 */
137 View Code Duplication
	public static function get_instance() {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
		if ( null === static::$instance ) {
139
			self::$instance = new static();
140
			self::$instance->__setup();
141
		}
142
143
		return self::$instance;
144
	}
145
146
	/**
147
	 * Setup
148
	 *
149
	 * @since  2.2.0
150
	 * @access public
151
	 */
152
	private function __setup() {  // @codingStandardsIgnoreLine
153
		$this->exp_option = give_get_option( 'session_lifetime' );
154
		$this->exp_option = ! empty( $this->exp_option )
155
			? $this->exp_option
156
			: 30 * 60 * 24; // Default expiration time is 12 hours
157
158
		$this->set_cookie_name();
159
		$this->cookie_name = $this->get_cookie_name( 'session' );
160
		$cookie            = $this->get_session_cookie();
161
162
		if ( ! empty( $cookie ) ) {
163
			$this->donor_id           = $cookie[0];
164
			$this->session_expiration = $cookie[1];
165 12
			$this->session_expiring   = $cookie[2];
166 12
			$this->has_cookie         = true;
167
168 12
			// Update session if its close to expiring.
169
			if ( time() > $this->session_expiring ) {
170
				$this->set_expiration_time();
171
				Give()->session_db->update_session_timestamp( $this->donor_id, $this->session_expiration );
172
			}
173
174
			// Load session.
175
			$this->session = $this->get_session_data();
176
177
		} else {
178
			$this->generate_donor_id();
179
		}
180
181
		add_action( 'give_process_donation_after_validation', array( $this, 'maybe_start_session' ) );
182 11
183
		add_action( 'shutdown', array( $this, 'save_data' ), 20 );
184 11
		add_action( 'wp_logout', array( $this, 'destroy_session' ) );
185
186 11
		if ( ! is_user_logged_in() ) {
187 9
			add_filter( 'nonce_user_logged_out', array( $this, '__nonce_user_logged_out' ) );
188 9
		}
189 9
190
		// Remove old sessions.
191
		Give_Cron::add_daily_event( array( $this, '__cleanup_sessions' ) );
192 11
	}
193 11
194 11
	/**
195
	 * Get session data
196 11
	 *
197
	 * @since  2.2.0
198
	 * @access public
199
	 *
200
	 * @return array
201
	 */
202
	public function get_session_data() {
203
		return $this->has_session() ? (array) Give()->session_db->get_session( $this->donor_id, array() ) : array();
204
	}
205
206
207
	/**
208
	 * Get session by session id
209
	 *
210
	 * @since  2.2.0
211
	 * @access public
212
	 *
213
	 * @return array
214
	 */
215
	public function get_session_cookie() {
216
		$session      = array();
217
		$cookie_value = isset( $_COOKIE[ $this->cookie_name ] ) ? give_clean( $_COOKIE[ $this->cookie_name ] ) : false; // @codingStandardsIgnoreLine.
218
219
		if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) {
220
			return $session;
221
		}
222
223
		list( $donor_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $cookie_value );
224
225
		if ( empty( $donor_id ) ) {
226
			return $session;
227
		}
228
229
		// Validate hash.
230
		$to_hash = $donor_id . '|' . $session_expiration;
231
		$hash    = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
232
233
		if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) {
234
			return $session;
235
		}
236
237
		return array( $donor_id, $session_expiration, $session_expiring, $cookie_hash );
238
	}
239
240
241
	/**
242
	 * Check if session exist for specific session id
243
	 *
244
	 * @since  2.2.0
245
	 * @access public
246
	 *
247
	 * @return bool
248
	 */
249
	public function has_session() {
250
		return $this->has_cookie;
251
	}
252
253
	/**
254
	 * Set cookie name
255
	 *
256
	 * @since  2.2.0
257 4
	 * @access private
258
	 *
259 4
	 * @return string Cookie name.
260
	 */
261
	private function set_cookie_name() {
262 4
		$this->cookie_name       = apply_filters( 'give_session_cookie', 'wp_give_session_' . COOKIEHASH );
263
		$this->nonce_cookie_name = 'wp_give_session_reset_nonce_' . COOKIEHASH;
264 4
	}
265
266
	/**
267
	 * Get Session
268
	 *
269
	 * Retrieve session variable for a given session key.
270
	 *
271
	 * @since  1.0
272
	 * @access public
273
	 *
274
	 * @param  string $key     Session key.
275
	 * @param mixed   $default default value.
276
	 *
277
	 * @return string|array      Session variable.
278
	 */
279
	public function get( $key, $default = false ) {
280
		$key = sanitize_key( $key );
281
282
		return isset( $this->session[ $key ] ) ? maybe_unserialize( $this->session[ $key ] ) : $default;
283
	}
284 4
285
	/**
286
	 * Set Session
287
	 *
288 4
	 * @since  1.0
289
	 * @access public
290 4
	 *
291 4
	 * @param  string $key   Session key.
292 4
	 * @param  mixed  $value Session variable.
293
	 *
294 4
	 * @return string        Session variable.
295
	 */
296
	public function set( $key, $value ) {
297
		if ( $value !== $this->get( $key ) ) {
298
			$this->session[ sanitize_key( $key ) ] = maybe_serialize( $value );
299
			$this->session_data_changed            = true;
0 ignored issues
show
Documentation Bug introduced by
The property $session_data_changed was declared of type string, but true is of type boolean. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
300
		}
301
302
		return $this->session[ $key ];
303 2
	}
304
305 2
	/**
306
	 * Set Session Cookies
307 2
	 *
308
	 * Cookies are used to increase the session lifetime using the give setting. This is helpful for when a user closes
309 2
	 * their browser after making a donation and comes back to the site.
310 2
	 *
311 2
	 * @since  1.4
312 2
	 * @access public
313 2
	 *
314 2
	 * @param bool $set Flag to check if set cookie or not.
315 2
	 */
316
	public function set_session_cookies( $set ) {
317 2
		if ( $set ) {
318 2
			$this->set_expiration_time();
319 2
320 2
			$to_hash          = $this->donor_id . '|' . $this->session_expiration;
321
			$cookie_hash      = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
322
			$cookie_value     = $this->donor_id . '||' . $this->session_expiration . '||' . $this->session_expiring . '||' . $cookie_hash;
323 2
			$this->has_cookie = true;
324
325
			give_setcookie( $this->cookie_name, $cookie_value, $this->session_expiration, apply_filters( 'give_session_use_secure_cookie', false ) );
326 2
			give_setcookie( $this->nonce_cookie_name, '1', $this->session_expiration, apply_filters( 'give_session_use_secure_cookie', false ) );
327 2
		}
328 2
	}
329 2
330
	/**
331 2
	 * Set Cookie Expiration
332
	 *
333
	 * Force the cookie expiration time if set, default to 24 hours.
334
	 *
335
	 * @since  1.0
336
	 * @access public
337
	 *
338
	 * @return int
339
	 */
340
	public function set_expiration_time() {
341
		$this->session_expiring   = time() + intval( apply_filters( 'give_session_expiring', ( $this->exp_option - 3600 ) ) ); // Default 11 Hours.
0 ignored issues
show
Documentation Bug introduced by
The property $session_expiring was declared of type string, but time() + intval(apply_fi...is->exp_option - 3600)) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
342
		$this->session_expiration = time() + intval( apply_filters( 'give_session_expiration', $this->exp_option ) ); // Default 12 Hours.
0 ignored issues
show
Documentation Bug introduced by
The property $session_expiration was declared of type string, but time() + intval(apply_fi...n', $this->exp_option)) is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
343
344
		return $this->session_expiration;
345
	}
346
347
	/**
348
	 * Get Session Expiration
349
	 *
350
	 * Looks at the session cookies and returns the expiration date for this session if applicable
351
	 *
352
	 * @since  2.2.0
353
	 * @access public
354
	 *
355
	 * @return string Formatted expiration date string.
356
	 */
357
	public function get_session_expiration() {
358
		return $this->session_expiration;
359
	}
360
361
	/**
362
	 * Maybe Start Session
363
	 *
364
	 * Starts a new session if one hasn't started yet.
365
	 *
366
	 * @since  2.2.0
367
	 * @access public
368
	 *
369
	 * @return void
370
	 */
371
	public function maybe_start_session() {
372
		if (
373
			! headers_sent()
374
			&& empty( $this->session )
375
			&& ! $this->has_cookie
376
		) {
377
			$this->set_session_cookies( true );
378
		}
379
	}
380
381
	/**
382
	 * Generate a unique donor ID.
383
	 *
384
	 * Uses Portable PHP password hashing framework to generate a unique cryptographically strong ID.
385
	 *
386
	 * @since  2.2.0
387
	 * @access public
388
	 */
389
	public function generate_donor_id() {
390
		require_once ABSPATH . 'wp-includes/class-phpass.php';
391
392
		$hasher         = new PasswordHash( 8, false );
393
		$this->donor_id = md5( $hasher->get_random_bytes( 32 ) );
394
	}
395
396
	/**
397
	 * Save donor session data
398
	 *
399
	 * @since  2.2.0
400
	 * @access public
401
	 */
402
	public function save_data() {
403
		// Dirty if something changed - prevents saving nothing new.
404
		if ( $this->session_data_changed && $this->has_session() ) {
405
			global $wpdb;
406
407
			Give()->session_db->__replace(
408
				Give()->session_db->table_name,
409
				array(
410
					'session_key'    => $this->donor_id,
411
					'session_value'  => maybe_serialize( $this->session ),
412
					'session_expiry' => $this->session_expiration,
413
				),
414
				array(
415
					'%s',
416
					'%s',
417
					'%d',
418
				)
419
			);
420
421
			$this->session_data_changed = false;
0 ignored issues
show
Documentation Bug introduced by
The property $session_data_changed was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
422
		}
423
	}
424
425
	/**
426
	 * Destroy all session data.
427
	 *
428
	 * @since  2.2.0
429
	 * @access public
430
	 */
431
	public function destroy_session() {
432
		give_setcookie( $this->cookie_name, '', time() - YEAR_IN_SECONDS, apply_filters( 'give_session_use_secure_cookie', false ) );
433
		give_setcookie( $this->nonce_cookie_name, '', time() - YEAR_IN_SECONDS, apply_filters( 'give_session_use_secure_cookie', false ) );
434
435
		Give()->session_db->delete_session( $this->donor_id );
436
437
		$this->session              = array();
438
		$this->session_data_changed = false;
0 ignored issues
show
Documentation Bug introduced by
The property $session_data_changed was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
439
440
		$this->generate_donor_id();
441
	}
442
443
	/**
444
	 * Delete nonce cookie if generating fresh form html.
445
	 *
446
	 * @since 2.2.0
447
	 * @access public
448
	 *
449
	 * @return bool
450
	 */
451
	public function is_delete_nonce_cookie(){
452
		$value = false;
453
454
		if ( Give()->session->has_session() ) {
455
			$value = true;
456
		}
457
458
		return $value;
459
	}
460
461
	/**
462
	 * Get cookie names
463
	 *
464
	 * @since  2.2.0
465
	 * @access public
466
	 *
467
	 * @param string $type Nonce type.
468
	 *
469
	 * @return string Cookie name
470
	 */
471
	public function get_cookie_name( $type = '' ) {
472
		$name = '';
473
474
		switch ( $type ) {
475
			case 'nonce':
476
				$name = $this->nonce_cookie_name;
477
				break;
478
479
			case 'session':
480
				$name = $this->cookie_name;
481
				break;
482
		}
483
484
		return $name;
485
	}
486
487
	/**
488
	 * When a user is logged out, ensure they have a unique nonce by using the donor/session ID.
489
	 * Note: for internal logic only.
490
	 *
491
	 * @since  2.2.0
492
	 * @access public
493
	 *
494
	 * @param int $uid User ID.
495
	 *
496
	 * @return string
497
	 */
498
	public function __nonce_user_logged_out( $uid ) {
0 ignored issues
show
Method name "Give_Session::__nonce_user_logged_out" is invalid; only PHP magic methods should be prefixed with a double underscore
Loading history...
499
		return $this->has_session() && $this->donor_id ? $this->donor_id : $uid;
500
	}
501
502
503
	/**
504
	 * Cleanup session data from the database and clear caches.
505
	 * Note: for internal logic only.
506
	 *
507
	 * @since  2.2.0
508
	 * @access public
509
	 */
510
	public function __cleanup_sessions() { // @codingStandardsIgnoreLine
511
		Give()->session_db->delete_expired_sessions();
512
	}
513
514
515
	/**
516
	 * Get Session ID
517
	 *
518
	 * Retrieve session ID.
519
	 *
520
	 * @since      1.0
521
	 * @deprecated 2.2.0
522
	 * @access     public
523
	 *
524
	 * @return string Session ID.
525
	 */
526
	public function get_id() {
527
		return $this->get_cookie_name('session');
0 ignored issues
show
Expected 1 spaces after opening bracket; 0 found
Loading history...
Expected 1 spaces before closing bracket; 0 found
Loading history...
528
	}
529
530
	/**
531
	 * Set Cookie Variant Time
532
	 *
533
	 * Force the cookie expiration variant time to custom expiration option, less and hour. defaults to 23 hours
534
	 * (set_expiration_variant_time used in WP_Session).
535
	 *
536
	 * @since      1.0
537
	 * @deprecated 2.2.0
538
	 * @access     public
539
	 *
540
	 * @return int
541
	 */
542
	public function set_expiration_variant_time() {
543
544
		return ( ! empty( $this->exp_option ) ? ( intval( $this->exp_option ) - 3600 ) : 30 * 60 * 23 );
545
	}
546
547
	/**
548
	 * Starts a new session if one has not started yet.
549
	 *
550
	 * Checks to see if the server supports PHP sessions or if the GIVE_USE_PHP_SESSIONS constant is defined.
551
	 *
552
	 * @since      1.0
553
	 * @access     public
554
	 * @deprecated 2.2.0
555
	 *
556
	 * @return bool $ret True if we are using PHP sessions, false otherwise.
557
	 */
558
	public function use_php_sessions() {
559
		$ret = false;
560
561
		give_doing_it_wrong( __FUNCTION__, __( 'We are using database session logic instead of PHP session', 'give' ), '2.2.0' );
562
563
		return (bool) apply_filters( 'give_use_php_sessions', $ret );
564
	}
565
566
	/**
567
	 * Should Start Session
568
	 *
569
	 * Determines if we should start sessions.
570
	 *
571
	 * @since      1.4
572
	 * @access     public
573
	 * @deprecated 2.2.0
574
	 *
575
	 * @return bool
576
	 */
577
	public function should_start_session() {
578
579
		$start_session = true;
580
581
		give_doing_it_wrong( __FUNCTION__, __( 'We are using database session logic instead of PHP session', 'give' ), '2.2.0' );
582
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
583
584
		if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {  // @codingStandardsIgnoreLine
585
586
			$blacklist = apply_filters(
587
				'give_session_start_uri_blacklist', array(
588
					'feed',
589
					'feed',
590
					'feed/rss',
591
					'feed/rss2',
592
					'feed/rdf',
593
					'feed/atom',
594
					'comments/feed/',
595
				)
596
			);
597
			$uri       = ltrim( $_SERVER['REQUEST_URI'], '/' ); // // @codingStandardsIgnoreLine
598
			$uri       = untrailingslashit( $uri );
599
			if ( in_array( $uri, $blacklist, true ) ) {
600
				$start_session = false;
601
			}
602
			if ( false !== strpos( $uri, 'feed=' ) ) {
603
				$start_session = false;
604
			}
605
			if ( is_admin() ) {
606
				$start_session = false;
607
			}
608
		}
609
610
		return apply_filters( 'give_start_session', $start_session );
611
	}
612
}
613