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/registration/ExtensionRegistry.php (2 issues)

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
use MediaWiki\MediaWikiServices;
4
5
/**
6
 * ExtensionRegistry class
7
 *
8
 * The Registry loads JSON files, and uses a Processor
9
 * to extract information from them. It also registers
10
 * classes with the autoloader.
11
 *
12
 * @since 1.25
13
 */
14
class ExtensionRegistry {
15
16
	/**
17
	 * "requires" key that applies to MediaWiki core/$wgVersion
18
	 */
19
	const MEDIAWIKI_CORE = 'MediaWiki';
20
21
	/**
22
	 * Version of the highest supported manifest version
23
	 */
24
	const MANIFEST_VERSION = 2;
25
26
	/**
27
	 * Version of the oldest supported manifest version
28
	 */
29
	const OLDEST_MANIFEST_VERSION = 1;
30
31
	/**
32
	 * Bump whenever the registration cache needs resetting
33
	 */
34
	const CACHE_VERSION = 3;
35
36
	/**
37
	 * Special key that defines the merge strategy
38
	 *
39
	 * @since 1.26
40
	 */
41
	const MERGE_STRATEGY = '_merge_strategy';
42
43
	/**
44
	 * @var BagOStuff
45
	 */
46
	protected $cache;
47
48
	/**
49
	 * Array of loaded things, keyed by name, values are credits information
50
	 *
51
	 * @var array
52
	 */
53
	private $loaded = [];
54
55
	/**
56
	 * List of paths that should be loaded
57
	 *
58
	 * @var array
59
	 */
60
	protected $queued = [];
61
62
	/**
63
	 * Items in the JSON file that aren't being
64
	 * set as globals
65
	 *
66
	 * @var array
67
	 */
68
	protected $attributes = [];
69
70
	/**
71
	 * @var ExtensionRegistry
72
	 */
73
	private static $instance;
74
75
	/**
76
	 * @return ExtensionRegistry
77
	 */
78
	public static function getInstance() {
79
		if ( self::$instance === null ) {
80
			self::$instance = new self();
81
		}
82
83
		return self::$instance;
84
	}
85
86
	public function __construct() {
87
		// We use a try/catch because we don't want to fail here
88
		// if $wgObjectCaches is not configured properly for APC setup
89
		try {
90
			$this->cache = MediaWikiServices::getInstance()->getLocalServerObjectCache();
91
		} catch ( MWException $e ) {
92
			$this->cache = new EmptyBagOStuff();
93
		}
94
	}
95
96
	/**
97
	 * @param string $path Absolute path to the JSON file
98
	 */
99
	public function queue( $path ) {
100
		global $wgExtensionInfoMTime;
101
102
		$mtime = $wgExtensionInfoMTime;
103
		if ( $mtime === false ) {
104
			if ( file_exists( $path ) ) {
105
				$mtime = filemtime( $path );
106
			} else {
107
				throw new Exception( "$path does not exist!" );
108
			}
109
			if ( !$mtime ) {
110
				$err = error_get_last();
111
				throw new Exception( "Couldn't stat $path: {$err['message']}" );
112
			}
113
		}
114
		$this->queued[$path] = $mtime;
115
	}
116
117
	public function loadFromQueue() {
118
		global $wgVersion;
119
		if ( !$this->queued ) {
120
			return;
121
		}
122
123
		// A few more things to vary the cache on
124
		$versions = [
125
			'registration' => self::CACHE_VERSION,
126
			'mediawiki' => $wgVersion
127
		];
128
129
		// See if this queue is in APC
130
		$key = wfMemcKey(
131
			'registration',
132
			md5( json_encode( $this->queued + $versions ) )
133
		);
134
		$data = $this->cache->get( $key );
135
		if ( $data ) {
136
			$this->exportExtractedData( $data );
137
		} else {
138
			$data = $this->readFromQueue( $this->queued );
139
			$this->exportExtractedData( $data );
140
			// Do this late since we don't want to extract it since we already
141
			// did that, but it should be cached
142
			$data['globals']['wgAutoloadClasses'] += $data['autoload'];
143
			unset( $data['autoload'] );
144
			$this->cache->set( $key, $data, 60 * 60 * 24 );
145
		}
146
		$this->queued = [];
147
	}
148
149
	/**
150
	 * Get the current load queue. Not intended to be used
151
	 * outside of the installer.
152
	 *
153
	 * @return array
154
	 */
155
	public function getQueue() {
156
		return $this->queued;
157
	}
158
159
	/**
160
	 * Clear the current load queue. Not intended to be used
161
	 * outside of the installer.
162
	 */
163
	public function clearQueue() {
164
		$this->queued = [];
165
	}
166
167
	/**
168
	 * Process a queue of extensions and return their extracted data
169
	 *
170
	 * @param array $queue keys are filenames, values are ignored
171
	 * @return array extracted info
172
	 * @throws Exception
173
	 */
174
	public function readFromQueue( array $queue ) {
0 ignored issues
show
readFromQueue uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
175
		global $wgVersion;
176
		$autoloadClasses = [];
177
		$autoloaderPaths = [];
178
		$processor = new ExtensionProcessor();
179
		$incompatible = [];
180
		$coreVersionParser = new CoreVersionChecker( $wgVersion );
181
		foreach ( $queue as $path => $mtime ) {
182
			$json = file_get_contents( $path );
183
			if ( $json === false ) {
184
				throw new Exception( "Unable to read $path, does it exist?" );
185
			}
186
			$info = json_decode( $json, /* $assoc = */ true );
187
			if ( !is_array( $info ) ) {
188
				throw new Exception( "$path is not a valid JSON file." );
189
			}
190
191
			// Check any constraints against MediaWiki core
192
			$requires = $processor->getRequirements( $info );
193
			if ( isset( $requires[self::MEDIAWIKI_CORE] )
194
				&& !$coreVersionParser->check( $requires[self::MEDIAWIKI_CORE] )
195
			) {
196
				// Doesn't match, mark it as incompatible.
197
				$incompatible[] = "{$info['name']} is not compatible with the current "
198
					. "MediaWiki core (version {$wgVersion}), it requires: " . $requires[self::MEDIAWIKI_CORE]
199
					. '.';
200
				continue;
201
			}
202
203
			if ( !isset( $info['manifest_version'] ) ) {
204
				// For backwards-compatability, assume a version of 1
205
				$info['manifest_version'] = 1;
206
			}
207
			$version = $info['manifest_version'];
208
			if ( $version < self::OLDEST_MANIFEST_VERSION || $version > self::MANIFEST_VERSION ) {
209
				throw new Exception( "$path: unsupported manifest_version: {$version}" );
210
			}
211
212
			$autoload = $this->processAutoLoader( dirname( $path ), $info );
213
			// Set up the autoloader now so custom processors will work
214
			$GLOBALS['wgAutoloadClasses'] += $autoload;
215
			$autoloadClasses += $autoload;
216
217
			// Get extra paths for later inclusion
218
			$autoloaderPaths = array_merge( $autoloaderPaths,
219
				$processor->getExtraAutoloaderPaths( dirname( $path ), $info ) );
220
			// Compatible, read and extract info
221
			$processor->extractInfo( $path, $info, $version );
222
		}
223
		if ( $incompatible ) {
224
			if ( count( $incompatible ) === 1 ) {
225
				throw new Exception( $incompatible[0] );
226
			} else {
227
				throw new Exception( implode( "\n", $incompatible ) );
228
			}
229
		}
230
		$data = $processor->getExtractedInfo();
231
		// Need to set this so we can += to it later
232
		$data['globals']['wgAutoloadClasses'] = [];
233
		$data['autoload'] = $autoloadClasses;
234
		$data['autoloaderPaths'] = $autoloaderPaths;
235
		return $data;
236
	}
237
238
	protected function exportExtractedData( array $info ) {
0 ignored issues
show
exportExtractedData uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
239
		foreach ( $info['globals'] as $key => $val ) {
240
			// If a merge strategy is set, read it and remove it from the value
241
			// so it doesn't accidentally end up getting set.
242
			if ( is_array( $val ) && isset( $val[self::MERGE_STRATEGY] ) ) {
243
				$mergeStrategy = $val[self::MERGE_STRATEGY];
244
				unset( $val[self::MERGE_STRATEGY] );
245
			} else {
246
				$mergeStrategy = 'array_merge';
247
			}
248
249
			// Optimistic: If the global is not set, or is an empty array, replace it entirely.
250
			// Will be O(1) performance.
251 View Code Duplication
			if ( !isset( $GLOBALS[$key] ) || ( is_array( $GLOBALS[$key] ) && !$GLOBALS[$key] ) ) {
252
				$GLOBALS[$key] = $val;
253
				continue;
254
			}
255
256
			if ( !is_array( $GLOBALS[$key] ) || !is_array( $val ) ) {
257
				// config setting that has already been overridden, don't set it
258
				continue;
259
			}
260
261
			switch ( $mergeStrategy ) {
262
				case 'array_merge_recursive':
263
					$GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
264
					break;
265
				case 'array_replace_recursive':
266
					$GLOBALS[$key] = array_replace_recursive( $GLOBALS[$key], $val );
267
					break;
268
				case 'array_plus_2d':
269
					$GLOBALS[$key] = wfArrayPlus2d( $GLOBALS[$key], $val );
270
					break;
271
				case 'array_plus':
272
					$GLOBALS[$key] += $val;
273
					break;
274
				case 'array_merge':
275
					$GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
276
					break;
277
				default:
278
					throw new UnexpectedValueException( "Unknown merge strategy '$mergeStrategy'" );
279
			}
280
		}
281
282
		foreach ( $info['defines'] as $name => $val ) {
283
			define( $name, $val );
284
		}
285
		foreach ( $info['autoloaderPaths'] as $path ) {
286
			require_once $path;
287
		}
288
		foreach ( $info['callbacks'] as $cb ) {
289
			call_user_func( $cb );
290
		}
291
292
		$this->loaded += $info['credits'];
293
		if ( $info['attributes'] ) {
294
			if ( !$this->attributes ) {
295
				$this->attributes = $info['attributes'];
296
			} else {
297
				$this->attributes = array_merge_recursive( $this->attributes, $info['attributes'] );
298
			}
299
		}
300
	}
301
302
	/**
303
	 * Loads and processes the given JSON file without delay
304
	 *
305
	 * If some extensions are already queued, this will load
306
	 * those as well.
307
	 *
308
	 * @param string $path Absolute path to the JSON file
309
	 */
310
	public function load( $path ) {
311
		$this->loadFromQueue(); // First clear the queue
312
		$this->queue( $path );
313
		$this->loadFromQueue();
314
	}
315
316
	/**
317
	 * Whether a thing has been loaded
318
	 * @param string $name
319
	 * @return bool
320
	 */
321
	public function isLoaded( $name ) {
322
		return isset( $this->loaded[$name] );
323
	}
324
325
	/**
326
	 * @param string $name
327
	 * @return array
328
	 */
329
	public function getAttribute( $name ) {
330
		if ( isset( $this->attributes[$name] ) ) {
331
			return $this->attributes[$name];
332
		} else {
333
			return [];
334
		}
335
	}
336
337
	/**
338
	 * Get information about all things
339
	 *
340
	 * @return array
341
	 */
342
	public function getAllThings() {
343
		return $this->loaded;
344
	}
345
346
	/**
347
	 * Mark a thing as loaded
348
	 *
349
	 * @param string $name
350
	 * @param array $credits
351
	 */
352
	protected function markLoaded( $name, array $credits ) {
353
		$this->loaded[$name] = $credits;
354
	}
355
356
	/**
357
	 * Register classes with the autoloader
358
	 *
359
	 * @param string $dir
360
	 * @param array $info
361
	 * @return array
362
	 */
363
	protected function processAutoLoader( $dir, array $info ) {
364
		if ( isset( $info['AutoloadClasses'] ) ) {
365
			// Make paths absolute, relative to the JSON file
366
			return array_map( function( $file ) use ( $dir ) {
367
				return "$dir/$file";
368
			}, $info['AutoloadClasses'] );
369
		} else {
370
			return [];
371
		}
372
	}
373
}
374