Make WordPress Core

Ticket #28633: 28633.6.patch

File 28633.6.patch, 10.6 KB (added by sarciszewski, 10 years ago)

More paranoid return checking, use a binary-safe strlen() and substr()

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

    From 335a16f65d95b8182b9cc4a1055a9d14a9c34feb Mon Sep 17 00:00:00 2001
    From: Scott Arciszewski <scott@arciszewski.me>
    Date: Thu, 12 Feb 2015 21:06:21 -0500
    Subject: [PATCH] Patch with review from Taylor Hornby
    
    ---
     src/wp-admin/includes/ms.php      |   4 +-
     src/wp-admin/includes/schema.php  |   2 +-
     src/wp-includes/ms-functions.php  |   4 +-
     src/wp-includes/pluggable.php     | 207 +++++++++++++++++++++++++++++++++++++-
     tests/phpunit/tests/functions.php |  34 +++++++
     5 files changed, 245 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-admin/includes/schema.php

    diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php
    index c522ab5..2ef1a8a 100644
    a b We hope you enjoy your new site. Thanks! 
    10231023
    10241024                $vhost_ok = false;
    10251025                $errstr = '';
    1026                 $hostname = substr( md5( time() ), 0, 6 ) . '.' . $domain; // Very random hostname!
     1026                $hostname = bin2hex( wp_random_bytes(3) ) . '.' . $domain; // Very random hostname!
    10271027                $page = wp_remote_get( 'http://' . $hostname, array( 'timeout' => 5, 'httpversion' => '1.1' ) );
    10281028                if ( is_wp_error( $page ) )
    10291029                        $errstr = $page->get_error_message();
  • 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..3a8d930 100644
    a b function wp_generate_password( $length = 12, $special_chars = true, $extra_speci 
    20032003
    20042004        $password = '';
    20052005        for ( $i = 0; $i < $length; $i++ ) {
    2006                 $password .= substr($chars, wp_rand(0, strlen($chars) - 1), 1);
     2006                $password .= substr($chars, wp_secure_rand(0, strlen($chars) - 1), 1);
    20072007        }
    20082008
    20092009        /**
    function wp_generate_password( $length = 12, $special_chars = true, $extra_speci 
    20162016        return apply_filters( 'random_password', $password );
    20172017}
    20182018endif;
     2019if ( !function_exists('wp_random_bytes') ) :
     2020
     2021/**
     2022 * Generate a cryptographically secure random string
     2023 * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
     2024 *
     2025 * @param int $bytes - how many bytes do we want?
     2026 */
     2027function wp_random_bytes($bytes = 32) {
     2028        $buf = '';
     2029    /**
     2030     * This is preferable to userspace RNGs (e.g. openssl)
     2031     * because it reads directly from /dev/urandom on most OS's,
     2032     * and uses Windows's Crypto APIs transparently.
     2033     *
     2034     * Requires the mcrypt extension be installed/enabled.
     2035     */
     2036        if (function_exists('mcrypt_create_iv')) {
     2037                $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
     2038                if($buf !== FALSE) {
     2039                        return $buf;
     2040                }
     2041        }
     2042    /**
     2043     * If /dev/urandom is available, let's prefer it over every other method
     2044     *
     2045     * This should only return false on Windows hosts and in chroot jails
     2046     */
     2047        if (is_readable('/dev/urandom')) {
     2048                $fp = fopen('/dev/urandom', 'rb');
     2049        // Extra cautious, thanks to feedback from Taylor Hornby (https://defuse.ca)
     2050        $streamset = stream_set_read_buffer($fp, 0);
     2051                if ($fp !== false && $streamset === 0) {
     2052            $buf = '';
     2053            // How many bytes do we need?
     2054            $remaining = $bytes;
     2055            do {
     2056                $read = fread($fp, $remaining);
     2057                if ($read === FALSE) {
     2058                    // We cannot safely read from urandom.
     2059                    $buf = FALSE;
     2060                    break;
     2061                }
     2062                // Decrease the number of bytes returned from remaining
     2063                $remaining -= wp_binsafe_strlen($read);
     2064
     2065                // We actually read bytes! Let's append them to our buffer
     2066                $buf .= $read;
     2067            } while ($remaining > 0);
     2068            // Let's make sure we have enough bytes
     2069
     2070                        fclose($fp);
     2071                        if ($buf !== FALSE) {
     2072                return $buf;
     2073                        }
     2074                }
     2075        }
     2076    /**
     2077     * This is available since PHP 5.3.0 on every PHP install.
     2078     *
     2079     * Better than nothing, but not really urandom
     2080     */
     2081        if (function_exists('openssl_random_pseudo_bytes')) {
     2082                $buf = openssl_random_pseudo_bytes($bytes);
     2083        if ($buf !== false) {
     2084                        return $buf;
     2085        }
     2086        }
     2087
     2088    /**
     2089     * Windows with PHP < 5.3.0 will not have the function
     2090     * openssl_random_pseudo_bytes() available, so let's use
     2091     * CAPICOM to work around this deficiency.
     2092     */
     2093    if (class_exists('\\COM', false)) {
     2094        try {
     2095            if ($buf === FALSE) {
     2096                $buf = ''; // Make it a string, not false
     2097            }
     2098            $util = new \COM('CAPICOM.Utilities.1');
     2099            $execs = 0;
     2100            /**
     2101             * Let's not let it loop forever. If we run N times and fail to
     2102             * get N bytes of entropy, then CAPICOM has failed us.
     2103             */
     2104            while ($execs < $bytes) {
     2105                $buf .= base64_decode($util->GetRandom($bytes, 0));
     2106                if (wp_binsafe_strlen($buf) >= $bytes) {
     2107                    return wp_binsafe_substr($buf, 0, $bytes);
     2108                }
     2109                ++$execs;
     2110            }
     2111        } catch (\Exception($e)) {
     2112            unset($e); // Let's not let CAPICOM errors kill our app
     2113        }
     2114    }
     2115    if ($buf === FALSE) {
     2116        $buf = ''; // Make it a string, not false
     2117    }
     2118    /**
     2119     * Okay, at this point, we cannot guarantee a CSPRNG no matter what we
     2120     * do. Our only recourse is to return something insecure.
     2121     */
     2122    for ($i = 0; $i < $bytes; ++$i) {
     2123        $buf .= chr(wp_rand(0, 255));
     2124    }
     2125    return $buf;
     2126}
     2127endif;
     2128
     2129if ( !function_exists('wp_binsafe_strlen'):
     2130/**
     2131 * Binary-safe string length (won't get chewed by multibyte strings)
     2132 *
     2133 * @param string $str The string whose length we are retreiving
     2134 * @return int The string length (binary-safe)
     2135 */
     2136function wp_binsafe_strlen($str) {
     2137    if (function_exists('mb_strlen')) {
     2138        return mb_strlen($str, '8bit');
     2139    }
     2140    return strlen($str);
     2141}
     2142endif;
     2143if ( !function_exists('wp_binsafe_substr'):
     2144/**
     2145 * Binary-safe substring (won't get chewed by multibyte strings)
     2146 *
     2147 * @param string $str The string whose length we are retreiving
     2148 * @param int $start     The
     2149 * @return int The string length (binary-safe)
     2150 */
     2151function wp_binsafe_substr($str, $start, $length = null) {
     2152    if (function_exists('mb_substr')) {
     2153        return mb_substr($str, $start, $length, '8bit');
     2154    }
     2155    return substr($str, $start, $length);
     2156}
     2157endif;
     2158
     2159
     2160if ( !function_exists('wp_random_positive_int'):
     2161/**
     2162 * Generate a random positive integer between 0 and PHP_INT_MAX
     2163 *
     2164 * @return int A random positive integer
     2165 */
     2166function wp_random_positive_int() {
     2167    $buf = wp_random_bytes(PHP_INT_SIZE);
     2168    if($buf === false) {
     2169        trigger_error('Random number failure', E_USER_ERROR);
     2170    }
     2171
     2172    $val = 0;
     2173    $i = strlen($buf);
     2174
     2175    do {
     2176        $i--;
     2177        $val <<= 8;
     2178        $val |= ord($buf[$i]);
     2179    } while ($i != 0);
     2180
     2181    return $val & PHP_INT_MAX;
     2182}
     2183endif;
     2184
     2185if ( !function_exists('wp_secure_rand') ):
     2186/**
     2187 * Generates an unbiased, unpredictably random number
     2188 *
     2189 * @since x.x.x
     2190 *
     2191 * @param int $min Lower limit for the generated number
     2192 * @param int $max Upper limit for the generated number
     2193 * @return int A random number between min and max
     2194 */
     2195function wp_secure_rand( $min = 0, $max = 0) {
     2196    $range = $max - $min;
     2197    if ($range < 2) {
     2198        return $min;
     2199    }
     2200
     2201    // 7776 -> 13
     2202    $bits = ceil(log($range)/log(2));
     2203
     2204    // 2^13 - 1 == 8191 or 0x00001111 11111111
     2205    $mask = ceil(pow(2, $bits)) - 1;
     2206
     2207    do {
     2208        // Grab a random integer
     2209        $val = wp_random_positive_int();
     2210        if ($val === FALSE) {
     2211            // RNG failure
     2212            return FALSE;
     2213        }
     2214        // Apply mask
     2215        $val = $val & $mask;
     2216        // If $val is larger than the maximum acceptable number for
     2217        // $min and $max, we discard and try again.
     2218    } while ($val > $range);
     2219    return (int) ($min + $val);
     2220}
     2221
     2222endif;
     2223
    20192224
    20202225if ( !function_exists('wp_rand') ) :
    20212226/**
  • 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}