WordPress.org

Make WordPress Core

Ticket #28633: 28633_alt.patch

File 28633_alt.patch, 8.8 KB (added by sarciszewski, 5 years ago)

Alternative, based on https://github.com/resonantcore/lib/blob/develop/src/Secure.php which I have tested extensively.

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

    From 830fbbab8b5cff4e901b139186df620604cdc5d2 Mon Sep 17 00:00:00 2001
    From: Scott Arciszewski <scott.arciszewski@bookthatdoc.com>
    Date: Thu, 12 Feb 2015 13:21:16 -0500
    Subject: [PATCH] Patch round 6.
    
    ---
     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     | 153 +++++++++++++++++++++++++++++++++++++-
     tests/phpunit/tests/functions.php |  34 +++++++++
     5 files changed, 191 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..1b536d7 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     * If /dev/urandom is available, let's prefer it over every other method
     2031     *
     2032     * This should only return false on Windows hosts and in chroot jails
     2033     */
     2034        if (is_readable('/dev/urandom')) {
     2035                $fp = fopen('/dev/urandom', 'rb');
     2036        stream_set_read_buffer($fp, 0);
     2037                if ($fp !== false) {
     2038                        $buf = fread($fp, $bytes);
     2039                        fclose($fp);
     2040                        if ($buf !== FALSE) {
     2041                                return $buf;
     2042                        }
     2043                }
     2044        }
     2045    /**
     2046     * This is preferable to userspace RNGs (e.g. openssl)
     2047     * because it reads directly from /dev/urandom on most OS's,
     2048     * and uses Windows's Crypto APIs transparently.
     2049     *
     2050     * Requires the mcrypt extension be installed/enabled.
     2051     */
     2052        if (function_exists('mcrypt_create_iv')) {
     2053                $buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
     2054                if($buf !== FALSE) {
     2055                        return $buf;
     2056                }
     2057        }
     2058    /**
     2059     * This is available since PHP 5.3.0 on every PHP install.
     2060     */
     2061        if (function_exists('openssl_random_pseudo_bytes')) {
     2062                $buf = openssl_random_pseudo_bytes($bytes);
     2063        if ($buf !== false) {
     2064                        return $buf;
     2065        }
     2066        }
     2067
     2068    /**
     2069     * Windows with PHP < 5.3.0 will not have the function
     2070     * openssl_random_pseudo_bytes() available, so let's use
     2071     * CAPICOM to work around this deficiency.
     2072     *
     2073     * When we stop supporting unsupported versions of PHP,
     2074     * (i.e. less than 5.5) this should be easily removable.
     2075     */
     2076    if (class_exists('\\COM', false)) {
     2077        try {
     2078            $util = new \COM('CAPICOM.Utilities.1');
     2079            $execs = 0;
     2080            /**
     2081             * Let's not let it loop forever. If we run N times and fail to
     2082             * get N bytes of entropy, then CAPICOM has failed us.
     2083             */
     2084            while ($execs < $bytes) {
     2085                $buf .= base64_decode($util->GetRandom($bytes, 0));
     2086                if (strlen($buf) > $bytes) {
     2087                    return substr($buf, 0, $bytes);
     2088                }
     2089            }
     2090        } catch (\Exception($e)) {
     2091            unset($e); // Let's not let CAPICOM errors kill our app
     2092        }
     2093    }
     2094    /**
     2095     * Okay, at this point, we cannot guarantee a CSPRNG no matter what we
     2096     * do. Our only recourse is to return something insecure.
     2097     */
     2098    for ($i = 0; $i < $bytes; ++$i) {
     2099        $buf .= chr(wp_rand(0, 255));
     2100    }
     2101    return $buf;
     2102}
     2103
     2104endif;
     2105
     2106if ( !function_exists('wp_random_positive_int'):
     2107/**
     2108 * Generate a random positive integer between 0 and PHP_INT_MAX
     2109 *
     2110 * @return int A random positive integer
     2111 */
     2112function wp_random_positive_int() {
     2113    $buf = wp_random_bytes(PHP_INT_SIZE);
     2114    if($buf === false) {
     2115        trigger_error('Random number failure', E_USER_ERROR);
     2116    }
     2117
     2118    $val = 0;
     2119    $i = strlen($buf);
     2120
     2121    do {
     2122        $i--;
     2123        $val <<= 8;
     2124        $val |= ord($buf[$i]);
     2125    } while ($i != 0);
     2126
     2127    return $val & PHP_INT_MAX;
     2128}
     2129endif;
     2130
     2131if ( !function_exists('wp_secure_rand') ):
     2132/**
     2133 * Generates an unbiased, unpredictably random number
     2134 *
     2135 * @since x.x.x
     2136 *
     2137 * @param int $min Lower limit for the generated number
     2138 * @param int $max Upper limit for the generated number
     2139 * @return int A random number between min and max
     2140 */
     2141function wp_secure_rand( $min = 0, $max = 0) {
     2142    $range = $max - $min;
     2143    if ($range < 2) {
     2144        return $min;
     2145    }
     2146   
     2147    // 7776 -> 13
     2148    $bits = ceil(log($range)/log(2));
     2149   
     2150    // 2^13 - 1 == 8191 or 0x00001111 11111111
     2151    $mask = ceil(pow(2, $bits)) - 1;
     2152   
     2153    do {
     2154        // Grab a random integer
     2155        $val = wp_random_positive_int();
     2156        if ($val === FALSE) {
     2157            // RNG failure
     2158            return FALSE;
     2159        }
     2160        // Apply mask
     2161        $val = $val & $mask;
     2162        // If $val is larger than the maximum acceptable number for
     2163        // $min and $max, we discard and try again.
     2164    } while ($val > $range);
     2165    return (int) ($min + $val);
     2166}
     2167
     2168endif;
     2169
    20192170
    20202171if ( !function_exists('wp_rand') ) :
    20212172/**
  • 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}