CVE-2021-3689: Use of Insufficiently Random Values (CWE-330)
value: yii2 is vulnerable to Use of Predictable Algorithm in Random Number Generator
CWE description: The product uses insufficiently random numbers or values in a security context that depends on unpredictable numbers.
CVSS v3 base score: 7.5
Published: 2021-08-10T11:15Z
Repository: https://github.com/yiisoft/yii2
Language: PHP
Files changed: composer.json, composer.lock, framework/CHANGELOG.md, framework/base/Security.php, framework/caching/DbCache.php, framework/caching/FileCache.php, framework/captcha/CaptchaAction.php, framework/composer.json, framework/mail/BaseMailer.php, tests/framework/base/SecurityTest.php, tests/framework/validators/FileValidatorTest.php, tests/framework/web/UploadedFileTest.php
Commit date: 2021-08-09 10:25:36 +0300
Commit message: Fix #18817: Use `paragonie/random_compat` for random bytes and int generation


Diff: diff --git a/composer.json b/composer.json
index d3065cb66f..1a0eb701fc 100644
--- a/composer.json
+++ b/composer.json
@@ -76,11 +76,12 @@
         "ezyang/htmlpurifier": "~4.6",
         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0",
         "bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
         "bower-asset/inputmask": "~3.2.2 | ~3.3.5",
         "bower-asset/punycode": "1.3.*",
-        "bower-asset/yii2-pjax": "~2.0.1"
+        "bower-asset/yii2-pjax": "~2.0.1",
+        "paragonie/random_compat": ">=1"
     },
     "require-dev": {
         "cweagans/composer-patches": "^1.7",
         "phpunit/phpunit": "4.8.34",
         "cebe/indent": "~1.0.2",
diff --git a/composer.lock b/composer.lock
index f0d91e3aeb..35d4dd11f0 100644
--- a/composer.lock
+++ b/composer.lock
@@ -2,11 +2,11 @@
     "_readme": [
         "This file locks the dependencies of your project to a known state",
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "7e41c6fc0175fd049e0e52c4e8b25e5c",
+    "content-hash": "8f9a4d7e645592f806605d32d676f54e",
     "packages": [
         {
             "name": "bower-asset/inputmask",
             "version": "3.3.11",
             "source": {
@@ -197,10 +197,64 @@
                 "issues": "https://github.com/ezyang/htmlpurifier/issues",
                 "source": "https://github.com/ezyang/htmlpurifier/tree/master"
             },
             "time": "2020-06-29T00:56:53+00:00"
         },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v2.0.20",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
+                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "lib/random.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "polyfill",
+                "pseudorandom",
+                "random"
+            ],
+            "support": {
+                "email": "info@paragonie.com",
+                "issues": "https://github.com/paragonie/random_compat/issues",
+                "source": "https://github.com/paragonie/random_compat"
+            },
+            "time": "2021-04-17T09:33:01+00:00"
+        },
         {
             "name": "yiisoft/yii2-composer",
             "version": "2.0.10",
             "source": {
                 "type": "git",
@@ -889,64 +943,10 @@
                 "issues": "https://github.com/johnkary/phpunit-speedtrap/issues",
                 "source": "https://github.com/johnkary/phpunit-speedtrap/tree/master"
             },
             "time": "2015-09-13T19:01:00+00:00"
         },
-        {
-            "name": "paragonie/random_compat",
-            "version": "v2.0.20",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/paragonie/random_compat.git",
-                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
-                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=5.2.0"
-            },
-            "require-dev": {
-                "phpunit/phpunit": "4.*|5.*"
-            },
-            "suggest": {
-                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "lib/random.php"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Paragon Initiative Enterprises",
-                    "email": "security@paragonie.com",
-                    "homepage": "https://paragonie.com"
-                }
-            ],
-            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
-            "keywords": [
-                "csprng",
-                "polyfill",
-                "pseudorandom",
-                "random"
-            ],
-            "support": {
-                "email": "info@paragonie.com",
-                "issues": "https://github.com/paragonie/random_compat/issues",
-                "source": "https://github.com/paragonie/random_compat"
-            },
-            "time": "2021-04-17T09:33:01+00:00"
-        },
         {
             "name": "phpdocumentor/reflection-docblock",
             "version": "2.0.5",
             "source": {
                 "type": "git",
@@ -2917,7 +2917,7 @@
     },
     "platform-dev": [],
     "platform-overrides": {
         "php": "5.4"
     },
-    "plugin-api-version": "2.0.0"
+    "plugin-api-version": "2.1.0"
 }
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index a1b8cadc88..f3df9a4b2c 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -2,10 +2,11 @@ Yii Framework 2 Change Log
 ==========================
 
 2.0.43 under development
 ------------------------
 
+- Enh #18817: Use `paragonie/random_compat` for random bytes and int generation (samdark)
 - Bug #14663: Do not convert int to string if database type of a column is numeric (egorrishe)
 - Bug #18650: Refactor `framework/assets/yii.activeForm.js` arrow function into traditional function for IE11 compatibility (marcovtwout)
 - Bug #18749: Fix `yii\web\ErrorHandler::encodeHtml()` to support strings with invalid UTF symbols (vjik)
 - Enh #18724: Allow jQuery 3.6 to be installed (marcovtwout)
 - Enh #18628: Added strings "software", and "hardware" to `$specials` array in `yii\helpers\BaseInflector` (kjusupov)
diff --git a/framework/base/Security.php b/framework/base/Security.php
index e3a2373ab8..49972d17bd 100644
--- a/framework/base/Security.php
+++ b/framework/base/Security.php
@@ -114,18 +114,10 @@ class Security extends Component
         }
 
         return $this->_useLibreSSL;
     }
 
-    /**
-     * @return bool if operating system is Windows
-     */
-    private function isWindows()
-    {
-        return DIRECTORY_SEPARATOR !== '/';
-    }
-
     /**
      * Encrypts data using a password.
      * Derives keys for encryption and authentication from the password using PBKDF2 and a random salt,
      * which is deliberately slow to protect against dictionary attacks. Use [[encryptByKey()]] to
      * encrypt fast using a cryptographic key rather than a password. Key derivation time is
@@ -469,12 +461,10 @@ class Security extends Component
         }
 
         return false;
     }
 
-    private $_randomFile;
-
     /**
      * Generates specified number of random bytes.
      * Note that output may not be ASCII.
      * @see generateRandomString() if you need a string.
      *
@@ -491,88 +481,11 @@ class Security extends Component
 
         if ($length < 1) {
             throw new InvalidArgumentException('First parameter ($length) must be greater than 0');
         }
 
-        // always use random_bytes() if it is available
-        if (function_exists('random_bytes')) {
-            return random_bytes($length);
-        }
-
-        // The recent LibreSSL RNGs are faster and likely better than /dev/urandom.
-        // Since 5.4.0, openssl_random_pseudo_bytes() reads from CryptGenRandom on Windows instead
-        // of using OpenSSL library. LibreSSL is OK everywhere but don't use OpenSSL on non-Windows.
-        if (function_exists('openssl_random_pseudo_bytes')
-            && ($this->shouldUseLibreSSL() || $this->isWindows())
-        ) {
-            $key = openssl_random_pseudo_bytes($length, $cryptoStrong);
-            if ($cryptoStrong === false) {
-                throw new Exception(
-                    'openssl_random_pseudo_bytes() set $crypto_strong false. Your PHP setup is insecure.'
-                );
-            }
-            if ($key !== false && StringHelper::byteLength($key) === $length) {
-                return $key;
-            }
-        }
-
-        // mcrypt_create_iv() does not use libmcrypt. Since PHP 5.3.7 it directly reads
-        // CryptGenRandom on Windows. Elsewhere it directly reads /dev/urandom.
-        if (function_exists('mcrypt_create_iv')) {
-            $key = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
-            if (StringHelper::byteLength($key) === $length) {
-                return $key;
-            }
-        }
-
-        // If not on Windows, try to open a random device.
-        if ($this->_randomFile === null && !$this->isWindows()) {
-            // urandom is a symlink to random on FreeBSD.
-            $device = PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom';
-            // Check random device for special character device protection mode. Use lstat()
-            // instead of stat() in case an attacker arranges a symlink to a fake device.
-            $lstat = @lstat($device);
-            if ($lstat !== false && ($lstat['mode'] & 0170000) === 020000) {
-                $this->_randomFile = fopen($device, 'rb') ?: null;
-
-                if (is_resource($this->_randomFile)) {
-                    // Reduce PHP stream buffer from default 8192 bytes to optimize data
-                    // transfer from the random device for smaller values of $length.
-                    // This also helps to keep future randoms out of user memory space.
-                    $bufferSize = 8;
-
-                    if (function_exists('stream_set_read_buffer')) {
-                        stream_set_read_buffer($this->_randomFile, $bufferSize);
-                    }
-                    // stream_set_read_buffer() isn't implemented on HHVM
-                    if (function_exists('stream_set_chunk_size')) {
-                        stream_set_chunk_size($this->_randomFile, $bufferSize);
-                    }
-                }
-            }
-        }
-
-        if (is_resource($this->_randomFile)) {
-            $buffer = '';
-            $stillNeed = $length;
-            while ($stillNeed > 0) {
-                $someBytes = fread($this->_randomFile, $stillNeed);
-                if ($someBytes === false) {
-                    break;
-                }
-                $buffer .= $someBytes;
-                $stillNeed -= StringHelper::byteLength($someBytes);
-                if ($stillNeed === 0) {
-                    // Leaving file pointer open in order to make next generation faster by reusing it.
-                    return $buffer;
-                }
-            }
-            fclose($this->_randomFile);
-            $this->_randomFile = null;
-        }
-
-        throw new Exception('Unable to generate a random key');
+        return random_bytes($length);
     }
 
     /**
      * Generates a random string of specified length.
      * The string generated matches [A-Za-z0-9_-]+ and is transparent to URL-encoding.
diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php
index dc7a1316f0..3cd211ec01 100644
--- a/framework/caching/DbCache.php
+++ b/framework/caching/DbCache.php
@@ -274,11 +274,12 @@ class DbCache extends Cache
      * @param bool $force whether to enforce the garbage collection regardless of [[gcProbability]].
      * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
      */
     public function gc($force = false)
     {
-        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+
+        if ($force || random_int(0, 1000000) < $this->gcProbability) {
             $this->db->createCommand()
                 ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
                 ->execute();
         }
     }
diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php
index b221998db8..378a84fb73 100644
--- a/framework/caching/FileCache.php
+++ b/framework/caching/FileCache.php
@@ -243,11 +243,11 @@ class FileCache extends Cache
      * @param bool $expiredOnly whether to removed expired cache files only.
      * If false, all cache files under [[cachePath]] will be removed.
      */
     public function gc($force = false, $expiredOnly = true)
     {
-        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+        if ($force || random_int(0, 1000000) < $this->gcProbability) {
             $this->gcRecursive($this->cachePath, $expiredOnly);
         }
     }
 
     /**
diff --git a/framework/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php
index 40e1aa03a0..e61a9ed34a 100644
--- a/framework/captcha/CaptchaAction.php
+++ b/framework/captcha/CaptchaAction.php
@@ -212,20 +212,21 @@ class CaptchaAction extends Action
             $this->minLength = 3;
         }
         if ($this->maxLength > 20) {
             $this->maxLength = 20;
         }
-        $length = mt_rand($this->minLength, $this->maxLength);
+
+        $length = random_int($this->minLength, $this->maxLength);
 
         $letters = 'bcdfghjklmnpqrstvwxyz';
         $vowels = 'aeiou';
         $code = '';
         for ($i = 0; $i < $length; ++$i) {
-            if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) {
-                $code .= $vowels[mt_rand(0, 4)];
+            if ($i % 2 && random_int(0, 10) > 2 || !($i % 2) && random_int(0, 10) > 9) {
+                $code .= $vowels[random_int(0, 4)];
             } else {
-                $code .= $letters[mt_rand(0, 20)];
+                $code .= $letters[random_int(0, 20)];
             }
         }
 
         return $code;
     }
@@ -296,12 +297,12 @@ class CaptchaAction extends Action
         $h = $box[1] - $box[5];
         $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
         $x = 10;
         $y = round($this->height * 27 / 40);
         for ($i = 0; $i < $length; ++$i) {
-            $fontSize = (int) (mt_rand(26, 32) * $scale * 0.8);
-            $angle = mt_rand(-10, 10);
+            $fontSize = (int) (random_int(26, 32) * $scale * 0.8);
+            $angle = random_int(-10, 10);
             $letter = $code[$i];
             $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter);
             $x = $box[2] + $this->offset;
         }
 
@@ -339,13 +340,13 @@ class CaptchaAction extends Action
         $x = 10;
         $y = round($this->height * 27 / 40);
         for ($i = 0; $i < $length; ++$i) {
             $draw = new \ImagickDraw();
             $draw->setFont($this->fontFile);
-            $draw->setFontSize((int) (mt_rand(26, 32) * $scale * 0.8));
+            $draw->setFontSize((int) (random_int(26, 32) * $scale * 0.8));
             $draw->setFillColor($foreColor);
-            $image->annotateImage($draw, $x, $y, mt_rand(-10, 10), $code[$i]);
+            $image->annotateImage($draw, $x, $y, random_int(-10, 10), $code[$i]);
             $fontMetrics = $image->queryFontMetrics($draw, $code[$i]);
             $x += (int) $fontMetrics['textWidth'] + $this->offset;
         }
 
         $image->setImageFormat('png');
diff --git a/framework/composer.json b/framework/composer.json
index 8d6105d262..b211cb5eea 100644
--- a/framework/composer.json
+++ b/framework/composer.json
@@ -71,11 +71,12 @@
         "ezyang/htmlpurifier": "~4.6",
         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0",
         "bower-asset/jquery": "3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
         "bower-asset/inputmask": "~3.2.2 | ~3.3.5",
         "bower-asset/punycode": "1.3.*",
-        "bower-asset/yii2-pjax": "~2.0.1"
+        "bower-asset/yii2-pjax": "~2.0.1",
+        "paragonie/random_compat": ">=1"
     },
     "autoload": {
         "psr-4": {"yii\\": ""}
     },
     "bin": [
diff --git a/framework/mail/BaseMailer.php b/framework/mail/BaseMailer.php
index c4546fbd1f..63b6904ee5 100644
--- a/framework/mail/BaseMailer.php
+++ b/framework/mail/BaseMailer.php
@@ -341,11 +341,11 @@ abstract class BaseMailer extends Component implements MailerInterface, ViewCont
      */
     public function generateMessageFileName()
     {
         $time = microtime(true);
 
-        return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', mt_rand(0, 10000)) . '.eml';
+        return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', random_int(0, 10000)) . '.eml';
     }
 
     /**
      * @return string the directory that contains the view files for composing mail messages
      * Defaults to '@app/mail'.
diff --git a/tests/framework/base/SecurityTest.php b/tests/framework/base/SecurityTest.php
index e84714000e..215999c12a 100644
--- a/tests/framework/base/SecurityTest.php
+++ b/tests/framework/base/SecurityTest.php
@@ -3,57 +3,11 @@
  * @link http://www.yiiframework.com/
  * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\base {
-
-    /**
-     * emulate availability of functions, to test different branches of Security class
-     * where different execution paths are chosen based on calling function_exists.
-     *
-     * This function overrides function_exists from the root namespace in yii\base.
-     * @param string $name
-     */
-    function function_exists($name)
-    {
-        if (isset(\yiiunit\framework\base\SecurityTest::$functions[$name])) {
-            return \yiiunit\framework\base\SecurityTest::$functions[$name];
-        }
-
-        return \function_exists($name);
-    }
-    /**
-     * Emulate chunked reading of fread(), to test different branches of Security class
-     * where different execution paths are chosen based on the return value of fopen/fread.
-     *
-     * This function overrides fopen and fread from the root namespace in yii\base.
-     * @param string $filename
-     * @param mixed $mode
-     */
-    function fopen($filename, $mode)
-    {
-        if (\yiiunit\framework\base\SecurityTest::$fopen !== null) {
-            return \yiiunit\framework\base\SecurityTest::$fopen;
-        }
-
-        return \fopen($filename, $mode);
-    }
-    function fread($handle, $length)
-    {
-        if (\yiiunit\framework\base\SecurityTest::$fread !== null) {
-            return \yiiunit\framework\base\SecurityTest::$fread;
-        }
-        if (\yiiunit\framework\base\SecurityTest::$fopen !== null) {
-            return $length < 8 ? \str_repeat('s', $length) : 'test1234';
-        }
-
-        return \fread($handle, $length);
-    }
-} // closing namespace yii\base;
-
-namespace yiiunit\framework\base {
+namespace yiiunit\framework\base;
 
 use yii\base\Security;
 use yiiunit\TestCase;
 
 /**
@@ -61,48 +15,22 @@ use yiiunit\TestCase;
  */
 class SecurityTest extends TestCase
 {
     const CRYPT_VECTORS = 'old';
 
-    /**
-     * @var array set of functions for which a fake return value for `function_exists()` is provided.
-     */
-    public static $functions = [];
-    /**
-     * @var resource|false|null fake return value for fopen() in \yii\base namespace. Normal behavior if this is null.
-     */
-    public static $fopen;
-    public static $fread;
-
     /**
      * @var ExposedSecurity
      */
     protected $security;
 
     protected function setUp()
     {
-        static::$functions = [];
-        static::$fopen = null;
-        static::$fread = null;
         parent::setUp();
         $this->security = new ExposedSecurity();
         $this->security->derivationIterations = 1000; // speed up test running
     }
 
-    protected function tearDown()
-    {
-        static::$functions = [];
-        static::$fopen = null;
-        static::$fread = null;
-        parent::tearDown();
-    }
-
-    private function isWindows()
-    {
-        return DIRECTORY_SEPARATOR !== '/';
-    }
-
     // Tests :
 
     public function testHashData()
     {
         $data = 'known data';
@@ -891,80 +819,12 @@ TEXT;
     public function testRandomKeyInvalidInput($input)
     {
         $key1 = $this->security->generateRandomKey($input);
     }
 
-    /**
-     * Test the case where opening /dev/urandom fails.
-     */
-    public function testRandomKeyNoOptions()
-    {
-        static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false];
-        static::$fopen = false;
-        $this->expectException('yii\base\Exception');
-        $this->expectExceptionMessage('Unable to generate a random key');
-
-        $this->security->generateRandomKey(42);
-    }
-
-    /**
-     * Test the case where reading from /dev/urandom fails.
-     */
-    public function testRandomKeyFreadFailure()
-    {
-        static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false];
-        static::$fread = false;
-        $this->expectException('yii\base\Exception');
-        $this->expectExceptionMessage('Unable to generate a random key');
-
-        $this->security->generateRandomKey(42);
-    }
-
-    /**
-     * returns a set of different combinations of functions available.
-     */
-    public function randomKeyVariants()
+    public function testGenerateRandomKey()
     {
-        return [
-            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => true]],
-            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => false]],
-            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true]],
-            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false]],
-            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => true]],
-            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => false]],
-            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true]],
-            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false]],
-        ];
-    }
-
-    /**
-     * @dataProvider randomKeyVariants
-     * @param array $functions
-     */
-    public function testGenerateRandomKey($functions)
-    {
-        foreach ($functions as $fun => $available) {
-            if ($available && !\function_exists($fun)) {
-                $this->markTestSkipped("Can not test generateRandomKey() branch that includes $fun, because it is not available on your system.");
-            }
-        }
-        // there is no /dev/urandom on windows so we expect this to fail
-        if ($this->isWindows() && $functions['random_bytes'] === false && $functions['openssl_random_pseudo_bytes'] === false && $functions['mcrypt_create_iv'] === false) {
-            $this->expectException('yii\base\Exception');
-            $this->expectExceptionMessage('Unable to generate a random key');
-        }
-        // Function mcrypt_create_iv() is deprecated since PHP 7.1
-        if (version_compare(PHP_VERSION, '7.1.0alpha', '>=') && $functions['random_bytes'] === false && $functions['mcrypt_create_iv'] === true) {
-            if ($functions['openssl_random_pseudo_bytes'] === false) {
-                $this->markTestSkipped('Function mcrypt_create_iv() is deprecated as of PHP 7.1');
-            } elseif (!$this->security->shouldUseLibreSSL() && !$this->isWindows()) {
-                $this->markTestSkipped('Function openssl_random_pseudo_bytes need LibreSSL version >=2.1.5 or Windows system on server');
-            }
-        }
-
-        static::$functions = $functions;
-
         // test various string lengths
         for ($length = 1; $length < 64; $length++) {
             $key1 = $this->security->generateRandomKey($length);
             $this->assertInternalType('string', $key1);
             $this->assertEquals($length, strlen($key1));
@@ -983,20 +843,10 @@ TEXT;
         $this->assertEquals($length, strlen($key1));
         $key2 = $this->security->generateRandomKey($length);
         $this->assertInternalType('string', $key2);
         $this->assertEquals($length, strlen($key2));
         $this->assertNotEquals($key1, $key2);
-
-        // force /dev/urandom reading loop to deal with chunked data
-        // the above test may have read everything in one run.
-        // not sure if this can happen in real life but if it does
-        // we should be prepared
-        static::$fopen = fopen('php://memory', 'rwb');
-        $length = 1024 * 1024;
-        $key1 = $this->security->generateRandomKey($length);
-        $this->assertInternalType('string', $key1);
-        $this->assertEquals($length, strlen($key1));
     }
 
     protected function randTime(Security $security, $count, $length, $message)
     {
         $t = microtime(true);
@@ -1008,46 +858,10 @@ TEXT;
         $milisec = number_format(1000 * ($t), 3);
         $rate = number_format($count * $length / $t / 1000000, 3);
         fwrite(STDERR, "$message: $count x $length B = $nbytes B in $milisec ms => $rate MB/s\n");
     }
 
-    public function testGenerateRandomKeySpeed()
-    {
-        self::markTestSkipped('Comment markTestSkipped in testGenerateRandomKeySpeed() in order to get RNG benchmark.');
-        $tests = [
-            "function_exists('random_bytes')",
-            "defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : null",
-            'PHP_VERSION_ID',
-            'PHP_OS',
-            "function_exists('mcrypt_create_iv') ? bin2hex(mcrypt_create_iv(4, MCRYPT_DEV_URANDOM)) : null",
-            'DIRECTORY_SEPARATOR',
-            "ini_get('open_basedir')",
-        ];
-        if ($this->isWindows()) {
-            $tests[] = "sprintf('%o', lstat(PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom')['mode'] & 0170000)";
-            $tests[] = "bin2hex(file_get_contents(PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom', false, null, 0, 8))";
-        }
-        foreach ($tests as $i => $test) {
-            $result = eval('return ' . $test . ';');
-            fwrite(STDERR, sprintf("%2d %s ==> %s\n", $i + 1, $test, var_export($result, true)));
-        }
-
-        foreach ([16, 2000, 262144] as $block) {
-            $security = new Security();
-            foreach (range(1, 10) as $nth) {
-                $this->randTime($security, 1, $block, "Call $nth");
-            }
-            unset($security);
-        }
-
-        $security = new Security();
-        $this->randTime($security, 10000, 16, 'Rate test');
-
-        $security = new Security();
-        $this->randTime($security, 10000, 5000, 'Rate test');
-    }
-
     public function testGenerateRandomString()
     {
         $length = 21;
         $key = $this->security->generateRandomString($length);
         $this->assertEquals($length, strlen($key));
@@ -1303,6 +1117,5 @@ TEXT;
             ['Token with special characters: %d1    5"'],
             ['Token with UTF8 character: †'],
         ];
     }
 }
-} // closing namespace yiiunit\framework\base;
diff --git a/tests/framework/validators/FileValidatorTest.php b/tests/framework/validators/FileValidatorTest.php
index cbed1fbabf..2ce3c797ca 100644
--- a/tests/framework/validators/FileValidatorTest.php
+++ b/tests/framework/validators/FileValidatorTest.php
@@ -322,11 +322,11 @@ class FileValidatorTest extends TestCase
     {
         $rndString = function ($len = 10) {
             $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
             $randomString = '';
             for ($i = 0; $i < $len; $i++) {
-                $randomString .= $characters[rand(0, strlen($characters) - 1)];
+                $randomString .= $characters[random_int(0, strlen($characters) - 1)];
             }
 
             return $randomString;
         };
         $files = [];
@@ -338,11 +338,11 @@ class FileValidatorTest extends TestCase
             $name = isset($param['name']) ? $param['name'] : $rndString();
             $tempName = \Yii::getAlias('@yiiunit/runtime/validators/file/tmp/') . $name;
             if (is_readable($tempName)) {
                 $size = filesize($tempName);
             } else {
-                $size = isset($param['size']) ? $param['size'] : rand(
+                $size = isset($param['size']) ? $param['size'] : random_int(
                     1,
                     $this->sizeToBytes(ini_get('upload_max_filesize'))
                 );
             }
             $type = isset($param['type']) ? $param['type'] : 'text/plain';
diff --git a/tests/framework/web/UploadedFileTest.php b/tests/framework/web/UploadedFileTest.php
index 4702c5adb6..d374a73333 100644
--- a/tests/framework/web/UploadedFileTest.php
+++ b/tests/framework/web/UploadedFileTest.php
@@ -5,10 +5,11 @@
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yiiunit\framework\web;
 
+use Yii;
 use yii\web\UploadedFile;
 use yiiunit\framework\web\mocks\UploadedFileMock;
 use yiiunit\framework\web\stubs\ModelStub;
 use yiiunit\framework\web\stubs\VendorImage;
 use yiiunit\TestCase;
@@ -26,25 +27,25 @@ class UploadedFileTest extends TestCase
     }
 
     private function generateFakeFileData()
     {
         return [
-            'name' => md5(mt_rand()),
-            'tmp_name' => md5(mt_rand()),
+            'name' => md5(random_int(0, PHP_INT_MAX)),
+            'tmp_name' => md5(random_int(0, PHP_INT_MAX)),
             'type' => 'image/jpeg',
-            'size' => mt_rand(1000, 10000),
+            'size' => random_int(1000, 10000),
             'error' => '0',
         ];
     }
 
     private function generateTempFileData()
     {
         return [
-            'name' => md5(mt_rand()),
+            'name' => md5(random_int(0, PHP_INT_MAX)),
             'tmp_name' => tempnam(sys_get_temp_dir(), ''),
             'type' => 'image/jpeg',
-            'size' => mt_rand(1000, 10000),
+            'size' => random_int(1000, 10000),
             'error' => '0',
         ];
     }
 
     private function generateFakeFiles()

Vulnerable code:         "bower-asset/yii2-pjax": "~2.0.1"
    "content-hash": "7e41c6fc0175fd049e0e52c4e8b25e5c",
        {
            "name": "paragonie/random_compat",
            "version": "v2.0.20",
            "source": {
                "type": "git",
                "url": "https://github.com/paragonie/random_compat.git",
                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.0"
            },
            "require-dev": {
                "phpunit/phpunit": "4.*|5.*"
            },
            "suggest": {
                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
            },
            "type": "library",
            "autoload": {
                "files": [
                    "lib/random.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Paragon Initiative Enterprises",
                    "email": "security@paragonie.com",
                    "homepage": "https://paragonie.com"
                }
            ],
            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
            "keywords": [
                "csprng",
                "polyfill",
                "pseudorandom",
                "random"
            ],
            "support": {
                "email": "info@paragonie.com",
                "issues": "https://github.com/paragonie/random_compat/issues",
                "source": "https://github.com/paragonie/random_compat"
            },
            "time": "2021-04-17T09:33:01+00:00"
        },
    "plugin-api-version": "2.0.0"
    /**
     * @return bool if operating system is Windows
     */
    private function isWindows()
    {
        return DIRECTORY_SEPARATOR !== '/';
    }

    private $_randomFile;

        // always use random_bytes() if it is available
        if (function_exists('random_bytes')) {
            return random_bytes($length);
        }

        // The recent LibreSSL RNGs are faster and likely better than /dev/urandom.
        // Since 5.4.0, openssl_random_pseudo_bytes() reads from CryptGenRandom on Windows instead
        // of using OpenSSL library. LibreSSL is OK everywhere but don't use OpenSSL on non-Windows.
        if (function_exists('openssl_random_pseudo_bytes')
            && ($this->shouldUseLibreSSL() || $this->isWindows())
        ) {
            $key = openssl_random_pseudo_bytes($length, $cryptoStrong);
            if ($cryptoStrong === false) {
                throw new Exception(
                    'openssl_random_pseudo_bytes() set $crypto_strong false. Your PHP setup is insecure.'
                );
            }
            if ($key !== false && StringHelper::byteLength($key) === $length) {
                return $key;
            }
        }

        // mcrypt_create_iv() does not use libmcrypt. Since PHP 5.3.7 it directly reads
        // CryptGenRandom on Windows. Elsewhere it directly reads /dev/urandom.
        if (function_exists('mcrypt_create_iv')) {
            $key = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
            if (StringHelper::byteLength($key) === $length) {
                return $key;
            }
        }

        // If not on Windows, try to open a random device.
        if ($this->_randomFile === null && !$this->isWindows()) {
            // urandom is a symlink to random on FreeBSD.
            $device = PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom';
            // Check random device for special character device protection mode. Use lstat()
            // instead of stat() in case an attacker arranges a symlink to a fake device.
            $lstat = @lstat($device);
            if ($lstat !== false && ($lstat['mode'] & 0170000) === 020000) {
                $this->_randomFile = fopen($device, 'rb') ?: null;

                if (is_resource($this->_randomFile)) {
                    // Reduce PHP stream buffer from default 8192 bytes to optimize data
                    // transfer from the random device for smaller values of $length.
                    // This also helps to keep future randoms out of user memory space.
                    $bufferSize = 8;

                    if (function_exists('stream_set_read_buffer')) {
                        stream_set_read_buffer($this->_randomFile, $bufferSize);
                    }
                    // stream_set_read_buffer() isn't implemented on HHVM
                    if (function_exists('stream_set_chunk_size')) {
                        stream_set_chunk_size($this->_randomFile, $bufferSize);
                    }
                }
            }
        }

        if (is_resource($this->_randomFile)) {
            $buffer = '';
            $stillNeed = $length;
            while ($stillNeed > 0) {
                $someBytes = fread($this->_randomFile, $stillNeed);
                if ($someBytes === false) {
                    break;
                }
                $buffer .= $someBytes;
                $stillNeed -= StringHelper::byteLength($someBytes);
                if ($stillNeed === 0) {
                    // Leaving file pointer open in order to make next generation faster by reusing it.
                    return $buffer;
                }
            }
            fclose($this->_randomFile);
            $this->_randomFile = null;
        }

        throw new Exception('Unable to generate a random key');
        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
        if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
        $length = mt_rand($this->minLength, $this->maxLength);
            if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) {
                $code .= $vowels[mt_rand(0, 4)];
                $code .= $letters[mt_rand(0, 20)];
            $fontSize = (int) (mt_rand(26, 32) * $scale * 0.8);
            $angle = mt_rand(-10, 10);
            $draw->setFontSize((int) (mt_rand(26, 32) * $scale * 0.8));
            $image->annotateImage($draw, $x, $y, mt_rand(-10, 10), $code[$i]);
        "bower-asset/yii2-pjax": "~2.0.1"
        return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', mt_rand(0, 10000)) . '.eml';
namespace yii\base {

    /**
     * emulate availability of functions, to test different branches of Security class
     * where different execution paths are chosen based on calling function_exists.
     *
     * This function overrides function_exists from the root namespace in yii\base.
     * @param string $name
     */
    function function_exists($name)
    {
        if (isset(\yiiunit\framework\base\SecurityTest::$functions[$name])) {
            return \yiiunit\framework\base\SecurityTest::$functions[$name];
        }

        return \function_exists($name);
    }
    /**
     * Emulate chunked reading of fread(), to test different branches of Security class
     * where different execution paths are chosen based on the return value of fopen/fread.
     *
     * This function overrides fopen and fread from the root namespace in yii\base.
     * @param string $filename
     * @param mixed $mode
     */
    function fopen($filename, $mode)
    {
        if (\yiiunit\framework\base\SecurityTest::$fopen !== null) {
            return \yiiunit\framework\base\SecurityTest::$fopen;
        }

        return \fopen($filename, $mode);
    }
    function fread($handle, $length)
    {
        if (\yiiunit\framework\base\SecurityTest::$fread !== null) {
            return \yiiunit\framework\base\SecurityTest::$fread;
        }
        if (\yiiunit\framework\base\SecurityTest::$fopen !== null) {
            return $length < 8 ? \str_repeat('s', $length) : 'test1234';
        }

        return \fread($handle, $length);
    }
} // closing namespace yii\base;

namespace yiiunit\framework\base {
    /**
     * @var array set of functions for which a fake return value for `function_exists()` is provided.
     */
    public static $functions = [];
    /**
     * @var resource|false|null fake return value for fopen() in \yii\base namespace. Normal behavior if this is null.
     */
    public static $fopen;
    public static $fread;

        static::$functions = [];
        static::$fopen = null;
        static::$fread = null;
    protected function tearDown()
    {
        static::$functions = [];
        static::$fopen = null;
        static::$fread = null;
        parent::tearDown();
    }

    private function isWindows()
    {
        return DIRECTORY_SEPARATOR !== '/';
    }

    /**
     * Test the case where opening /dev/urandom fails.
     */
    public function testRandomKeyNoOptions()
    {
        static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false];
        static::$fopen = false;
        $this->expectException('yii\base\Exception');
        $this->expectExceptionMessage('Unable to generate a random key');

        $this->security->generateRandomKey(42);
    }

    /**
     * Test the case where reading from /dev/urandom fails.
     */
    public function testRandomKeyFreadFailure()
    {
        static::$functions = ['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false];
        static::$fread = false;
        $this->expectException('yii\base\Exception');
        $this->expectExceptionMessage('Unable to generate a random key');

        $this->security->generateRandomKey(42);
    }

    /**
     * returns a set of different combinations of functions available.
     */
    public function randomKeyVariants()
        return [
            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => true]],
            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => false]],
            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true]],
            [['random_bytes' => true,  'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false]],
            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => true]],
            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => true,  'mcrypt_create_iv' => false]],
            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => true]],
            [['random_bytes' => false, 'openssl_random_pseudo_bytes' => false, 'mcrypt_create_iv' => false]],
        ];
    }

    /**
     * @dataProvider randomKeyVariants
     * @param array $functions
     */
    public function testGenerateRandomKey($functions)
    {
        foreach ($functions as $fun => $available) {
            if ($available && !\function_exists($fun)) {
                $this->markTestSkipped("Can not test generateRandomKey() branch that includes $fun, because it is not available on your system.");
            }
        }
        // there is no /dev/urandom on windows so we expect this to fail
        if ($this->isWindows() && $functions['random_bytes'] === false && $functions['openssl_random_pseudo_bytes'] === false && $functions['mcrypt_create_iv'] === false) {
            $this->expectException('yii\base\Exception');
            $this->expectExceptionMessage('Unable to generate a random key');
        }
        // Function mcrypt_create_iv() is deprecated since PHP 7.1
        if (version_compare(PHP_VERSION, '7.1.0alpha', '>=') && $functions['random_bytes'] === false && $functions['mcrypt_create_iv'] === true) {
            if ($functions['openssl_random_pseudo_bytes'] === false) {
                $this->markTestSkipped('Function mcrypt_create_iv() is deprecated as of PHP 7.1');
            } elseif (!$this->security->shouldUseLibreSSL() && !$this->isWindows()) {
                $this->markTestSkipped('Function openssl_random_pseudo_bytes need LibreSSL version >=2.1.5 or Windows system on server');
            }
        }

        static::$functions = $functions;


        // force /dev/urandom reading loop to deal with chunked data
        // the above test may have read everything in one run.
        // not sure if this can happen in real life but if it does
        // we should be prepared
        static::$fopen = fopen('php://memory', 'rwb');
        $length = 1024 * 1024;
        $key1 = $this->security->generateRandomKey($length);
        $this->assertInternalType('string', $key1);
        $this->assertEquals($length, strlen($key1));
    public function testGenerateRandomKeySpeed()
    {
        self::markTestSkipped('Comment markTestSkipped in testGenerateRandomKeySpeed() in order to get RNG benchmark.');
        $tests = [
            "function_exists('random_bytes')",
            "defined('OPENSSL_VERSION_TEXT') ? OPENSSL_VERSION_TEXT : null",
            'PHP_VERSION_ID',
            'PHP_OS',
            "function_exists('mcrypt_create_iv') ? bin2hex(mcrypt_create_iv(4, MCRYPT_DEV_URANDOM)) : null",
            'DIRECTORY_SEPARATOR',
            "ini_get('open_basedir')",
        ];
        if ($this->isWindows()) {
            $tests[] = "sprintf('%o', lstat(PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom')['mode'] & 0170000)";
            $tests[] = "bin2hex(file_get_contents(PHP_OS === 'FreeBSD' ? '/dev/random' : '/dev/urandom', false, null, 0, 8))";
        }
        foreach ($tests as $i => $test) {
            $result = eval('return ' . $test . ';');
            fwrite(STDERR, sprintf("%2d %s ==> %s\n", $i + 1, $test, var_export($result, true)));
        }

        foreach ([16, 2000, 262144] as $block) {
            $security = new Security();
            foreach (range(1, 10) as $nth) {
                $this->randTime($security, 1, $block, "Call $nth");
            }
            unset($security);
        }

        $security = new Security();
        $this->randTime($security, 10000, 16, 'Rate test');

        $security = new Security();
        $this->randTime($security, 10000, 5000, 'Rate test');
    }

} // closing namespace yiiunit\framework\base;
                $randomString .= $characters[rand(0, strlen($characters) - 1)];
                $size = isset($param['size']) ? $param['size'] : rand(
            'name' => md5(mt_rand()),
            'tmp_name' => md5(mt_rand()),
            'size' => mt_rand(1000, 10000),
            'name' => md5(mt_rand()),
            'size' => mt_rand(1000, 10000),
Fixed code:         "bower-asset/yii2-pjax": "~2.0.1",
        "paragonie/random_compat": ">=1"
    "content-hash": "8f9a4d7e645592f806605d32d676f54e",
        {
            "name": "paragonie/random_compat",
            "version": "v2.0.20",
            "source": {
                "type": "git",
                "url": "https://github.com/paragonie/random_compat.git",
                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
                "reference": "0f1f60250fccffeaf5dda91eea1c018aed1adc2a",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.0"
            },
            "require-dev": {
                "phpunit/phpunit": "4.*|5.*"
            },
            "suggest": {
                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
            },
            "type": "library",
            "autoload": {
                "files": [
                    "lib/random.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Paragon Initiative Enterprises",
                    "email": "security@paragonie.com",
                    "homepage": "https://paragonie.com"
                }
            ],
            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
            "keywords": [
                "csprng",
                "polyfill",
                "pseudorandom",
                "random"
            ],
            "support": {
                "email": "info@paragonie.com",
                "issues": "https://github.com/paragonie/random_compat/issues",
                "source": "https://github.com/paragonie/random_compat"
            },
            "time": "2021-04-17T09:33:01+00:00"
        },
    "plugin-api-version": "2.1.0"
- Enh #18817: Use `paragonie/random_compat` for random bytes and int generation (samdark)
        return random_bytes($length);

        if ($force || random_int(0, 1000000) < $this->gcProbability) {
        if ($force || random_int(0, 1000000) < $this->gcProbability) {

        $length = random_int($this->minLength, $this->maxLength);
            if ($i % 2 && random_int(0, 10) > 2 || !($i % 2) && random_int(0, 10) > 9) {
                $code .= $vowels[random_int(0, 4)];
                $code .= $letters[random_int(0, 20)];
            $fontSize = (int) (random_int(26, 32) * $scale * 0.8);
            $angle = random_int(-10, 10);
            $draw->setFontSize((int) (random_int(26, 32) * $scale * 0.8));
            $image->annotateImage($draw, $x, $y, random_int(-10, 10), $code[$i]);
        "bower-asset/yii2-pjax": "~2.0.1",
        "paragonie/random_compat": ">=1"
        return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', random_int(0, 10000)) . '.eml';
namespace yiiunit\framework\base;
    public function testGenerateRandomKey()
                $randomString .= $characters[random_int(0, strlen($characters) - 1)];
                $size = isset($param['size']) ? $param['size'] : random_int(
use Yii;
            'name' => md5(random_int(0, PHP_INT_MAX)),
            'tmp_name' => md5(random_int(0, PHP_INT_MAX)),
            'size' => random_int(1000, 10000),
            'name' => md5(random_int(0, PHP_INT_MAX)),
            'size' => random_int(1000, 10000),