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/libs/CryptRand.php (1 issue)

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
 * A cryptographic random generator class used for generating secret keys
4
 *
5
 * This is based in part on Drupal code as well as what we used in our own code
6
 * prior to introduction of this class.
7
 *
8
 * This program is free software; you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation; either version 2 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License along
19
 * with this program; if not, write to the Free Software Foundation, Inc.,
20
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21
 * http://www.gnu.org/copyleft/gpl.html
22
 *
23
 * @author Daniel Friesen
24
 * @file
25
 */
26
use Psr\Log\LoggerInterface;
27
28
class CryptRand {
29
	/**
30
	 * Minimum number of iterations we want to make in our drift calculations.
31
	 */
32
	const MIN_ITERATIONS = 1000;
33
34
	/**
35
	 * Number of milliseconds we want to spend generating each separate byte
36
	 * of the final generated bytes.
37
	 * This is used in combination with the hash length to determine the duration
38
	 * we should spend doing drift calculations.
39
	 */
40
	const MSEC_PER_BYTE = 0.5;
41
42
	/**
43
	 * A boolean indicating whether the previous random generation was done using
44
	 * cryptographically strong random number generator or not.
45
	 */
46
	protected $strong = null;
47
48
	/**
49
	 * List of functions to call to generate some random state
50
	 *
51
	 * @var callable[]
52
	 */
53
	protected $randomFuncs = [];
54
55
	/**
56
	 * List of files to generate some random state from
57
	 *
58
	 * @var string[]
59
	 */
60
	protected $randomFiles = [];
61
62
	/**
63
	 * @var LoggerInterface
64
	 */
65
	protected $logger;
66
67
	public function __construct( array $randomFuncs, array $randomFiles, LoggerInterface $logger ) {
68
		$this->randomFuncs = $randomFuncs;
69
		$this->randomFiles = $randomFiles;
70
		$this->logger = $logger;
71
	}
72
73
	/**
74
	 * Initialize an initial random state based off of whatever we can find
75
	 * @return string
76
	 */
77
	protected function initialRandomState() {
0 ignored issues
show
initialRandomState uses the super-global variable $_SERVER 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...
78
		// $_SERVER contains a variety of unstable user and system specific information
79
		// It'll vary a little with each page, and vary even more with separate users
80
		// It'll also vary slightly across different machines
81
		$state = serialize( $_SERVER );
82
83
		// Try to gather a little entropy from the different php rand sources
84
		$state .= rand() . uniqid( mt_rand(), true );
85
86
		// Include some information about the filesystem's current state in the random state
87
		$files = $this->randomFiles;
88
89
		// We know this file is here so grab some info about ourselves
90
		$files[] = __FILE__;
91
92
		// We must also have a parent folder, and with the usual file structure, a grandparent
93
		$files[] = __DIR__;
94
		$files[] = dirname( __DIR__ );
95
96
		foreach ( $files as $file ) {
97
			MediaWiki\suppressWarnings();
98
			$stat = stat( $file );
99
			MediaWiki\restoreWarnings();
100
			if ( $stat ) {
101
				// stat() duplicates data into numeric and string keys so kill off all the numeric ones
102
				foreach ( $stat as $k => $v ) {
103
					if ( is_numeric( $k ) ) {
104
						unset( $k );
105
					}
106
				}
107
				// The absolute filename itself will differ from install to install so don't leave it out
108
				$path = realpath( $file );
109
				if ( $path !== false ) {
110
					$state .= $path;
111
				} else {
112
					$state .= $file;
113
				}
114
				$state .= implode( '', $stat );
115
			} else {
116
				// The fact that the file isn't there is worth at least a
117
				// minuscule amount of entropy.
118
				$state .= '0';
119
			}
120
		}
121
122
		// Try and make this a little more unstable by including the varying process
123
		// id of the php process we are running inside of if we are able to access it
124
		if ( function_exists( 'getmypid' ) ) {
125
			$state .= getmypid();
126
		}
127
128
		// If available try to increase the instability of the data by throwing in
129
		// the precise amount of memory that we happen to be using at the moment.
130
		if ( function_exists( 'memory_get_usage' ) ) {
131
			$state .= memory_get_usage( true );
132
		}
133
134
		foreach ( $this->randomFuncs as $randomFunc ) {
135
			$state .= call_user_func( $randomFunc );
136
		}
137
138
		return $state;
139
	}
140
141
	/**
142
	 * Randomly hash data while mixing in clock drift data for randomness
143
	 *
144
	 * @param string $data The data to randomly hash.
145
	 * @return string The hashed bytes
146
	 * @author Tim Starling
147
	 */
148
	protected function driftHash( $data ) {
149
		// Minimum number of iterations (to avoid slow operations causing the
150
		// loop to gather little entropy)
151
		$minIterations = self::MIN_ITERATIONS;
152
		// Duration of time to spend doing calculations (in seconds)
153
		$duration = ( self::MSEC_PER_BYTE / 1000 ) * MWCryptHash::hashLength();
154
		// Create a buffer to use to trigger memory operations
155
		$bufLength = 10000000;
156
		$buffer = str_repeat( ' ', $bufLength );
157
		$bufPos = 0;
158
159
		// Iterate for $duration seconds or at least $minIterations number of iterations
160
		$iterations = 0;
161
		$startTime = microtime( true );
162
		$currentTime = $startTime;
163
		while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
164
			// Trigger some memory writing to trigger some bus activity
165
			// This may create variance in the time between iterations
166
			$bufPos = ( $bufPos + 13 ) % $bufLength;
167
			$buffer[$bufPos] = ' ';
168
			// Add the drift between this iteration and the last in as entropy
169
			$nextTime = microtime( true );
170
			$delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
171
			$data .= $delta;
172
			// Every 100 iterations hash the data and entropy
173
			if ( $iterations % 100 === 0 ) {
174
				$data = sha1( $data );
175
			}
176
			$currentTime = $nextTime;
177
			$iterations++;
178
		}
179
		$timeTaken = $currentTime - $startTime;
180
		$data = MWCryptHash::hash( $data );
181
182
		$this->logger->debug( "Clock drift calculation " .
183
			"(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
184
			"iterations=$iterations, " .
185
			"time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)" );
186
187
		return $data;
188
	}
189
190
	/**
191
	 * Return a rolling random state initially build using data from unstable sources
192
	 * @return string A new weak random state
193
	 */
194
	protected function randomState() {
195
		static $state = null;
196
		if ( is_null( $state ) ) {
197
			// Initialize the state with whatever unstable data we can find
198
			// It's important that this data is hashed right afterwards to prevent
199
			// it from being leaked into the output stream
200
			$state = MWCryptHash::hash( $this->initialRandomState() );
201
		}
202
		// Generate a new random state based on the initial random state or previous
203
		// random state by combining it with clock drift
204
		$state = $this->driftHash( $state );
205
206
		return $state;
207
	}
208
209
	/**
210
	 * Return a boolean indicating whether or not the source used for cryptographic
211
	 * random bytes generation in the previously run generate* call
212
	 * was cryptographically strong.
213
	 *
214
	 * @return bool Returns true if the source was strong, false if not.
215
	 */
216
	public function wasStrong() {
217
		if ( is_null( $this->strong ) ) {
218
			throw new RuntimeException( __METHOD__ . ' called before generation of random data' );
219
		}
220
221
		return $this->strong;
222
	}
223
224
	/**
225
	 * Generate a run of (ideally) cryptographically random data and return
226
	 * it in raw binary form.
227
	 * You can use CryptRand::wasStrong() if you wish to know if the source used
228
	 * was cryptographically strong.
229
	 *
230
	 * @param int $bytes The number of bytes of random data to generate
231
	 * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
232
	 *                          strong sources of entropy even if reading from them may steal
233
	 *                          more entropy from the system than optimal.
234
	 * @return string Raw binary random data
235
	 */
236
	public function generate( $bytes, $forceStrong = false ) {
237
238
		$bytes = floor( $bytes );
239
		static $buffer = '';
240
		if ( is_null( $this->strong ) ) {
241
			// Set strength to false initially until we know what source data is coming from
242
			$this->strong = true;
243
		}
244
245
		if ( strlen( $buffer ) < $bytes ) {
246
			// If available make use of mcrypt_create_iv URANDOM source to generate randomness
247
			// On unix-like systems this reads from /dev/urandom but does it without any buffering
248
			// and bypasses openbasedir restrictions, so it's preferable to reading directly
249
			// On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
250
			// entropy so this is also preferable to just trying to read urandom because it may work
251
			// on Windows systems as well.
252
			if ( function_exists( 'mcrypt_create_iv' ) ) {
253
				$rem = $bytes - strlen( $buffer );
254
				$iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
255
				if ( $iv === false ) {
256
					$this->logger->debug( "mcrypt_create_iv returned false." );
257
				} else {
258
					$buffer .= $iv;
259
					$this->logger->debug( "mcrypt_create_iv generated " . strlen( $iv ) .
260
						" bytes of randomness." );
261
				}
262
			}
263
		}
264
265
		if ( strlen( $buffer ) < $bytes ) {
266
			if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
267
				$rem = $bytes - strlen( $buffer );
268
				$openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
269
				if ( $openssl_bytes === false ) {
270
					$this->logger->debug( "openssl_random_pseudo_bytes returned false." );
271
				} else {
272
					$buffer .= $openssl_bytes;
273
					$this->logger->debug( "openssl_random_pseudo_bytes generated " .
274
						strlen( $openssl_bytes ) . " bytes of " .
275
						( $openssl_strong ? "strong" : "weak" ) . " randomness." );
276
				}
277
				if ( strlen( $buffer ) >= $bytes ) {
278
					// openssl tells us if the random source was strong, if some of our data was generated
279
					// using it use it's say on whether the randomness is strong
280
					$this->strong = !!$openssl_strong;
281
				}
282
			}
283
		}
284
285
		// Only read from urandom if we can control the buffer size or were passed forceStrong
286
		if ( strlen( $buffer ) < $bytes &&
287
			( function_exists( 'stream_set_read_buffer' ) || $forceStrong )
288
		) {
289
			$rem = $bytes - strlen( $buffer );
290
			if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
291
				$this->logger->debug( "Was forced to read from /dev/urandom " .
292
					"without control over the buffer size." );
293
			}
294
			// /dev/urandom is generally considered the best possible commonly
295
			// available random source, and is available on most *nix systems.
296
			MediaWiki\suppressWarnings();
297
			$urandom = fopen( "/dev/urandom", "rb" );
298
			MediaWiki\restoreWarnings();
299
300
			// Attempt to read all our random data from urandom
301
			// php's fread always does buffered reads based on the stream's chunk_size
302
			// so in reality it will usually read more than the amount of data we're
303
			// asked for and not storing that risks depleting the system's random pool.
304
			// If stream_set_read_buffer is available set the chunk_size to the amount
305
			// of data we need. Otherwise read 8k, php's default chunk_size.
306
			if ( $urandom ) {
307
				// php's default chunk_size is 8k
308
				$chunk_size = 1024 * 8;
309
				if ( function_exists( 'stream_set_read_buffer' ) ) {
310
					// If possible set the chunk_size to the amount of data we need
311
					stream_set_read_buffer( $urandom, $rem );
312
					$chunk_size = $rem;
313
				}
314
				$random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
315
				$buffer .= $random_bytes;
316
				fclose( $urandom );
317
				$this->logger->debug( "/dev/urandom generated " . strlen( $random_bytes ) .
318
					" bytes of randomness." );
319
320
				if ( strlen( $buffer ) >= $bytes ) {
321
					// urandom is always strong, set to true if all our data was generated using it
322
					$this->strong = true;
323
				}
324
			} else {
325
				$this->logger->debug( "/dev/urandom could not be opened." );
326
			}
327
		}
328
329
		// If we cannot use or generate enough data from a secure source
330
		// use this loop to generate a good set of pseudo random data.
331
		// This works by initializing a random state using a pile of unstable data
332
		// and continually shoving it through a hash along with a variable salt.
333
		// We hash the random state with more salt to avoid the state from leaking
334
		// out and being used to predict the /randomness/ that follows.
335
		if ( strlen( $buffer ) < $bytes ) {
336
			$this->logger->debug( __METHOD__ .
337
				": Falling back to using a pseudo random state to generate randomness." );
338
		}
339
		while ( strlen( $buffer ) < $bytes ) {
340
			$buffer .= MWCryptHash::hmac( $this->randomState(), strval( mt_rand() ) );
341
			// This code is never really cryptographically strong, if we use it
342
			// at all, then set strong to false.
343
			$this->strong = false;
344
		}
345
346
		// Once the buffer has been filled up with enough random data to fulfill
347
		// the request shift off enough data to handle the request and leave the
348
		// unused portion left inside the buffer for the next request for random data
349
		$generated = substr( $buffer, 0, $bytes );
350
		$buffer = substr( $buffer, $bytes );
351
352
		$this->logger->debug( strlen( $buffer ) .
353
			" bytes of randomness leftover in the buffer." );
354
355
		return $generated;
356
	}
357
358
	/**
359
	 * Generate a run of (ideally) cryptographically random data and return
360
	 * it in hexadecimal string format.
361
	 * You can use CryptRand::wasStrong() if you wish to know if the source used
362
	 * was cryptographically strong.
363
	 *
364
	 * @param int $chars The number of hex chars of random data to generate
365
	 * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
366
	 *                          strong sources of entropy even if reading from them may steal
367
	 *                          more entropy from the system than optimal.
368
	 * @return string Hexadecimal random data
369
	 */
370
	public function generateHex( $chars, $forceStrong = false ) {
371
		// hex strings are 2x the length of raw binary so we divide the length in half
372
		// odd numbers will result in a .5 that leads the generate() being 1 character
373
		// short, so we use ceil() to ensure that we always have enough bytes
374
		$bytes = ceil( $chars / 2 );
375
		// Generate the data and then convert it to a hex string
376
		$hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
377
378
		// A bit of paranoia here, the caller asked for a specific length of string
379
		// here, and it's possible (eg when given an odd number) that we may actually
380
		// have at least 1 char more than they asked for. Just in case they made this
381
		// call intending to insert it into a database that does truncation we don't
382
		// want to give them too much and end up with their database and their live
383
		// code having two different values because part of what we gave them is truncated
384
		// hence, we strip out any run of characters longer than what we were asked for.
385
		return substr( $hex, 0, $chars );
386
	}
387
}
388