Issues (94)

Security Analysis    no request data  

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.

Random.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 namespace nyx\utils;
2
3
// External dependencies
4
use nyx\core;
5
use nyx\diagnostics;
6
7
/**
8
 * Random
9
 *
10
 * Utilities for generating and dealing with (pseudo-)random values.
11
 *
12
 * --- Strength
13
 *
14
 * All methods in this class can be called with a strength setting, being one of the STRENGTH_* class
15
 * constants and STRENGTH_MEDIUM being the default for each method.
16
 *
17
 *   - STRENGTH_STRONG is cryptographically secure but may be the slowest setting;
18
 *   - STRENGTH_MEDIUM is cryptographically secure and can be safely used for generating keys and salts;
19
 *   - STRENGTH_WEAK is *not* cryptographically secure but should be used in a non-crypto context for
20
 *     generating randomized values (it's the fastest out of the settings);
21
 *   - STRENGTH_NONE is *absolutely not* cryptographically secure - it should only be used is a context
22
 *     with absolutely no relation to encryption or authentication. Currently it only affects Random::string()
23
 *     in that this method will use str_shuffle() instead of generating a string based on a stronger
24
 *     pseudo-random seed (which is, simply, considerably faster);
25
 *
26
 * Important note: This class *is not* a cryptography class and does not perform any sort of mixing
27
 * of the generated values. For stronger input vectors for actual encryption algorithms you may want
28
 * to employ a mixing strategy for the data generated by this class before signing it off.
29
 *
30
 * In some situations, for instance when a weak random integer is sufficient, for performance reasons you
31
 * may be better off simply using mt_rand() (which is also used by self::int() if called with STRENGTH_WEAK) -
32
 * this class does however provide a consistent API and error handling to ensure nothing silently fails
33
 * leading to security holes.
34
 *
35
 * --- Sources
36
 *
37
 * This class is primarily a strict wrapper around PHP's native random_bytes() and random_int() functions,
38
 * which make use of the following sources:
39
 *   - Windows: CryptGenRandom() (only)
40
 *   - Others: getrandom(2) syscall (Linux only), then /dev/urandom
41
 *
42
 * The first valid source in those orders gets used. In the edge case where that procedure fails,
43
 * this class throws exceptions, but does attempt one additional fallback before doing so:
44
 *   - openssl_random_pseudo_bytes(), if available (which uses a userspace hash algo making it
45
 *     potentially an additional point of failure and thus only valid for the STRENGTH_MEDIUM setting
46
 *
47
 * No additional userspace entropy sources are used nor introduced by this utility. Libsodium may be
48
 * introduced as an additional primary fallback at a later date.
49
 *
50
 * The fallback mechanism is lazily instantiated - unless PHP's native functions fail us, there will
51
 * be minimal overhead of using this class over random_bytes() directly, but with added verbosity.
52
 *
53
 * --- Others
54
 *
55
 * Based on Anthony Ferrara's work on RandomLib {@see https://github.com/ircmaxell/RandomLib}.
56
 *
57
 * If you need an utility for generating random/fake real-world data, you should take a look
58
 * at Faker {@see https://github.com/fzaninotto/Faker}. This functionality is beyond the scope
59
 * of this utility.
60
 *
61
 * @package     Nyx\Utils
62
 * @version     0.0.6
63
 * @author      Michal Chojnacki <[email protected]>
64
 * @copyright   2012-2016 Nyx Dev Team
65
 * @link        http://docs.muyo.io/nyx/utils/random.html
66
 * @todo        Random::password() utility.
67
 */
68
class Random
69
{
70
    /**
71
     * The traits of the Str class.
72
     */
73
    use traits\StaticallyExtendable;
74
75
    /**
76
     * Strength constants. The default for all methods is STRENGTH_MEDIUM. Consult the class description
77
     * for more information on when and how to use these constants.
78
     */
79
    const STRENGTH_NONE   = 1;
80
    const STRENGTH_WEAK   = 2;
81
    const STRENGTH_MEDIUM = 3;
82
    const STRENGTH_STRONG = 4;
83
84
    /**
85
     * @var array   A map of Source classes grouped together by their STRENGTH_*. Remains null until an edge-case
86
     *              is hit and this class needs to fall back to non-native entropy sources.
87
     *
88
     * @see Random::fallbackBytes()
89
     * @see Random::getSources()
90
     */
91
    protected static $sources;
92
93
    /**
94
     * Generates a sequence of pseudo-random bytes of the given $length.
95
     *
96
     * @param   int     $length             The length of the random string of bytes that should be generated.
97
     * @param   int     $strength           The requested strength of entropy (one of the STRENGTH_* class constants)
98
     * @return  string                      The resulting string in binary format.
99
     * @throws  \InvalidArgumentException   When a expected length smaller than 1 was given.
100
     */
101
    public static function bytes(int $length, int $strength = self::STRENGTH_MEDIUM) : string
102
    {
103
        if ($length < 1) {
104
            throw new \InvalidArgumentException('The expected number of random bytes must be at least 1.');
105
        }
106
107
        // For any strength above the lowest we are gonna rely on sources with proper entropy.
108
        // Note that on platforms with a full Suhosin patch mt_rand() isn't actually *that* weak.
109
        if ($strength > self::STRENGTH_WEAK) {
110
            try {
111
                return random_bytes($length);
112
            } catch (\Exception $exception) {
113
                // Try a fallback if native PHP failed us. The fallback will also throw an exception if it fails
114
                // to generate random bytes of sufficient entropy.
115
                return static::fallbackBytes($length, $strength);
116
            }
117
        }
118
119
        // At STRENGTH_WEAK or lower we will simply fall back to mt_rand().
120
        // Note that on platforms with a full Suhosin patch mt_rand() isn't actually *that* weak.
121
        $result = '';
122
123
        for ($i = 0; $i < $length; $i++) {
124
            $result .= chr((mt_rand() ^ mt_rand()) % 256);
125
        }
126
127
        return $result;
128
    }
129
130
    /**
131
     * Generates a pseudo-random integer in the specified range, {0 .. PHP_INT_MAX} by default.
132
     * The range is inclusive.
133
     *
134
     * The arguments can be passed in in any order. The resulting range must be <= PHP_INT_MAX and neither of the
135
     * arguments may exceed PHP_INT_MIN nor PHP_INT_MAX.
136
     *
137
     * @param   int     $min                The minimal expected value of the generated integer (>= than PHP_INT_MIN).
138
     * @param   int     $max                The maximal expected value of the generated integer (<= than PHP_INT_MAX).
139
     * @param   int     $strength           The requested strength of entropy (one of the STRENGTH_* class constants)
140
     * @return  int                         The generated integer.
141
     * @throws  \RangeException             When the specified range is invalid.
142
     */
143
    public static function int(int $min = 0, int $max = PHP_INT_MAX, int $strength = self::STRENGTH_MEDIUM) : int
144
    {
145
        // Allow for passing in the range in reverse order.
146
        $tmp   = max($min, $max);
147
        $min   = min($min, $max);
148
        $max   = $tmp;
149
        $range = $max - $min;
150
151
        if ($range === 0) {
152
            return $max;
153
        }
154
155
        // A range < 0 shouldn't happen at this point but may denote an arithmetic error.
156
        if ($range < 0 || $range > PHP_INT_MAX) {
157
            throw new \RangeException('The supplied range is too broad to generate a random integer from.');
158
        }
159
160
        // For any strength above the lowest we are gonna rely on sources with proper entropy.
161
        if ($strength > self::STRENGTH_WEAK) {
162
            try {
163
                return random_int($min, $max);
164
            } catch (\Exception $exception) {
165
                // Note: We're not checking for entropy sources here. self::fallbackInt() makes use of self::bytes()
166
                // so the exception will be thrown there if no pseudo-random bytes could be generated or in
167
                // self::fallbackInt() itself when the result is not a valid integer in the requested range.
168
                return static::fallbackInt($min, $max, $strength);
169
            }
170
        }
171
172
        // At STRENGTH_WEAK or lower we will simply fall back to mt_rand().
173
        // Note that on platforms with a full Suhosin patch mt_rand() isn't actually *that* weak.
174
        return mt_rand($min, $max);
175
    }
176
177
    /**
178
     * Generates a pseudo-random float in the specified range, {0 .. 1} by default. The range is inclusive.
179
     *
180
     * The arguments can be passed in in any order. The resulting range must be <= PHP_INT_MAX and neither of the
181
     * arguments may exceed PHP_INT_MIN nor PHP_INT_MAX.
182
     *
183
     * Note: You can pass in integers instead of floats for $min and $max, since PHP will perform a widening
184
     * conversion on them. as long as they are above and below PHP_INT_MIN and PHP_INT_MAX respectively.
185
     *
186
     * @param   float   $min                The minimal value of the generated float. Must be >= than PHP_INT_MIN.
187
     * @param   float   $max                The maximal value of the generated float. Must be <= than PHP_INT_MAX.
188
     * @param   int     $strength           The requested strength of entropy (one of the STRENGTH_* class constants)
189
     * @return  float                       The generated float.
190
     * @throws  \RangeException             When the specified range is invalid.
191
     */
192
    public static function float(float $min = 0.0, float $max = 1.0, int $strength = self::STRENGTH_MEDIUM) : float
193
    {
194
        // Allow for passing in the range in reverse order.
195
        $tmp   = max($min, $max);
196
        $min   = min($min, $max);
197
        $max   = $tmp;
198
        $range = $max - $min;
199
200
        if ($range === 0) {
201
            return $max;
202
        }
203
204
        // A range < 0 shouldn't happen at this point but may denote an arithmetic error.
205
        if ($range < 0 || $range > PHP_INT_MAX) {
206
            throw new \RangeException('The supplied range is too broad to generate a random floating point number from.');
207
        }
208
209
        return $min + static::int(0, PHP_INT_MAX, $strength) / PHP_INT_MAX * $range;
210
    }
211
212
    /**
213
     * Generates a pseudo-random boolean value.
214
     *
215
     * @return  bool    The resulting boolean.
216
     */
217
    public static function bool(int $strength = self::STRENGTH_MEDIUM) : bool
218
    {
219
        return (bool) (ord(static::bytes(1, $strength)) % 2);
220
    }
221
222
    /**
223
     * Generates a pseudo-random string of the specified length using random alpha-numeric (base64)
224
     * characters or the characters provided.
225
     *
226
     * Triggers an E_USER_NOTICE error if a $characters list containing only one character is given
227
     * while at the same time expecting a generated string with a $length > 1, since this results
228
     * in repeating that character $length number of times and is a dangerous op in a cryptographic
229
     * context.
230
     *
231
     * Note: Does *not* support multi-byte characters!
232
     *
233
     * Aliases:
234
     *  - @see Str::random()
235
     *
236
     * @param   int         $length          The expected length of the generated string.
237
     * @param   string|int  $charset         The character list to use. Can be either a string
238
     *                                       with the characters to use or an int | nyx\core\Mask
239
     *                                       to generate a list - @see \nyx\utils\str\Character::buildSet().
240
     *                                       If not provided or an invalid mask, the method will fall
241
     *                                       back to the Base64 character set.
242
     * @param   int         $strength        The requested strength of entropy (one of the STRENGTH_* class constants)
243
     * @return  string                       The generated string.
244
     * @throws  \InvalidArgumentException    When a expected length smaller than 1 was given.
245
     * @throws
246
     */
247
    public static function string(int $length = 8, $charset = str\Character::CHARS_BASE64, int $strength = self::STRENGTH_MEDIUM) : string
248
    {
249
        if ($length < 1) {
250
            throw new \InvalidArgumentException('The expected length of the generated string must be at least 1.');
251
        }
252
253
        if (is_int($charset) || $charset instanceof core\Mask) {
254
            if (empty($charset = str\Character::buildSet($charset))) {
255
                throw new \LogicException('The provided bitmask did not resolve into a valid character set.');
256
            }
257
        }
258
        // Fall back to the Base64 character set if none was provided.
259
        elseif (empty($charset)) {
260
            $charset = str\Character::buildSet(str\Character::CHARS_BASE64);
261
        }
262
        // Otherwise, if it's a truthy value but not a string - we riot.
263
        elseif (!is_string($charset)) {
264
            throw new \InvalidArgumentException('Expected $characters to be a string or bitmask, got ['.diagnostics\Debug::getTypeName($charset).'] instead.');
265
        }
266
267
        // If only a single character was given...
268
        if (1 === $charsetLength = strlen($charset)) {
269
270
            // ... and we only expected one to be generated, d'oh, we're gonna return it.
271
            if ($charsetLength === $length) {
272
                return $charset;
273
            }
274
275
            // Since this might be done in a cryptographic context, at least be sassy about it
276
            // and notify the user that we do not find this amusing.
277
            trigger_error("Attempted to generate a random string of [$length] characters but was given only 1 character to create it out of. This is potentially unsafe.");
278
279
            // We're gonna repeat it $length times in a *totally random* order, d'oh.
280
            return str_repeat($charsetLength, $length);
281
        }
282
283
        // With a STRENGTH_NONE (exclusively) setting we will simply shuffle the characters.
284
        // This is faster but not doesn't come close to random. Every higher setting will go through
285
        // the process of getting a random seed of the specified strength and actually generating
286
        // the string.
287
        if ($strength === self::STRENGTH_NONE) {
288
            return substr(str_shuffle(str_repeat($charset, ceil($length / 2))), 0, $length);
289
        }
290
291
        $result = '';
292
        $bytes  = static::bytes($length, $strength);
293
        $pos    = 0;
294
295
        // Generate one character at a time until we reach the expected length.
296
        // @todo Benchmark for faster/less predictable implementations.
297
        for ($idx = 0; $idx < $length; $idx++) {
298
            $pos     = ($pos + ord($bytes[$idx])) % $charsetLength;
299
            $result .= $charset[$pos];
300
        }
301
302
        return $result;
303
    }
304
305
    /**
306
     * Generates a sequence of pseudo-random bytes of the given $length from fallback sources if the
307
     * default implementation in self::bytes() failed for any reason. The sources used are returned
308
     * by self::getSources() and this mechanism respects the requested entropy strength (ie. stronger
309
     * sources may satisfy the request but weaker sources won't).
310
     *
311
     * Note: Does *not* check whether $length is valid - intended to be called internally by self::bytes()
312
     *       which does perform all relevant checks (and throws exceptions), so take this into account when overriding.
313
     *
314
     * @param   int     $length     The length of the random string of bytes that should be generated.
315
     * @param   int     $strength   The requested strength of entropy (one of the STRENGTH_* class constants)
316
     * @return  string              The resulting string in binary format.
317
     * @throws  \DomainException    When one of the sources returned by self::getSources() does not have a key pointing
318
     *                              to a class implementing random\interfaces\Source.
319
     * @throws  \RuntimeException   When pseudo-random bytes of the requested entropy could not be generated
320
     *                              (most likely due to lack of appropriate sources).
321
     */
322
    protected static function fallbackBytes(int $length, int $strength) : string
323
    {
324
        foreach (static::getSources() as $sourceStrength => &$sources) {
0 ignored issues
show
The expression static::getSources() cannot be used as a reference.

Let?s assume that you have the following foreach statement:

foreach ($array as &$itemValue) { }

$itemValue is assigned by reference. This is possible because the expression (in the example $array) can be used as a reference target.

However, if we were to replace $array with something different like the result of a function call as in

foreach (getArray() as &$itemValue) { }

then assigning by reference is not possible anymore as there is no target that could be modified.

Available Fixes

1. Do not assign by reference
foreach (getArray() as $itemValue) { }
2. Assign to a local variable first
$array = getArray();
foreach ($array as &$itemValue) {}
3. Return a reference
function &getArray() { $array = array(); return $array; }

foreach (getArray() as &$itemValue) { }
Loading history...
325
326
            // A lower strength requirement can be satisfied by higher strength sources - as such
327
            // the order of the sources matters.
328
            if ($sourceStrength < $strength) {
329
                continue;
330
            }
331
332
            foreach ($sources as $class => &$source) {
333
334
                // Sources with a value of false are marked as not available on this platform and will
335
                // be omitted.
336
                if (false === $source) {
337
                    continue;
338
                }
339
340
                // If no instance was made yet, we will try to create one.
341
                if (!isset($source)) {
342
343
                    if (!$class instanceof random\interfaces\Source) {
344
                        throw new \DomainException('Sources must have a key pointing to a class implementing '.random\interfaces\Source::class);
345
                    }
346
347
                    // Try to instantiate - Sources that aren't supported MUST throw during construction.
348
                    // On failure just skip this iteration entirely, but mark the failure to avoid retrying
349
                    // this source.
350
                    /* @var random\interfaces\Source $source */
351
                    try {
352
                        $source = new $class;
353
                    } catch(\RuntimeException $exception) {
354
                        $source = false;
355
                        continue;
356
                    }
357
                }
358
359
                // First valid result is good enough to return. Any failed attempts will be handled
360
                // by exceptions being thrown by Source::generate() itself and will prevent the return.
361
                try {
362
                    return $source->generate($length);
363
                } catch(\RuntimeException $exception) {
364
                    // Ignoring the Exception since we handled it by not returning any valid result.
365
                }
366
            }
367
        }
368
369
        // At this stage we didn't get a single valid result.
370
        throw new \RuntimeException('No source with sufficient entropy is available on this platform.');
371
    }
372
373
    /**
374
     * Generates a pseudo-random integer in the specified range from fallback sources if the default
375
     * implementation in self::int() failed for any reason.
376
     *
377
     * Note: Does *not* check whether the specified range is valid for integers - intended to be called
378
     *       internally by self::int() which does perform all relevant checks (and throws exceptions), so take
379
     *       this into account when overriding.
380
     *
381
     * @param   int     $min        The minimal expected value of the generated integer (>= than PHP_INT_MIN).
382
     * @param   int     $max        The maximal expected value of the generated integer (<= than PHP_INT_MAX).
383
     * @param   int     $strength   The requested strength of entropy (one of the STRENGTH_* class constants)
384
     * @return  int                 The generated integer.
385
     * @throws  \RuntimeException   When failing to generate a pseudo-random integer in the specified range.
386
     */
387
    protected static function fallbackInt(int $min, int $max, int $strength) : int
388
    {
389
        // Note: No type/value validity checks performed here. We're assuming to be called from self::int()
390
        // only and don't want to duplicate the checks contained therein.
391
        $range = $max - $min;
392
393
        // We need to count the bits required to represent the range.
394
        $bits = 0;
395
        while ($range >>= 1) {
396
            $bits++;
397
        }
398
399
        // We'll be offsetting the resulting integer to squeeze it into the range
400
        // and we need some data for that.
401
        $bits   = max($bits, 1);
402
        $bytes  = max(ceil($bits / 8), 1);
403
        $mask   = (1 << $bits) - 1;
404
405
        do {
406
            $result = hexdec(bin2hex(static::bytes($bytes, $strength))) & $mask;
407
        } while ($result > $range);
408
409
        $result = $min + $result;
410
411
        // Assert we got a integer in the requested range.
412
        if (!is_int($result) || $result < $min || $result > $max) {
413
            throw new \RuntimeException('Failed to generate a random integer in the ['.$min.' - '.$max.'] range. Possibly due to lack of sources with sufficient entropy.');
414
        }
415
416
        return $result;
417
    }
418
419
    /**
420
     * Returns a list of sources to be used by self::fallbackBytes(), ordered in ascending order
421
     * by their strength (also for performance reasons) and their priority of evaluation.
422
     *
423
     * @see     Random::fallbackBytes()
424
     * @return  array
425
     */
426
    protected static function getSources() : array
427
    {
428
        return static::$sources ?? (static::$sources = [
429
            self::STRENGTH_STRONG => [
430
                random\sources\OpenSSL::class => null
431
            ]
432
        ]);
433
    }
434
}
435