WordPress.org

Make WordPress Core

Ticket #28633: 28633.7.patch

File 28633.7.patch, 11.4 KB (added by sarciszewski, 5 years ago)

@Otto42 - You're absolutely correct. Also, there's a bug in mcrypt on Windows for PHP < 5.3.7 where it always produces insufficient entropy.

  • src/wp-admin/includes/ms.php

    From ef776ac723ee755ffb50709b3760b10ed3a4cc30 Mon Sep 17 00:00:00 2001
    From: Scott Arciszewski <scott@resonantcore.net>
    Date: Sat, 14 Feb 2015 17:42:26 -0500
    Subject: [PATCH] Round 7
    
    ---
     src/wp-admin/includes/ms.php      |   4 +-
     src/wp-includes/ms-functions.php  |   4 +-
     src/wp-includes/pluggable.php     | 243 +++++++++++++++++++++++++++++++++++++-
     tests/phpunit/tests/functions.php |  34 ++++++
     4 files changed, 279 insertions(+), 6 deletions(-)
    
    diff --git a/src/wp-admin/includes/ms.php b/src/wp-admin/includes/ms.php
    index 71129bb..637acef 100644
    a b function update_option_new_admin_email( $old_value, $value ) { 
    246246        if ( $value == get_option( 'admin_email' ) || !is_email( $value ) )
    247247                return;
    248248
    249         $hash = md5( $value. time() .mt_rand() );
     249        $hash = md5( wp_random_bytes(16) . $value. time() .mt_rand() );
    250250        $new_admin_email = array(
    251251                'hash' => $hash,
    252252                'newemail' => $value
    function send_confirmation_on_profile_email() { 
    327327                        return;
    328328                }
    329329
    330                 $hash = md5( $_POST['email'] . time() . mt_rand() );
     330                $hash = bin2hex(wp_random_bytes(16));
    331331                $new_user_email = array(
    332332                                'hash' => $hash,
    333333                                'newemail' => $_POST['email']
  • src/wp-includes/ms-functions.php

    diff --git a/src/wp-includes/ms-functions.php b/src/wp-includes/ms-functions.php
    index 0e38638..b3d76bc 100644
    a b function wpmu_validate_blog_signup( $blogname, $blog_title, $user = '' ) { 
    710710function wpmu_signup_blog( $domain, $path, $title, $user, $user_email, $meta = array() )  {
    711711        global $wpdb;
    712712
    713         $key = substr( md5( time() . rand() . $domain ), 0, 16 );
     713        $key = bin2hex(wp_random_bytes(8));
    714714        $meta = serialize($meta);
    715715
    716716        $wpdb->insert( $wpdb->signups, array(
    function wpmu_signup_user( $user, $user_email, $meta = array() ) { 
    745745        // Format data
    746746        $user = preg_replace( '/\s+/', '', sanitize_user( $user, true ) );
    747747        $user_email = sanitize_email( $user_email );
    748         $key = substr( md5( time() . rand() . $user_email ), 0, 16 );
     748        $key = bin2hex(wp_random_bytes(8));
    749749        $meta = serialize($meta);
    750750
    751751        $wpdb->insert( $wpdb->signups, array(
  • src/wp-includes/pluggable.php

    diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php
    index ebb7001..1309579 100644
    a b function wp_generate_password( $length = 12, $special_chars = true, $extra_speci 
    20022002                $chars .= '-_ []{}<>~`+=,.;:/?|';
    20032003
    20042004        $password = '';
    2005         for ( $i = 0; $i < $length; $i++ ) {
    2006                 $password .= substr($chars, wp_rand(0, strlen($chars) - 1), 1);
     2005        for ( $i = 0; $i < $length; ++$i ) {
     2006        $rand = wp_secure_rand(0, strlen($chars) - 1);
     2007                $password .= $chars[$rand];
    20072008        }
    20082009
    20092010        /**
    function wp_generate_password( $length = 12, $special_chars = true, $extra_speci 
    20162017        return apply_filters( 'random_password', $password );
    20172018}
    20182019endif;
     2020if ( !function_exists('wp_random_bytes') ) :
     2021
     2022/**
     2023 * Generate a cryptographically secure random string
     2024 * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
     2025 *
     2026 * @param int $bytes - how many bytes do we want?
     2027 */
     2028function wp_random_bytes($bytes = 32) {
     2029        $buf = '';
     2030    /**
     2031     * This is preferable to userspace RNGs (e.g. openssl)
     2032     * because it reads directly from /dev/urandom on most OS's,
     2033     * and uses Windows's Crypto APIs transparently.
     2034     *
     2035     * See PHP bug #55169 for why version 5.3.7 is required
     2036     *
     2037     * Requires the mcrypt extension be installed/enabled.
     2038     */
     2039        if ( function_exists('mcrypt_create_iv') && version_compare(PHP_VERSION, '5.3.7') >= 0 ) {
     2040                $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
     2041                if ($buf !== FALSE) {
     2042                        return $buf;
     2043                }
     2044        }
     2045    /**
     2046     * If /dev/urandom is available, let's prefer it over every other method
     2047     *
     2048     * This should only return false on Windows hosts and in chroot jails
     2049     */
     2050        if ( is_readable('/dev/urandom') ) {
     2051                $fp = fopen('/dev/urandom', 'rb');
     2052        // Extra cautious, thanks to feedback from Taylor Hornby (https://defuse.ca)
     2053        $streamset = stream_set_read_buffer($fp, 0);
     2054                if ( $fp !== false && $streamset === 0 ) {
     2055            $buf = '';
     2056            // How many bytes do we need?
     2057            $remaining = $bytes;
     2058            do {
     2059                $read = fread($fp, $remaining);
     2060                if ( $read === FALSE ) {
     2061                    // We cannot safely read from urandom.
     2062                    $buf = FALSE;
     2063                    break;
     2064                }
     2065                // Decrease the number of bytes returned from remaining
     2066                $remaining -= wp_binsafe_strlen($read);
     2067
     2068                // We actually read bytes! Let's append them to our buffer
     2069                $buf .= $read;
     2070
     2071            } while ( $remaining > 0 );
     2072            // Let's make sure we have enough bytes
     2073
     2074                        fclose($fp);
     2075                        if ( $buf !== FALSE ) {
     2076                return $buf;
     2077                        }
     2078                }
     2079        }
     2080
     2081    /**
     2082     * This is available since PHP 5.3.0 on every PHP install.
     2083     *
     2084     * Better than nothing, but not really urandom
     2085     */
     2086        if ( function_exists('openssl_random_pseudo_bytes') ) {
     2087                $buf = openssl_random_pseudo_bytes($bytes);
     2088        if ( $buf !== false ) {
     2089                        return $buf;
     2090        }
     2091        }
     2092
     2093    /**
     2094     * Windows with PHP < 5.3.0 will not have the function
     2095     * openssl_random_pseudo_bytes() available, so let's use
     2096     * CAPICOM to work around this deficiency.
     2097     */
     2098    if ( class_exists('COM', false) ) {
     2099        try {
     2100            if ($buf === FALSE) {
     2101                $buf = ''; // Make it a string, not false
     2102            }
     2103            $util = new COM('CAPICOM.Utilities.1');
     2104            $execs = 0;
     2105            /**
     2106             * Let's not let it loop forever. If we run N times and fail to
     2107             * get N bytes of entropy, then CAPICOM has failed us.
     2108             */
     2109            do {
     2110                $buf .= base64_decode( $util->GetRandom($bytes, 0) );
     2111                if ( wp_binsafe_strlen($buf) >= $bytes ) {
     2112                    return wp_binsafe_substr($buf, 0, $bytes);
     2113                }
     2114                ++$execs;
     2115            } while ( $execs < $bytes );
     2116        } catch ( Exception($e) ) {
     2117            unset($e); // Let's not let CAPICOM errors kill our app
     2118        }
     2119    }
     2120    if ($buf === FALSE) {
     2121        $buf = ''; // Make it a string, not false
     2122    }
     2123    /**
     2124     * Okay, at this point, we cannot guarantee a CSPRNG no matter what we
     2125     * do. Our only recourse is to rely on wp_rand()
     2126     */
     2127    for ($i = 0; $i < $bytes; ++$i) {
     2128        $buf .= chr( wp_rand(0, 255) );
     2129    }
     2130    return $buf;
     2131}
     2132endif;
     2133
     2134if ( !function_exists('wp_binsafe_strlen') ):
     2135/**
     2136 * Binary-safe string length (won't get chewed by multibyte strings)
     2137 *
     2138 * @param string $str The string whose length we are retreiving
     2139 * @return int The string length (binary-safe)
     2140 */
     2141function wp_binsafe_strlen($str) {
     2142    if ( function_exists('mb_strlen') ) {
     2143        return mb_strlen($str, '8bit');
     2144    }
     2145    return strlen($str);
     2146}
     2147endif;
     2148if ( !function_exists('wp_binsafe_substr') ):
     2149/**
     2150 * Binary-safe substring (won't get chewed by multibyte strings)
     2151 *
     2152 * @param string $str The string whose length we are retreiving
     2153 * @param int $start     The starting point in the binary string
     2154 * @param int $length optional - how many bytes to select
     2155 * @return int The string length (binary-safe)
     2156 */
     2157function wp_binsafe_substr($str, $start, $length = null) {
     2158    if ( function_exists('mb_substr') ) {
     2159        return mb_substr($str, $start, $length, '8bit');
     2160    }
     2161    return substr($str, $start, $length);
     2162}
     2163endif;
     2164
     2165
     2166if ( !function_exists('wp_random_positive_int') ):
     2167/**
     2168 * Generate a random positive integer between 0 and PHP_INT_MAX
     2169 *
     2170 * @return int A random positive integer
     2171 */
     2172function wp_random_positive_int() {
     2173    $buf = wp_random_bytes(PHP_INT_SIZE);
     2174    if( $buf === false ) {
     2175        trigger_error('Random number failure', E_USER_ERROR);
     2176    }
     2177
     2178    $val = 0;
     2179    $i = wp_binsafe_strlen($buf);
     2180
     2181    do {
     2182        $i--;
     2183        $val <<= 8;
     2184        $val |= ord($buf[$i]);
     2185    } while ( $i !== 0 );
     2186
     2187    return $val & PHP_INT_MAX;
     2188}
     2189endif;
     2190
     2191if ( !function_exists('wp_secure_rand') ):
     2192/**
     2193 * Generates an unbiased, unpredictably random number
     2194 *
     2195 * @since x.x.x
     2196 *
     2197 * @param int $min Lower limit for the generated number
     2198 * @param int $max Upper limit for the generated number
     2199 * @return int|bool A random number between min and max, False on failure
     2200 */
     2201function wp_secure_rand($min = 0, $max = 0) {
     2202    if ( !is_int($min) || !is_int($max) ) {
     2203        return (int) $min;
     2204    }
     2205    $range = (int) $max - $min;
     2206    if ($range < 2) {
     2207        return $min;
     2208    }
     2209
     2210    /**
     2211     * At this stage, we are calculating a bit mask to use
     2212     * to reduce the number of calls to wp_random_positive_int()
     2213     *
     2214     * We calculate the minimum number of bits to store $range
     2215     * ex. 9000 would need to be stored in 14 bits because it is larger than 2^13 but smaller than 2^14
     2216     *
     2217     * Then we take that even power of 2 and subtract 1 so we get a number that,
     2218     * when represented in binary, is all 1s.
     2219     *
     2220     * Later, when we apply our AND operation with the mask, all bits above 2^14
     2221     * would become 0 and the rest remain unchanged.
     2222     */
     2223    $bits = ceil(log($range)/log(2));
     2224    // ex. 9000 -> 14
     2225    $mask = ceil(pow(2, $bits)) - 1;
     2226    // ex. 2^14 - 1 == 16383 or 00111111 11111111
     2227
     2228    /**
     2229     *  9000 in binary is 00100011 00101000
     2230     * 16383 in binary is 00111111 11111111
     2231     *  9000 & 16383  ==  00100011 00101000
     2232     *
     2233     * If this test fails, there is something seriously wrong :(
     2234     */
     2235    if ( ($range & $mask) !== $range ) {
     2236        // Runtime test failed
     2237        return FALSE;
     2238    }
     2239
     2240    do {
     2241        // Grab a random integer
     2242        $val = wp_random_positive_int();
     2243        if ($val === FALSE) {
     2244            // RNG failure
     2245            return FALSE;
     2246        }
     2247        // Apply mask
     2248        $val = $val & $mask;
     2249        // If $val is larger than the maximum acceptable number for
     2250        // $min and $max, we discard and try again.
     2251    } while ($val > $range);
     2252
     2253    // We now have an unbiased value between 0 and ($max - $min), so let's add it to $min
     2254    return (int) ($min + $val);
     2255}
     2256
     2257endif;
    20192258
    20202259if ( !function_exists('wp_rand') ) :
    20212260/**
  • tests/phpunit/tests/functions.php

    diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
    index 527780a..81783b6 100644
    a b class Tests_Functions extends WP_UnitTestCase { 
    626626                $json = wp_json_encode( $data, 0, 1 );
    627627                $this->assertFalse( $json );
    628628        }
     629    /**
     630     * @ticket 28633
     631     */
     632    function test_wp_secure_rand() {
     633        // Let's load a buffer
     634        $buff = array();
     635
     636        // How size do we want our test array to be?
     637
     638        //$size = wp_rand(16,256);
     639        $size = 16;
     640
     641        // We should expect
     642        $to_run = 10;
     643
     644        //
     645        for($i = 0; $i < $size; ++$i) {
     646            $buff[$i] = 0;
     647        }
     648
     649        $tests = $size * $to_run;
     650
     651        for($i = 0; $i < $tests; ++$i) {
     652            ++$buff[wp_secure_rand(0, $size - 1)];
     653        }
     654
     655        // With any luck, all of these should be near $to_run.
     656        $passed = 0;
     657        for($i = 0; $i < $size; ++$i) {
     658            $this->assertNotEquals($buff[$i], 0);
     659        }
     660
     661        // We can do statistical tests later to ensure the output is unbiased.
     662    }
    629663}