Issues (2473)

Branch: master

Security Analysis    no vulnerabilities found

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.

engine/classes/ElggCrypto.php (5 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
 * \ElggCrypto
4
 *
5
 * @package    Elgg.Core
6
 * @subpackage Crypto
7
 *
8
 * @access private
9
 */
10
class ElggCrypto {
11
12
	/**
13
	 * Character set for temp passwords (no risk of embedded profanity/glyphs that look similar)
14
	 */
15
	const CHARS_PASSWORD = 'bcdfghjklmnpqrstvwxyz2346789';
16
17
	/**
18
	 * Character set for hexadecimal
19
	 */
20
	const CHARS_HEX = '0123456789abcdef';
21
22
	/**
23
	 * Generate a string of highly randomized bytes (over the full 8-bit range).
24
	 *
25
	 * @param int $length Number of bytes needed
26
	 * @return string Random bytes
27
	 *
28
	 * @author George Argyros <[email protected]>
29
	 * @copyright 2012, George Argyros. All rights reserved.
30
	 * @license Modified BSD
31
	 * @link https://github.com/GeorgeArgyros/Secure-random-bytes-in-PHP/blob/master/srand.php Original
32
	 *
33
	 * Redistribution and use in source and binary forms, with or without
34
	 * modification, are permitted provided that the following conditions are met:
35
	 *    * Redistributions of source code must retain the above copyright
36
	 *      notice, this list of conditions and the following disclaimer.
37
	 *    * Redistributions in binary form must reproduce the above copyright
38
	 *      notice, this list of conditions and the following disclaimer in the
39
	 *      documentation and/or other materials provided with the distribution.
40
	 *    * Neither the name of the <organization> nor the
41
	 *      names of its contributors may be used to endorse or promote products
42
	 *      derived from this software without specific prior written permission.
43
	 *
44
	 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
45
	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
46
	 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
47
	 * DISCLAIMED. IN NO EVENT SHALL GEORGE ARGYROS BE LIABLE FOR ANY
48
	 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
49
	 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
50
	 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
51
	 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52
	 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
53
	 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54
	 */
55 3
	public function getRandomBytes($length) {
56 3
		$SSLstr = '4'; // http://xkcd.com/221/
57
58
		/**
59
		 * Our primary choice for a cryptographic strong randomness function is
60
		 * openssl_random_pseudo_bytes.
61
		 */
62 3
		if (function_exists('openssl_random_pseudo_bytes') && substr(PHP_OS, 0, 3) !== 'WIN') {
63 3
			$SSLstr = openssl_random_pseudo_bytes($length, $strong);
64 3
			if ($strong) {
65 3
				return $SSLstr;
66
			}
67
		}
68
69
		/**
70
		 * If mcrypt extension is available then we use it to gather entropy from
71
		 * the operating system's PRNG. This is better than reading /dev/urandom
72
		 * directly since it avoids reading larger blocks of data than needed.
73
		 */
74
		if (function_exists('mcrypt_create_iv') && substr(PHP_OS, 0, 3) !== 'WIN') {
75
			$str = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
76
			if ($str !== false) {
77
				return $str;
78
			}
79
		}
80
81
		/**
82
		 * No build-in crypto randomness function found. We collect any entropy
83
		 * available in the PHP core PRNGs along with some filesystem info and memory
84
		 * stats. To make this data cryptographically strong we add data either from
85
		 * /dev/urandom or if its unavailable, we gather entropy by measuring the
86
		 * time needed to compute a number of SHA-1 hashes.
87
		 */
88
		$str = '';
89
		$bits_per_round = 2; // bits of entropy collected in each clock drift round
90
		$msec_per_round = 400; // expected running time of each round in microseconds
91
		$hash_len = 20; // SHA-1 Hash length
92
		$total = $length; // total bytes of entropy to collect
93
94
		$handle = @fopen('/dev/urandom', 'rb');
95
		if ($handle && function_exists('stream_set_read_buffer')) {
96
			@stream_set_read_buffer($handle, 0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
97
		}
98
99
		do {
100
			$bytes = ($total > $hash_len) ? $hash_len : $total;
101
			$total -= $bytes;
102
103
			//collect any entropy available from the PHP system and filesystem
104
			$entropy = rand() . uniqid(mt_rand(), true) . $SSLstr;
105
			$entropy .= implode('', @fstat(@fopen(__FILE__, 'r')));
106
			$entropy .= memory_get_usage() . getmypid();
107
			$entropy .= serialize($_ENV) . serialize($_SERVER);
108
			if (function_exists('posix_times')) {
109
				$entropy .= serialize(posix_times());
110
			}
111
			if (function_exists('zend_thread_id')) {
112
				$entropy .= zend_thread_id();
113
			}
114
115
			if ($handle) {
116
				$entropy .= @fread($handle, $bytes);
117
			} else {
118
				// Measure the time that the operations will take on average
119
				for ($i = 0; $i < 3; $i++) {
120
					$c1 = microtime(true);
121
					$var = sha1(mt_rand());
122
					for ($j = 0; $j < 50; $j++) {
123
						$var = sha1($var);
124
					}
125
					$c2 = microtime(true);
126
					$entropy .= $c1 . $c2;
127
				}
128
129
				// Based on the above measurement determine the total rounds
130
				// in order to bound the total running time.
131
				$rounds = (int) ($msec_per_round * 50 / (int) (($c2 - $c1) * 1000000));
0 ignored issues
show
The variable $c2 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
The variable $c1 does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
132
133
				// Take the additional measurements. On average we can expect
134
				// at least $bits_per_round bits of entropy from each measurement.
135
				$iter = $bytes * (int) (ceil(8 / $bits_per_round));
136
137
				for ($i = 0; $i < $iter; $i++) {
138
					$c1 = microtime();
139
					$var = sha1(mt_rand());
140
					for ($j = 0; $j < $rounds; $j++) {
141
						$var = sha1($var);
142
					}
143
					$c2 = microtime();
144
					$entropy .= $c1 . $c2;
145
				}
146
			}
147
148
			// We assume sha1 is a deterministic extractor for the $entropy variable.
149
			$str .= sha1($entropy, true);
150
151
		} while ($length > strlen($str));
152
153
		if ($handle) {
154
			@fclose($handle);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
155
		}
156
157
		return substr($str, 0, $length);
158
	}
159
160
	/**
161
	 * Get an HMAC token builder/validator object
162
	 *
163
	 * @param mixed  $data HMAC data or serializable data
164
	 * @param string $algo Hash algorithm
165
	 * @param string $key  Optional key (default uses site secret)
166
	 *
167
	 * @return \Elgg\Security\Hmac
168
	 */
169 7
	public function getHmac($data, $algo = 'sha256', $key = '') {
170 7
		if (!$key) {
171
			$key = _elgg_services()->siteSecret->get(true);
172
		}
173 7
		return new Elgg\Security\Hmac($key, [$this, 'areEqual'], $data, $algo);
0 ignored issues
show
It seems like $key defined by _elgg_services()->siteSecret->get(true) on line 171 can also be of type false or null; however, Elgg\Security\Hmac::__construct() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
174
	}
175
176
	/**
177
	 * Generate a random string of specified length.
178
	 *
179
	 * Uses supplied character list for generating the new string.
180
	 * If no character list provided - uses Base64 URL character set.
181
	 *
182
	 * @param int         $length Desired length of the string
183
	 * @param string|null $chars  Characters to be chosen from randomly. If not given, the Base64 URL
184
	 *                            charset will be used.
185
	 *
186
	 * @return string The random string
187
	 *
188
	 * @throws InvalidArgumentException
189
	 *
190
	 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
191
	 * @license   http://framework.zend.com/license/new-bsd New BSD License
192
	 *
193
	 * @see https://github.com/zendframework/zf2/blob/master/library/Zend/Math/Rand.php#L179
194
	 */
195 7
	public function getRandomString($length, $chars = null) {
196 7
		if ($length < 1) {
197
			throw new \InvalidArgumentException('Length should be >= 1');
198
		}
199
200 7
		if (empty($chars)) {
201 4
			$numBytes = ceil($length * 0.75);
202 4
			$bytes    = $this->getRandomBytes($numBytes);
203 4
			$string = substr(rtrim(base64_encode($bytes), '='), 0, $length);
204
205
			// Base64 URL
206 4
			return strtr($string, '+/', '-_');
207
		}
208
209 3
		if ($chars == self::CHARS_HEX) {
210
			// hex is easy
211 1
			$bytes = $this->getRandomBytes(ceil($length / 2));
212 1
			return substr(bin2hex($bytes), 0, $length);
213
		}
214
215 2
		$listLen = strlen($chars);
216
217 2
		if ($listLen == 1) {
218
			return str_repeat($chars, $length);
219
		}
220
221 2
		$bytes  = $this->getRandomBytes($length);
222 2
		$pos    = 0;
223 2
		$result = '';
224 2
		for ($i = 0; $i < $length; $i++) {
225 2
			$pos     = ($pos + ord($bytes[$i])) % $listLen;
226 2
			$result .= $chars[$pos];
227 2
		}
228
229 2
		return $result;
230
	}
231
232
	/**
233
	 * Are two strings equal (compared in constant time)?
234
	 *
235
	 * @param string $str1 First string to compare
236
	 * @param string $str2 Second string to compare
237
	 *
238
	 * @return bool
239
	 *
240
	 * Based on password_verify in PasswordCompat
241
	 * @author Anthony Ferrara <[email protected]>
242
	 * @license http://www.opensource.org/licenses/mit-license.html MIT License
243
	 * @copyright 2012 The Authors
244
	 */
245 1
	public function areEqual($str1, $str2) {
246 1
		$len1 = $this->strlen($str1);
247 1
		$len2 = $this->strlen($str2);
248 1
		if ($len1 !== $len2) {
249
			return false;
250
		}
251
252 1
		$status = 0;
253 1
		for ($i = 0; $i < $len1; $i++) {
254 1
			$status |= (ord($str1[$i]) ^ ord($str2[$i]));
255 1
		}
256
257 1
		return $status === 0;
258
	}
259
260
	/**
261
	 * Count the number of bytes in a string
262
	 *
263
	 * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
264
	 * In this case, strlen() will count the number of *characters* based on the internal encoding. A
265
	 * sequence of bytes might be regarded as a single multibyte character.
266
	 *
267
	 * Use elgg_strlen() to count UTF-characters instead of bytes.
268
	 *
269
	 * @param string $binary_string The input string
270
	 *
271
	 * @return int The number of bytes
272
	 *
273
	 * From PasswordCompat\binary\_strlen
274
	 * @author Anthony Ferrara <[email protected]>
275
	 * @license http://www.opensource.org/licenses/mit-license.html MIT License
276
	 * @copyright 2012 The Authors
277
	 */
278 1
	protected function strlen($binary_string) {
279 1
		if (function_exists('mb_strlen')) {
280 1
			return mb_strlen($binary_string, '8bit');
281
		}
282
		return strlen($binary_string);
283
	}
284
}
285