joomla / joomla-cms

Home of the Joomla! Content Management System
https://www.joomla.org
GNU General Public License v2.0
4.73k stars 3.64k forks source link

PHP failed to generate random data #9018

Closed flo1212 closed 8 years ago

flo1212 commented 8 years ago

The error appears by Installer und after Update Server with PHP 5.5/MySQL 5.5 and also PHP 5.4/MySQL 5.1

possibly an adjustment in the php.ini ?

mbabker commented 8 years ago

Please enable debug mode and provide the stack trace leading to that error (some templates may not render the stack trace even in debug mode, in which case you'll need to switch to one of the core templates).

FWIW, that error text comes from the random_bytes() polyfill and is basically a result of whatever internal path it used to generate random byte data being unable to get an adequate amount of data to generate this.

flo1212 commented 8 years ago

It is not possible to turn on the debug mode, because the script aborts before wi can see debug-output. The installer creates these HTML Output:

`

PHP failed to generate random data.`
flo1212 commented 8 years ago

BTW: Beta 1 runs

mbabker commented 8 years ago

The code in question was added after beta 1.

Basically, to get that error message, the code being used is either unable to read the data source to generate random bytes or is unable to get an adequate amount of data (i.e. if 32 bytes are requested and a source only returns 24 bytes). A stack trace will tell which method the polyfill library is using to generate the random bytes data (this doc block explains the order that the library uses) and that would help to give system specific instructions on why you're getting the error.

Edit the libraries/cms/html/form.php and replace the token() method with this snippet of code:

try {
    return '<input type="hidden" name="' . JSession::getFormToken() . '" value="1" />';
} catch (Exception $e) {
    echo $e->getTraceAsString();die;
}

The point it's failing at is when the CSRF token is generated. This will catch the error and give a trace.

flo1212 commented 8 years ago

The Result is:

#0 /htdocs/libraries/joomla/crypt/crypt.php(126): random_bytes(33) 
#1 /htdocs/libraries/joomla/user/helper.php(655): JCrypt::genRandomBytes(33) 
#2 /htdocs/libraries/joomla/session/session.php(816): JUserHelper::genRandomPassword(32) 
#3 /htdocs/libraries/joomla/session/session.php(232): JSession->_createToken() 
#4 /htdocs/libraries/joomla/session/session.php(283): JSession->getToken(false) 
#5 /htdocs/libraries/cms/html/form.php(32): JSession::getFormToken() 
#6 [internal function]: JHtmlForm::token() 
#7 /htdocs/libraries/cms/html/html.php(236): call_user_func_array(Array, Array) 
#8 /htdocs/libraries/cms/html/html.php(138): JHtml::call(Array, Array) 
#9 /htdocs/installation/view/site/tmpl/default.php(28): JHtml::_('form.token') 
#10 /htdocs/libraries/joomla/view/html.php(148): include('/var/www/virtua...') 
#11 /htdocs/installation/view/default.php(46): JViewHtml->render() 
#12 /htdocs/installation/controller/default.php(112): InstallationViewDefault->render() 
#13 /htdocs/installation/application/web.php(161): InstallationControllerDefault->execute() 
#14 /htdocs/installation/application/web.php(200): InstallationApplicationWeb->dispatch() 
#15 /htdocs/libraries/cms/application/cms.php(257): InstallationApplicationWeb->doExecute() 
#16 /htdocs/installation/index.php(32): JApplicationCms->execute() 
#17 {main}
flo1212 commented 8 years ago

The problem is in: /libraries/joomla/crypt/crypt.php Inkompatibel: public static function genRandomBytes($length = 16) { return random_bytes($length); } if i use the function from Beta 1, then its OK!

mbabker commented 8 years ago

Like I pointed out earlier, the implementation was changed after beta 1 (specifically to an option that provides more random data sources and mocks the PHP 7 random_bytes() function's behavior). The stack trace ended up not being so helpful because it stops at JCrypt::genRandomBytes() instead of continuing into the polyfill library. If it worked on beta 1 but not now, there are a few possibilities:

1) Your installation has PHP's ext/openssl installed and Joomla was using that without issue to generate random bytes (note this is inherently the weakest option made available in the polyfill library and PHP 7's native function doesn't even attempt to use this extension) 2) Joomla wasn't using ext/openssl and instead read from /dev/urandom (only available on a Linux based system), and that was properly being read/processed (the polyfill library uses this as the second choice only behind libsodium but has some additional checks to validate data compared to the old Joomla implementation so it could be failing with the additional validations that library uses if this source is in use) 3) Joomla was using neither the ext/openssl option nor reading from /dev/urandom so essentially you were using Joomla's custom random number generator, something not available with the new library (instead it's presumably using the mcrypt implementation and I have seen cases where new or low traffic servers will fail with mcrypt because there is not a high enough entropy in the system to generate secure random values)

We can validate what method Joomla was using with the old code. Use this as a replacement for JCrypt::genRandomBytes():

    public static function genRandomBytes($length = 16)
    {
        $length = (int) $length;
        $sslStr = '';

        // If a secure randomness generator exists use it.
        if (function_exists('openssl_random_pseudo_bytes'))
        {
            $sslStr = openssl_random_pseudo_bytes($length, $strong);

            if ($strong)
            {
                echo 'Used OpenSSL';die;
                return $sslStr;
            }
        }

        /*
         * Collect any entropy available in the system along with a number
         * of time measurements of operating system randomness.
         */
        $bitsPerRound = 2;
        $maxTimeMicro = 400;
        $shaHashLength = 20;
        $randomStr = '';
        $total = $length;

        // Check if we can use /dev/urandom.
        $urandom = false;
        $handle = null;

        // This is PHP 5.3.3 and up
        if (function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
        {
            $handle = @fopen('/dev/urandom', 'rb');

            if ($handle)
            {
                echo 'Used /dev/urandom';die;
                $urandom = true;
            }
        }

        while ($length > strlen($randomStr))
        {
            $bytes = ($total > $shaHashLength)? $shaHashLength : $total;
            $total -= $bytes;

            /*
             * Collect any entropy available from the PHP system and filesystem.
             * If we have ssl data that isn't strong, we use it once.
             */
            $entropy = rand() . uniqid(mt_rand(), true) . $sslStr;
            $entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
            $entropy .= memory_get_usage();
            $sslStr = '';

            if ($urandom)
            {
                stream_set_read_buffer($handle, 0);
                $entropy .= @fread($handle, $bytes);
            }
            else
            {
                echo 'Used Joomla custom';die;
                /*
                 * There is no external source of entropy so we repeat calls
                 * to mt_rand until we are assured there's real randomness in
                 * the result.
                 *
                 * Measure the time that the operations will take on average.
                 */
                $samples = 3;
                $duration = 0;

                for ($pass = 0; $pass < $samples; ++$pass)
                {
                    $microStart = microtime(true) * 1000000;
                    $hash = sha1(mt_rand(), true);

                    for ($count = 0; $count < 50; ++$count)
                    {
                        $hash = sha1($hash, true);
                    }

                    $microEnd = microtime(true) * 1000000;
                    $entropy .= $microStart . $microEnd;

                    if ($microStart >= $microEnd)
                    {
                        $microEnd += 1000000;
                    }

                    $duration += $microEnd - $microStart;
                }

                $duration = $duration / $samples;

                /*
                 * Based on the average time, determine the total rounds so that
                 * the total running time is bounded to a reasonable number.
                 */
                $rounds = (int) (($maxTimeMicro / $duration) * 50);

                /*
                 * Take additional measurements. On average we can expect
                 * at least $bitsPerRound bits of entropy from each measurement.
                 */
                $iter = $bytes * (int) ceil(8 / $bitsPerRound);

                for ($pass = 0; $pass < $iter; ++$pass)
                {
                    $microStart = microtime(true);
                    $hash = sha1(mt_rand(), true);

                    for ($count = 0; $count < $rounds; ++$count)
                    {
                        $hash = sha1($hash, true);
                    }

                    $entropy .= $microStart . microtime(true);
                }
            }

            $randomStr .= sha1($entropy, true);
        }

        if ($urandom)
        {
            @fclose($handle);
        }

        return substr($randomStr, 0, $length);
    }

If it comes back as using OpenSSL, there's a good chance the backfill library is using another option (as that's the choice of last resort). If it comes back as using /dev/urandom then presumably one of the checks in the backfill library isn't passing.

To know for sure which adapter is being used, in the libraries/vendor/paragonie/random_compat/lib directory edit each of the random_bytes_*.php files and just append whatever the adapter type is to the end of the Exception message at the end of the file (so com_dotnet's message looks something like PHP failed to generate random data (com_dotnet).).

flo1212 commented 8 years ago

I think I've found the cause of the problem . PHP did not have access to /dev This has been corrected on the server. Now the Installatation works with PHP 5.4 , 5.5 and 5.6 . However, the backend will not work with PHP 5.4 Fatal error: Can't use method return value in write context in /var/www/virtual/test/htdocs/administrator/modules/mod_version/helper.php on line 36

brianteeman commented 8 years ago

Your new error was a known issue with php 5.4 which has been resolved in the current staging. On 30 Jan 2016 1:30 pm, "flo1212" notifications@github.com wrote:

I think I've found the cause of the problem . PHP did not have access to /dev This has been corrected on the server. Now works the Installatation works with PHP 5.4 , 5.5 and 5.6 . However, the backend will not work PHP 5.4 Fatal error: Can't use method return value in write context in /var/www/virtual/test/htdocs/administrator/modules/mod_version/helper.php on line 36

— Reply to this email directly or view it on GitHub https://github.com/joomla/joomla-cms/issues/9018#issuecomment-177177529.

flo1212 commented 8 years ago

Thanks!

Bakual commented 8 years ago

Can we close this as it was a server issue? Or is there something we need (or can) take care of?

flo1212 commented 8 years ago

yes it was a server problem , because our security settings .