Issues (4122)

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/session/SessionProvider.php (1 issue)

Labels
Severity

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
 * MediaWiki session provider base class
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 * @ingroup Session
22
 */
23
24
namespace MediaWiki\Session;
25
26
use Psr\Log\LoggerAwareInterface;
27
use Psr\Log\LoggerInterface;
28
use Config;
29
use Language;
30
use User;
31
use WebRequest;
32
33
/**
34
 * A SessionProvider provides SessionInfo and support for Session
35
 *
36
 * A SessionProvider is responsible for taking a WebRequest and determining
37
 * the authenticated session that it's a part of. It does this by returning an
38
 * SessionInfo object with basic information about the session it thinks is
39
 * associated with the request, namely the session ID and possibly the
40
 * authenticated user the session belongs to.
41
 *
42
 * The SessionProvider also provides for updating the WebResponse with
43
 * information necessary to provide the client with data that the client will
44
 * send with later requests, and for populating the Vary and Key headers with
45
 * the data necessary to correctly vary the cache on these client requests.
46
 *
47
 * An important part of the latter is indicating whether it even *can* tell the
48
 * client to include such data in future requests, via the persistsSessionId()
49
 * and canChangeUser() methods. The cases are (in order of decreasing
50
 * commonness):
51
 *  - Cannot persist ID, no changing User: The request identifies and
52
 *    authenticates a particular local user, and the client cannot be
53
 *    instructed to include an arbitrary session ID with future requests. For
54
 *    example, OAuth or SSL certificate auth.
55
 *  - Can persist ID and can change User: The client can be instructed to
56
 *    return at least one piece of arbitrary data, that being the session ID.
57
 *    The user identity might also be given to the client, otherwise it's saved
58
 *    in the session data. For example, cookie-based sessions.
59
 *  - Can persist ID but no changing User: The request uniquely identifies and
60
 *    authenticates a local user, and the client can be instructed to return an
61
 *    arbitrary session ID with future requests. For example, HTTP Digest
62
 *    authentication might somehow use the 'opaque' field as a session ID
63
 *    (although getting MediaWiki to return 401 responses without breaking
64
 *    other stuff might be a challenge).
65
 *  - Cannot persist ID but can change User: I can't think of a way this
66
 *    would make sense.
67
 *
68
 * Note that many methods that are technically "cannot persist ID" could be
69
 * turned into "can persist ID but not change User" using a session cookie,
70
 * as implemented by ImmutableSessionProviderWithCookie. If doing so, different
71
 * session cookie names should be used for different providers to avoid
72
 * collisions.
73
 *
74
 * @ingroup Session
75
 * @since 1.27
76
 * @see https://www.mediawiki.org/wiki/Manual:SessionManager_and_AuthManager
77
 */
78
abstract class SessionProvider implements SessionProviderInterface, LoggerAwareInterface {
79
80
	/** @var LoggerInterface */
81
	protected $logger;
82
83
	/** @var Config */
84
	protected $config;
85
86
	/** @var SessionManager */
87
	protected $manager;
88
89
	/** @var int Session priority. Used for the default newSessionInfo(), but
90
	 * could be used by subclasses too.
91
	 */
92
	protected $priority;
93
94
	/**
95
	 * @note To fully initialize a SessionProvider, the setLogger(),
96
	 *  setConfig(), and setManager() methods must be called (and should be
97
	 *  called in that order). Failure to do so is liable to cause things to
98
	 *  fail unexpectedly.
99
	 */
100
	public function __construct() {
101
		$this->priority = SessionInfo::MIN_PRIORITY + 10;
102
	}
103
104
	public function setLogger( LoggerInterface $logger ) {
105
		$this->logger = $logger;
106
	}
107
108
	/**
109
	 * Set configuration
110
	 * @param Config $config
111
	 */
112
	public function setConfig( Config $config ) {
113
		$this->config = $config;
114
	}
115
116
	/**
117
	 * Set the session manager
118
	 * @param SessionManager $manager
119
	 */
120
	public function setManager( SessionManager $manager ) {
121
		$this->manager = $manager;
122
	}
123
124
	/**
125
	 * Get the session manager
126
	 * @return SessionManager
127
	 */
128
	public function getManager() {
129
		return $this->manager;
130
	}
131
132
	/**
133
	 * Provide session info for a request
134
	 *
135
	 * If no session exists for the request, return null. Otherwise return an
136
	 * SessionInfo object identifying the session.
137
	 *
138
	 * If multiple SessionProviders provide sessions, the one with highest
139
	 * priority wins. In case of a tie, an exception is thrown.
140
	 * SessionProviders are encouraged to make priorities user-configurable
141
	 * unless only max-priority makes sense.
142
	 *
143
	 * @warning This will be called early in the MediaWiki setup process,
144
	 *  before $wgUser, $wgLang, $wgOut, $wgParser, $wgTitle, and corresponding
145
	 *  pieces of the main RequestContext are set up! If you try to use these,
146
	 *  things *will* break.
147
	 * @note The SessionProvider must not attempt to auto-create users.
148
	 *  MediaWiki will do this later (when it's safe) if the chosen session has
149
	 *  a user with a valid name but no ID.
150
	 * @protected For use by \MediaWiki\Session\SessionManager only
151
	 * @param WebRequest $request
152
	 * @return SessionInfo|null
153
	 */
154
	abstract public function provideSessionInfo( WebRequest $request );
155
156
	/**
157
	 * Provide session info for a new, empty session
158
	 *
159
	 * Return null if such a session cannot be created. This base
160
	 * implementation assumes that it only makes sense if a session ID can be
161
	 * persisted and changing users is allowed.
162
	 *
163
	 * @protected For use by \MediaWiki\Session\SessionManager only
164
	 * @param string|null $id ID to force for the new session
165
	 * @return SessionInfo|null
166
	 *  If non-null, must return true for $info->isIdSafe(); pass true for
167
	 *  $data['idIsSafe'] to ensure this.
168
	 */
169
	public function newSessionInfo( $id = null ) {
170
		if ( $this->canChangeUser() && $this->persistsSessionId() ) {
171
			return new SessionInfo( $this->priority, [
172
				'id' => $id,
173
				'provider' => $this,
174
				'persisted' => false,
175
				'idIsSafe' => true,
176
			] );
177
		}
178
		return null;
179
	}
180
181
	/**
182
	 * Merge saved session provider metadata
183
	 *
184
	 * This method will be used to compare the metadata returned by
185
	 * provideSessionInfo() with the saved metadata (which has been returned by
186
	 * provideSessionInfo() the last time the session was saved), and merge the two
187
	 * into the new saved metadata, or abort if the current request is not a valid
188
	 * continuation of the session.
189
	 *
190
	 * The default implementation checks that anything in both arrays is
191
	 * identical, then returns $providedMetadata.
192
	 *
193
	 * @protected For use by \MediaWiki\Session\SessionManager only
194
	 * @param array $savedMetadata Saved provider metadata
195
	 * @param array $providedMetadata Provided provider metadata (from the SessionInfo)
196
	 * @return array Resulting metadata
197
	 * @throws MetadataMergeException If the metadata cannot be merged.
198
	 *  Such exceptions will be handled by SessionManager and are a safe way of rejecting
199
	 *  a suspicious or incompatible session. The provider is expected to write an
200
	 *  appropriate message to its logger.
201
	 */
202
	public function mergeMetadata( array $savedMetadata, array $providedMetadata ) {
203
		foreach ( $providedMetadata as $k => $v ) {
204
			if ( array_key_exists( $k, $savedMetadata ) && $savedMetadata[$k] !== $v ) {
205
				$e = new MetadataMergeException( "Key \"$k\" changed" );
206
				$e->setContext( [
207
					'old_value' => $savedMetadata[$k],
208
					'new_value' => $v,
209
				] );
210
				throw $e;
211
			}
212
		}
213
		return $providedMetadata;
214
	}
215
216
	/**
217
	 * Validate a loaded SessionInfo and refresh provider metadata
218
	 *
219
	 * This is similar in purpose to the 'SessionCheckInfo' hook, and also
220
	 * allows for updating the provider metadata. On failure, the provider is
221
	 * expected to write an appropriate message to its logger.
222
	 *
223
	 * @protected For use by \MediaWiki\Session\SessionManager only
224
	 * @param SessionInfo $info Any changes by mergeMetadata() will already be reflected here.
225
	 * @param WebRequest $request
226
	 * @param array|null &$metadata Provider metadata, may be altered.
227
	 * @return bool Return false to reject the SessionInfo after all.
228
	 */
229
	public function refreshSessionInfo( SessionInfo $info, WebRequest $request, &$metadata ) {
230
		return true;
231
	}
232
233
	/**
234
	 * Indicate whether self::persistSession() can save arbitrary session IDs
235
	 *
236
	 * If false, any session passed to self::persistSession() will have an ID
237
	 * that was originally provided by self::provideSessionInfo().
238
	 *
239
	 * If true, the provider may be passed sessions with arbitrary session IDs,
240
	 * and will be expected to manipulate the request in such a way that future
241
	 * requests will cause self::provideSessionInfo() to provide a SessionInfo
242
	 * with that ID.
243
	 *
244
	 * For example, a session provider for OAuth would function by matching the
245
	 * OAuth headers to a particular user, and then would use self::hashToSessionId()
246
	 * to turn the user and OAuth client ID (and maybe also the user token and
247
	 * client secret) into a session ID, and therefore can't easily assign that
248
	 * user+client a different ID. Similarly, a session provider for SSL client
249
	 * certificates would function by matching the certificate to a particular
250
	 * user, and then would use self::hashToSessionId() to turn the user and
251
	 * certificate fingerprint into a session ID, and therefore can't easily
252
	 * assign a different ID either. On the other hand, a provider that saves
253
	 * the session ID into a cookie can easily just set the cookie to a
254
	 * different value.
255
	 *
256
	 * @protected For use by \MediaWiki\Session\SessionBackend only
257
	 * @return bool
258
	 */
259
	abstract public function persistsSessionId();
260
261
	/**
262
	 * Indicate whether the user associated with the request can be changed
263
	 *
264
	 * If false, any session passed to self::persistSession() will have a user
265
	 * that was originally provided by self::provideSessionInfo(). Further,
266
	 * self::provideSessionInfo() may only provide sessions that have a user
267
	 * already set.
268
	 *
269
	 * If true, the provider may be passed sessions with arbitrary users, and
270
	 * will be expected to manipulate the request in such a way that future
271
	 * requests will cause self::provideSessionInfo() to provide a SessionInfo
272
	 * with that ID. This can be as simple as not passing any 'userInfo' into
273
	 * SessionInfo's constructor, in which case SessionInfo will load the user
274
	 * from the saved session's metadata.
275
	 *
276
	 * For example, a session provider for OAuth or SSL client certificates
277
	 * would function by matching the OAuth headers or certificate to a
278
	 * particular user, and thus would return false here since it can't
279
	 * arbitrarily assign those OAuth credentials or that certificate to a
280
	 * different user. A session provider that shoves information into cookies,
281
	 * on the other hand, could easily do so.
282
	 *
283
	 * @protected For use by \MediaWiki\Session\SessionBackend only
284
	 * @return bool
285
	 */
286
	abstract public function canChangeUser();
287
288
	/**
289
	 * Returns the duration (in seconds) for which users will be remembered when
290
	 * Session::setRememberUser() is set. Null means setting the remember flag will
291
	 * have no effect (and endpoints should not offer that option).
292
	 * @return int|null
293
	 */
294
	public function getRememberUserDuration() {
295
		return null;
296
	}
297
298
	/**
299
	 * Notification that the session ID was reset
300
	 *
301
	 * No need to persist here, persistSession() will be called if appropriate.
302
	 *
303
	 * @protected For use by \MediaWiki\Session\SessionBackend only
304
	 * @param SessionBackend $session Session to persist
305
	 * @param string $oldId Old session ID
306
	 * @codeCoverageIgnore
307
	 */
308
	public function sessionIdWasReset( SessionBackend $session, $oldId ) {
309
	}
310
311
	/**
312
	 * Persist a session into a request/response
313
	 *
314
	 * For example, you might set cookies for the session's ID, user ID, user
315
	 * name, and user token on the passed request.
316
	 *
317
	 * To correctly persist a user independently of the session ID, the
318
	 * provider should persist both the user ID (or name, but preferably the
319
	 * ID) and the user token. When reading the data from the request, it
320
	 * should construct a User object from the ID/name and then verify that the
321
	 * User object's token matches the token included in the request. Should
322
	 * the tokens not match, an anonymous user *must* be passed to
323
	 * SessionInfo::__construct().
324
	 *
325
	 * When persisting a user independently of the session ID,
326
	 * $session->shouldRememberUser() should be checked first. If this returns
327
	 * false, the user token *must not* be saved to cookies. The user name
328
	 * and/or ID may be persisted, and should be used to construct an
329
	 * unverified UserInfo to pass to SessionInfo::__construct().
330
	 *
331
	 * A backend that cannot persist sesison ID or user info should implement
332
	 * this as a no-op.
333
	 *
334
	 * @protected For use by \MediaWiki\Session\SessionBackend only
335
	 * @param SessionBackend $session Session to persist
336
	 * @param WebRequest $request Request into which to persist the session
337
	 */
338
	abstract public function persistSession( SessionBackend $session, WebRequest $request );
339
340
	/**
341
	 * Remove any persisted session from a request/response
342
	 *
343
	 * For example, blank and expire any cookies set by self::persistSession().
344
	 *
345
	 * A backend that cannot persist sesison ID or user info should implement
346
	 * this as a no-op.
347
	 *
348
	 * @protected For use by \MediaWiki\Session\SessionManager only
349
	 * @param WebRequest $request Request from which to remove any session data
350
	 */
351
	abstract public function unpersistSession( WebRequest $request );
352
353
	/**
354
	 * Prevent future sessions for the user
355
	 *
356
	 * If the provider is capable of returning a SessionInfo with a verified
357
	 * UserInfo for the named user in some manner other than by validating
358
	 * against $user->getToken(), steps must be taken to prevent that from
359
	 * occurring in the future. This might add the username to a blacklist, or
360
	 * it might just delete whatever authentication credentials would allow
361
	 * such a session in the first place (e.g. remove all OAuth grants or
362
	 * delete record of the SSL client certificate).
363
	 *
364
	 * The intention is that the named account will never again be usable for
365
	 * normal login (i.e. there is no way to undo the prevention of access).
366
	 *
367
	 * Note that the passed user name might not exist locally (i.e.
368
	 * User::idFromName( $username ) === 0); the name should still be
369
	 * prevented, if applicable.
370
	 *
371
	 * @protected For use by \MediaWiki\Session\SessionManager only
372
	 * @param string $username
373
	 */
374
	public function preventSessionsForUser( $username ) {
375
		if ( !$this->canChangeUser() ) {
376
			throw new \BadMethodCallException(
377
				__METHOD__ . ' must be implmented when canChangeUser() is false'
378
			);
379
		}
380
	}
381
382
	/**
383
	 * Invalidate existing sessions for a user
384
	 *
385
	 * If the provider has its own equivalent of CookieSessionProvider's Token
386
	 * cookie (and doesn't use User::getToken() to implement it), it should
387
	 * reset whatever token it does use here.
388
	 *
389
	 * @protected For use by \MediaWiki\Session\SessionManager only
390
	 * @param User $user;
0 ignored issues
show
There is no parameter named $user;. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
391
	 */
392
	public function invalidateSessionsForUser( User $user ) {
393
	}
394
395
	/**
396
	 * Return the HTTP headers that need varying on.
397
	 *
398
	 * The return value is such that someone could theoretically do this:
399
	 * @code
400
	 *  foreach ( $provider->getVaryHeaders() as $header => $options ) {
401
	 *  	$outputPage->addVaryHeader( $header, $options );
402
	 *  }
403
	 * @endcode
404
	 *
405
	 * @protected For use by \MediaWiki\Session\SessionManager only
406
	 * @return array
407
	 */
408
	public function getVaryHeaders() {
409
		return [];
410
	}
411
412
	/**
413
	 * Return the list of cookies that need varying on.
414
	 * @protected For use by \MediaWiki\Session\SessionManager only
415
	 * @return string[]
416
	 */
417
	public function getVaryCookies() {
418
		return [];
419
	}
420
421
	/**
422
	 * Get a suggested username for the login form
423
	 * @protected For use by \MediaWiki\Session\SessionBackend only
424
	 * @param WebRequest $request
425
	 * @return string|null
426
	 */
427
	public function suggestLoginUsername( WebRequest $request ) {
428
		return null;
429
	}
430
431
	/**
432
	 * Fetch the rights allowed the user when the specified session is active.
433
	 *
434
	 * This is mainly meant for allowing the user to restrict access to the account
435
	 * by certain methods; you probably want to use this with MWGrants. The returned
436
	 * rights will be intersected with the user's actual rights.
437
	 *
438
	 * @param SessionBackend $backend
439
	 * @return null|string[] Allowed user rights, or null to allow all.
440
	 */
441
	public function getAllowedUserRights( SessionBackend $backend ) {
442
		if ( $backend->getProvider() !== $this ) {
443
			// Not that this should ever happen...
444
			throw new \InvalidArgumentException( 'Backend\'s provider isn\'t $this' );
445
		}
446
447
		return null;
448
	}
449
450
	/**
451
	 * @note Only override this if it makes sense to instantiate multiple
452
	 *  instances of the provider. Value returned must be unique across
453
	 *  configured providers. If you override this, you'll likely need to
454
	 *  override self::describeMessage() as well.
455
	 * @return string
456
	 */
457
	public function __toString() {
458
		return get_class( $this );
459
	}
460
461
	/**
462
	 * Return a Message identifying this session type
463
	 *
464
	 * This default implementation takes the class name, lowercases it,
465
	 * replaces backslashes with dashes, and prefixes 'sessionprovider-' to
466
	 * determine the message key. For example, MediaWiki\Session\CookieSessionProvider
467
	 * produces 'sessionprovider-mediawiki-session-cookiesessionprovider'.
468
	 *
469
	 * @note If self::__toString() is overridden, this will likely need to be
470
	 *  overridden as well.
471
	 * @warning This will be called early during MediaWiki startup. Do not
472
	 *  use $wgUser, $wgLang, $wgOut, $wgParser, or their equivalents via
473
	 *  RequestContext from this method!
474
	 * @return \Message
475
	 */
476
	protected function describeMessage() {
477
		return wfMessage(
478
			'sessionprovider-' . str_replace( '\\', '-', strtolower( get_class( $this ) ) )
479
		);
480
	}
481
482
	public function describe( Language $lang ) {
483
		$msg = $this->describeMessage();
484
		$msg->inLanguage( $lang );
485
		if ( $msg->isDisabled() ) {
486
			$msg = wfMessage( 'sessionprovider-generic', (string)$this )->inLanguage( $lang );
487
		}
488
		return $msg->plain();
489
	}
490
491
	public function whyNoSession() {
492
		return null;
493
	}
494
495
	/**
496
	 * Hash data as a session ID
497
	 *
498
	 * Generally this will only be used when self::persistsSessionId() is false and
499
	 * the provider has to base the session ID on the verified user's identity
500
	 * or other static data. The SessionInfo should then typically have the
501
	 * 'forceUse' flag set to avoid persistent session failure if validation of
502
	 * the stored data fails.
503
	 *
504
	 * @param string $data
505
	 * @param string|null $key Defaults to $this->config->get( 'SecretKey' )
506
	 * @return string
507
	 */
508
	final protected function hashToSessionId( $data, $key = null ) {
509
		if ( !is_string( $data ) ) {
510
			throw new \InvalidArgumentException(
511
				'$data must be a string, ' . gettype( $data ) . ' was passed'
512
			);
513
		}
514
		if ( $key !== null && !is_string( $key ) ) {
515
			throw new \InvalidArgumentException(
516
				'$key must be a string or null, ' . gettype( $key ) . ' was passed'
517
			);
518
		}
519
520
		$hash = \MWCryptHash::hmac( "$this\n$data", $key ?: $this->config->get( 'SecretKey' ), false );
521
		if ( strlen( $hash ) < 32 ) {
522
			// Should never happen, even md5 is 128 bits
523
			// @codeCoverageIgnoreStart
524
			throw new \UnexpectedValueException( 'Hash fuction returned less than 128 bits' );
525
			// @codeCoverageIgnoreEnd
526
		}
527
		if ( strlen( $hash ) >= 40 ) {
528
			$hash = \Wikimedia\base_convert( $hash, 16, 32, 32 );
529
		}
530
		return substr( $hash, -32 );
531
	}
532
533
}
534