Issues (1282)

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 (7 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, GiveWP
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
	/**
71
	 * Session expiring time
72
	 *
73
	 * @since  2.2.0
74
	 * @access private
75
	 *
76
	 * @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
	private $session_expiration = false;
89
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
		$cookie = $this->get_session_cookie();
160
161
		if ( ! empty( $cookie ) ) {
162
			$this->donor_id           = $cookie[0];
163
			$this->session_expiration = $cookie[1];
164
			$this->session_expiring   = $cookie[2];
165
			$this->has_cookie         = true;
166
167
			// Update session if its close to expiring.
168
			if ( time() > $this->session_expiring ) {
169
				$this->set_expiration_time();
170
				Give()->session_db->update_session_timestamp( $this->donor_id, $this->session_expiration );
171
			}
172
173
			// Load session.
174
			$this->session = $this->get_session_data();
175
176
		} else {
177
			$this->generate_donor_id();
178
		}
179
180
		add_action( 'give_process_donation_after_validation', array( $this, 'maybe_start_session' ) );
181
182
		add_action( 'shutdown', array( $this, 'save_data' ), 20 );
183
		add_action( 'wp_logout', array( $this, 'destroy_session' ) );
184
185
		if ( ! is_user_logged_in() ) {
186
			add_filter( 'nonce_user_logged_out', array( $this, '__nonce_user_logged_out' ) );
187
		}
188
189
		// Remove old sessions.
190
		Give_Cron::add_daily_event( array( $this, '__cleanup_sessions' ) );
191
	}
192
193
	/**
194
	 * Get session data
195
	 *
196
	 * @since  2.2.0
197
	 * @access public
198
	 *
199
	 * @return array
200
	 */
201
	public function get_session_data() {
202
		return $this->has_session() ? (array) Give()->session_db->get_session( $this->donor_id, array() ) : array();
203
	}
204
205
206
	/**
207
	 * Get session by session id
208
	 *
209
	 * @since  2.2.0
210
	 * @access public
211
	 *
212
	 * @return array
213
	 */
214
	public function get_session_cookie() {
215
		$session      = array();
216
		$cookie_value = isset( $_COOKIE[ $this->cookie_name ] ) ? give_clean( $_COOKIE[ $this->cookie_name ] ) : $this->__handle_ajax_cookie(); // @codingStandardsIgnoreLine.
217
218
		if ( empty( $cookie_value ) || ! is_string( $cookie_value ) ) {
219
			return $session;
220
		}
221
222
		list( $donor_id, $session_expiration, $session_expiring, $cookie_hash ) = explode( '||', $cookie_value );
223
224
		if ( empty( $donor_id ) ) {
225
			return $session;
226
		}
227
228
		// Validate hash.
229
		$to_hash = $donor_id . '|' . $session_expiration;
230
		$hash    = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
231
232
		if ( empty( $cookie_hash ) || ! hash_equals( $hash, $cookie_hash ) ) {
233
			return $session;
234
		}
235
236
		/**
237
		 * Filter the session cookie data
238
		 *
239
		 * @since 2.2.6
240
		 */
241
		$cookie_data = apply_filters(
242
			'give_get_session_cookie',
243
			array( $donor_id, $session_expiration, $session_expiring, $cookie_hash )
244
		);
245
246
		return $cookie_data;
247
	}
248
249
250
	/**
251
	 * Load session cookie by ajax
252
	 *
253
	 * @since 2.2.6
254
	 * @access private
255
	 *
256
	 * @return array|bool|string
257
	 */
258
	private function __handle_ajax_cookie(){
259
		$cookie = false;
260
261
		// @see https://github.com/impress-org/give/issues/3705
262
		if (
263
			empty( $cookie )
264
			&& wp_doing_ajax()
265
			&& isset( $_GET['action'] )
266
			&& 'get_receipt' === $_GET['action']
267
		) {
268
			$cookie = isset( $_GET[$this->cookie_name] ) ? give_clean( $_GET[$this->cookie_name] ) : false;
269
		}
270
271
		return $cookie;
272
	}
273
274
275
	/**
276
	 * Check if session exist for specific session id
277
	 *
278
	 * @since  2.2.0
279
	 * @access public
280
	 *
281
	 * @return bool
282
	 */
283
	public function has_session() {
284
		return $this->has_cookie;
285
	}
286
287
	/**
288
	 * Set cookie name
289
	 *
290
	 * @since  2.2.0
291
	 * @access private
292
	 *
293
	 * @return void
294
	 */
295
	private function set_cookie_name() {
296
		/**
297
		 * Filter the cookie name
298
		 *
299
		 * @since 2.2.0
300
		 *
301
		 * @param string $cookie_name Cookie name.
302
		 * @param string $cookie_type Cookie type session or nonce.
303
		 */
304
		$this->cookie_name       = apply_filters(
305
			'give_session_cookie',
306
			'wp-give_session_' . COOKIEHASH, // Cookie name.
307
			'session' // Cookie type.
308
		);
309
310
		$this->nonce_cookie_name = apply_filters(
311
			'give_session_cookie',
312
			'wp-give_session_reset_nonce_' . COOKIEHASH, // Cookie name.
313
			'nonce' // Cookie type.
314
		);
315
	}
316
317
	/**
318
	 * Get Session
319
	 *
320
	 * Retrieve session variable for a given session key.
321
	 *
322
	 * @since  1.0
323
	 * @access public
324
	 *
325
	 * @param string $key     Session key.
326
	 * @param mixed  $default default value.
327
	 *
328
	 * @return string|array      Session variable.
329
	 */
330
	public function get( $key, $default = false ) {
331
		$key = sanitize_key( $key );
332
333
		return isset( $this->session[ $key ] ) ? maybe_unserialize( $this->session[ $key ] ) : $default;
334
	}
335
336
	/**
337
	 * Set Session
338
	 *
339
	 * @since  1.0
340
	 * @access public
341
	 *
342
	 * @param  string $key   Session key.
343
	 * @param  mixed  $value Session variable.
344
	 *
345
	 * @return string        Session variable.
346
	 */
347
	public function set( $key, $value ) {
348
		if ( $value !== $this->get( $key ) ) {
349
			$this->session[ sanitize_key( $key ) ] = maybe_serialize( $value );
350
			$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...
351
		}
352
353
		return $this->session[ $key ];
354
	}
355
356
	/**
357
	 * Set Session Cookies
358
	 *
359
	 * Cookies are used to increase the session lifetime using the give setting. This is helpful for when a user closes
360
	 * their browser after making a donation and comes back to the site.
361
	 *
362
	 * @since  1.4
363
	 * @access public
364
	 *
365
	 * @param bool $set Flag to check if set cookie or not.
366
	 */
367
	public function set_session_cookies( $set ) {
368
		if ( $set ) {
369
			$this->set_expiration_time();
370
371
			$to_hash          = $this->donor_id . '|' . $this->session_expiration;
372
			$cookie_hash      = hash_hmac( 'md5', $to_hash, wp_hash( $to_hash ) );
373
			$cookie_value     = $this->donor_id . '||' . $this->session_expiration . '||' . $this->session_expiring . '||' . $cookie_hash;
374
			$this->has_cookie = true;
375
376
			give_setcookie( $this->cookie_name, $cookie_value, $this->session_expiration, apply_filters( 'give_session_use_secure_cookie', false ) );
377
			give_setcookie( $this->nonce_cookie_name, '1', $this->session_expiration, apply_filters( 'give_session_use_secure_cookie', false ) );
378
		}
379
	}
380
381
	/**
382
	 * Set Cookie Expiration
383
	 *
384
	 * Force the cookie expiration time if set, default to 24 hours.
385
	 *
386
	 * @since  1.0
387
	 * @access public
388
	 *
389
	 * @return int
390
	 */
391
	public function set_expiration_time() {
392
		$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...
393
		$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...
394
395
		return $this->session_expiration;
396
	}
397
398
	/**
399
	 * Get Session Expiration
400
	 *
401
	 * Looks at the session cookies and returns the expiration date for this session if applicable
402
	 *
403
	 * @since  2.2.0
404
	 * @access public
405
	 *
406
	 * @return string|bool Formatted expiration date string.
407
	 */
408
	public function get_session_expiration() {
409
		return $this->has_session() ? $this->session_expiration :false;
410
	}
411
412
	/**
413
	 * Maybe Start Session
414
	 *
415
	 * Starts a new session if one hasn't started yet.
416
	 *
417
	 * @since  2.2.0
418
	 * @access public
419
	 *
420
	 * @return void
421
	 */
422
	public function maybe_start_session() {
423
		if (
424
			! headers_sent()
425
			&& empty( $this->session )
426
			&& ! $this->has_cookie
427
		) {
428
			$this->set_session_cookies( true );
429
		}
430
	}
431
432
	/**
433
	 * Generate a unique donor ID.
434
	 *
435
	 * Uses Portable PHP password hashing framework to generate a unique cryptographically strong ID.
436
	 *
437
	 * @since  2.2.0
438
	 * @access public
439
	 */
440
	public function generate_donor_id() {
441
		require_once ABSPATH . 'wp-includes/class-phpass.php';
442
443
		$hasher         = new PasswordHash( 8, false );
444
		$this->donor_id = md5( $hasher->get_random_bytes( 32 ) );
445
	}
446
447
	/**
448
	 * Save donor session data
449
	 *
450
	 * @since  2.2.0
451
	 * @access public
452
	 */
453
	public function save_data() {
454
		// Dirty if something changed - prevents saving nothing new.
455
		if ( $this->session_data_changed && $this->has_session() ) {
456
			global $wpdb;
457
458
			Give()->session_db->__replace(
459
				Give()->session_db->table_name,
460
				array(
461
					'session_key'    => $this->donor_id,
462
					'session_value'  => maybe_serialize( $this->session ),
463
					'session_expiry' => $this->session_expiration,
464
				),
465
				array(
466
					'%s',
467
					'%s',
468
					'%d',
469
				)
470
			);
471
472
			$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...
473
		}
474
	}
475
476
	/**
477
	 * Destroy all session data.
478
	 *
479
	 * @since  2.2.0
480
	 * @access public
481
	 */
482
	public function destroy_session() {
483
		give_setcookie( $this->cookie_name, '', time() - YEAR_IN_SECONDS, apply_filters( 'give_session_use_secure_cookie', false ) );
484
		give_setcookie( $this->nonce_cookie_name, '', time() - YEAR_IN_SECONDS, apply_filters( 'give_session_use_secure_cookie', false ) );
485
486
		Give()->session_db->delete_session( $this->donor_id );
487
488
		$this->session              = array();
489
		$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...
490
491
		$this->generate_donor_id();
492
	}
493
494
	/**
495
	 * Delete nonce cookie if generating fresh form html.
496
	 *
497
	 * @since 2.2.0
498
	 * @access public
499
	 *
500
	 * @return bool
501
	 */
502
	public function is_delete_nonce_cookie(){
503
		$value = false;
504
505
		if ( Give()->session->has_session() ) {
506
			$value = true;
507
		}
508
509
		return $value;
510
	}
511
512
	/**
513
	 * Get cookie names
514
	 *
515
	 * @since  2.2.0
516
	 * @access public
517
	 *
518
	 * @param string $type Nonce type.
519
	 *
520
	 * @return string Cookie name
521
	 */
522
	public function get_cookie_name( $type = '' ) {
523
		$name = '';
524
525
		switch ( $type ) {
526
			case 'nonce':
527
				$name = $this->nonce_cookie_name;
528
				break;
529
530
			case 'session':
531
				$name = $this->cookie_name;
532
				break;
533
		}
534
535
		return $name;
536
	}
537
538
	/**
539
	 * When a user is logged out, ensure they have a unique nonce by using the donor/session ID.
540
	 * Note: for internal logic only.
541
	 *
542
	 * @since  2.2.0
543
	 * @access public
544
	 *
545
	 * @param int $uid User ID.
546
	 *
547
	 * @return string
548
	 */
549
	public function __nonce_user_logged_out( $uid ) {
550
		return $this->has_session() && $this->donor_id ? $this->donor_id : $uid;
551
	}
552
553
554
	/**
555
	 * Cleanup session data from the database and clear caches.
556
	 * Note: for internal logic only.
557
	 *
558
	 * @since  2.2.0
559
	 * @access public
560
	 */
561
	public function __cleanup_sessions() { // @codingStandardsIgnoreLine
562
		Give()->session_db->delete_expired_sessions();
563
	}
564
565
566
	/**
567
	 * Get Session ID
568
	 *
569
	 * Retrieve session ID.
570
	 *
571
	 * @since      1.0
572
	 * @deprecated 2.2.0
573
	 * @access     public
574
	 *
575
	 * @return string Session ID.
576
	 */
577
	public function get_id() {
578
		return $this->get_cookie_name( 'session' );
579
	}
580
581
	/**
582
	 * Set Cookie Variant Time
583
	 *
584
	 * Force the cookie expiration variant time to custom expiration option, less and hour. defaults to 23 hours
585
	 * (set_expiration_variant_time used in WP_Session).
586
	 *
587
	 * @since      1.0
588
	 * @deprecated 2.2.0
589
	 * @access     public
590
	 *
591
	 * @return int
592
	 */
593
	public function set_expiration_variant_time() {
594
595
		return ( ! empty( $this->exp_option ) ? ( intval( $this->exp_option ) - 3600 ) : 30 * 60 * 23 );
596
	}
597
598
	/**
599
	 * Starts a new session if one has not started yet.
600
	 *
601
	 * Checks to see if the server supports PHP sessions or if the GIVE_USE_PHP_SESSIONS constant is defined.
602
	 *
603
	 * @since      1.0
604
	 * @access     public
605
	 * @deprecated 2.2.0
606
	 *
607
	 * @return bool $ret True if we are using PHP sessions, false otherwise.
608
	 */
609
	public function use_php_sessions() {
610
		$ret = false;
611
612
		give_doing_it_wrong( __FUNCTION__, __( 'We are using database session logic instead of PHP session', 'give' ), '2.2.0' );
613
614
		return (bool) apply_filters( 'give_use_php_sessions', $ret );
615
	}
616
617
	/**
618
	 * Should Start Session
619
	 *
620
	 * Determines if we should start sessions.
621
	 *
622
	 * @since      1.4
623
	 * @access     public
624
	 * @deprecated 2.2.0
625
	 *
626
	 * @return bool
627
	 */
628
	public function should_start_session() {
629
630
		$start_session = true;
631
632
		give_doing_it_wrong( __FUNCTION__, __( 'We are using database session logic instead of PHP session', 'give' ), '2.2.0' );
633
634
635
		if ( ! empty( $_SERVER['REQUEST_URI'] ) ) {  // @codingStandardsIgnoreLine
636
637
			$blacklist = apply_filters(
638
				'give_session_start_uri_blacklist', array(
639
					'feed',
640
					'feed',
641
					'feed/rss',
642
					'feed/rss2',
643
					'feed/rdf',
644
					'feed/atom',
645
					'comments/feed/',
646
				)
647
			);
648
			$uri       = ltrim( $_SERVER['REQUEST_URI'], '/' ); // // @codingStandardsIgnoreLine
649
			$uri       = untrailingslashit( $uri );
650
			if ( in_array( $uri, $blacklist, true ) ) {
651
				$start_session = false;
652
			}
653
			if ( false !== strpos( $uri, 'feed=' ) ) {
654
				$start_session = false;
655
			}
656
			if ( is_admin() ) {
657
				$start_session = false;
658
			}
659
		}
660
661
		return apply_filters( 'give_start_session', $start_session );
662
	}
663
}
664