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/src/wp-admin/includes/ms.php
+++ b/src/wp-admin/includes/ms.php
@@ -246,7 +246,7 @@ function update_option_new_admin_email( $old_value, $value ) {
 	if ( $value == get_option( 'admin_email' ) || !is_email( $value ) )
 		return;
 
-	$hash = md5( $value. time() .mt_rand() );
+	$hash = md5( wp_random_bytes(16) . $value. time() .mt_rand() );
 	$new_admin_email = array(
 		'hash' => $hash,
 		'newemail' => $value
@@ -327,7 +327,7 @@ function send_confirmation_on_profile_email() {
 			return;
 		}
 
-		$hash = md5( $_POST['email'] . time() . mt_rand() );
+		$hash = bin2hex(wp_random_bytes(16));
 		$new_user_email = array(
 				'hash' => $hash,
 				'newemail' => $_POST['email']
diff --git a/src/wp-admin/includes/schema.php b/src/wp-admin/includes/schema.php
index c522ab5..2ef1a8a 100644
--- a/src/wp-admin/includes/schema.php
+++ b/src/wp-admin/includes/schema.php
@@ -1023,7 +1023,7 @@ We hope you enjoy your new site. Thanks!
 
 		$vhost_ok = false;
 		$errstr = '';
-		$hostname = substr( md5( time() ), 0, 6 ) . '.' . $domain; // Very random hostname!
+		$hostname = bin2hex( wp_random_bytes(3) ) . '.' . $domain; // Very random hostname!
 		$page = wp_remote_get( 'http://' . $hostname, array( 'timeout' => 5, 'httpversion' => '1.1' ) );
 		if ( is_wp_error( $page ) )
 			$errstr = $page->get_error_message();
diff --git a/src/wp-includes/ms-functions.php b/src/wp-includes/ms-functions.php
index 0e38638..b3d76bc 100644
--- a/src/wp-includes/ms-functions.php
+++ b/src/wp-includes/ms-functions.php
@@ -710,7 +710,7 @@ function wpmu_validate_blog_signup( $blogname, $blog_title, $user = '' ) {
 function wpmu_signup_blog( $domain, $path, $title, $user, $user_email, $meta = array() )  {
 	global $wpdb;
 
-	$key = substr( md5( time() . rand() . $domain ), 0, 16 );
+	$key = bin2hex(wp_random_bytes(8));
 	$meta = serialize($meta);
 
 	$wpdb->insert( $wpdb->signups, array(
@@ -745,7 +745,7 @@ function wpmu_signup_user( $user, $user_email, $meta = array() ) {
 	// Format data
 	$user = preg_replace( '/\s+/', '', sanitize_user( $user, true ) );
 	$user_email = sanitize_email( $user_email );
-	$key = substr( md5( time() . rand() . $user_email ), 0, 16 );
+	$key = bin2hex(wp_random_bytes(8));
 	$meta = serialize($meta);
 
 	$wpdb->insert( $wpdb->signups, array(
diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php
index ebb7001..3a8d930 100644
--- a/src/wp-includes/pluggable.php
+++ b/src/wp-includes/pluggable.php
@@ -2003,7 +2003,7 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci
 
 	$password = '';
 	for ( $i = 0; $i < $length; $i++ ) {
-		$password .= substr($chars, wp_rand(0, strlen($chars) - 1), 1);
+		$password .= substr($chars, wp_secure_rand(0, strlen($chars) - 1), 1);
 	}
 
 	/**
@@ -2016,6 +2016,211 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci
 	return apply_filters( 'random_password', $password );
 }
 endif;
+if ( !function_exists('wp_random_bytes') ) :
+
+/**
+ * Generate a cryptographically secure random string
+ * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/
+ *
+ * @param int $bytes - how many bytes do we want?
+ */
+function wp_random_bytes($bytes = 32) {
+	$buf = '';
+    /**
+     * This is preferable to userspace RNGs (e.g. openssl)
+     * because it reads directly from /dev/urandom on most OS's,
+     * and uses Windows's Crypto APIs transparently.
+     *
+     * Requires the mcrypt extension be installed/enabled.
+     */
+	if (function_exists('mcrypt_create_iv')) {
+		$buf = mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
+		if($buf !== FALSE) {
+			return $buf;
+		}
+	}
+    /**
+     * If /dev/urandom is available, let's prefer it over every other method
+     *
+     * This should only return false on Windows hosts and in chroot jails
+     */
+	if (is_readable('/dev/urandom')) {
+		$fp = fopen('/dev/urandom', 'rb');
+        // Extra cautious, thanks to feedback from Taylor Hornby (https://defuse.ca)
+        $streamset = stream_set_read_buffer($fp, 0);
+		if ($fp !== false && $streamset === 0) {
+            $buf = '';
+            // How many bytes do we need?
+            $remaining = $bytes;
+            do {
+                $read = fread($fp, $remaining);
+                if ($read === FALSE) {
+                    // We cannot safely read from urandom.
+                    $buf = FALSE;
+                    break;
+                }
+                // Decrease the number of bytes returned from remaining
+                $remaining -= wp_binsafe_strlen($read);
+
+                // We actually read bytes! Let's append them to our buffer
+                $buf .= $read;
+            } while ($remaining > 0);
+            // Let's make sure we have enough bytes
+
+			fclose($fp);
+			if ($buf !== FALSE) {
+                return $buf;
+			}
+		}
+	}
+    /**
+     * This is available since PHP 5.3.0 on every PHP install.
+     *
+     * Better than nothing, but not really urandom
+     */
+	if (function_exists('openssl_random_pseudo_bytes')) {
+		$buf = openssl_random_pseudo_bytes($bytes);
+        if ($buf !== false) {
+			return $buf;
+        }
+	}
+
+    /**
+     * Windows with PHP < 5.3.0 will not have the function
+     * openssl_random_pseudo_bytes() available, so let's use
+     * CAPICOM to work around this deficiency.
+     */
+    if (class_exists('\\COM', false)) {
+        try {
+            if ($buf === FALSE) {
+                $buf = ''; // Make it a string, not false
+            }
+            $util = new \COM('CAPICOM.Utilities.1');
+            $execs = 0;
+            /**
+             * Let's not let it loop forever. If we run N times and fail to
+             * get N bytes of entropy, then CAPICOM has failed us.
+             */
+            while ($execs < $bytes) {
+                $buf .= base64_decode($util->GetRandom($bytes, 0));
+                if (wp_binsafe_strlen($buf) >= $bytes) {
+                    return wp_binsafe_substr($buf, 0, $bytes);
+                }
+                ++$execs;
+            }
+        } catch (\Exception($e)) {
+            unset($e); // Let's not let CAPICOM errors kill our app
+        }
+    }
+    if ($buf === FALSE) {
+        $buf = ''; // Make it a string, not false
+    }
+    /**
+     * Okay, at this point, we cannot guarantee a CSPRNG no matter what we
+     * do. Our only recourse is to return something insecure.
+     */
+    for ($i = 0; $i < $bytes; ++$i) {
+        $buf .= chr(wp_rand(0, 255));
+    }
+    return $buf;
+}
+endif;
+
+if ( !function_exists('wp_binsafe_strlen'):
+/**
+ * Binary-safe string length (won't get chewed by multibyte strings)
+ *
+ * @param string $str The string whose length we are retreiving
+ * @return int The string length (binary-safe)
+ */
+function wp_binsafe_strlen($str) {
+    if (function_exists('mb_strlen')) {
+        return mb_strlen($str, '8bit');
+    }
+    return strlen($str);
+}
+endif;
+if ( !function_exists('wp_binsafe_substr'):
+/**
+ * Binary-safe substring (won't get chewed by multibyte strings)
+ *
+ * @param string $str The string whose length we are retreiving
+ * @param int $start     The
+ * @return int The string length (binary-safe)
+ */
+function wp_binsafe_substr($str, $start, $length = null) {
+    if (function_exists('mb_substr')) {
+        return mb_substr($str, $start, $length, '8bit');
+    }
+    return substr($str, $start, $length);
+}
+endif;
+
+
+if ( !function_exists('wp_random_positive_int'):
+/**
+ * Generate a random positive integer between 0 and PHP_INT_MAX
+ *
+ * @return int A random positive integer
+ */
+function wp_random_positive_int() {
+    $buf = wp_random_bytes(PHP_INT_SIZE);
+    if($buf === false) {
+        trigger_error('Random number failure', E_USER_ERROR);
+    }
+
+    $val = 0;
+    $i = strlen($buf);
+
+    do {
+        $i--;
+        $val <<= 8;
+        $val |= ord($buf[$i]);
+    } while ($i != 0);
+
+    return $val & PHP_INT_MAX;
+}
+endif;
+
+if ( !function_exists('wp_secure_rand') ):
+/**
+ * Generates an unbiased, unpredictably random number
+ *
+ * @since x.x.x
+ *
+ * @param int $min Lower limit for the generated number
+ * @param int $max Upper limit for the generated number
+ * @return int A random number between min and max
+ */
+function wp_secure_rand( $min = 0, $max = 0) {
+    $range = $max - $min;
+    if ($range < 2) {
+        return $min;
+    }
+
+    // 7776 -> 13
+    $bits = ceil(log($range)/log(2));
+
+    // 2^13 - 1 == 8191 or 0x00001111 11111111
+    $mask = ceil(pow(2, $bits)) - 1;
+
+    do {
+        // Grab a random integer
+        $val = wp_random_positive_int();
+        if ($val === FALSE) {
+            // RNG failure
+            return FALSE;
+        }
+        // Apply mask
+        $val = $val & $mask;
+        // If $val is larger than the maximum acceptable number for
+        // $min and $max, we discard and try again.
+    } while ($val > $range);
+    return (int) ($min + $val);
+}
+
+endif;
+
 
 if ( !function_exists('wp_rand') ) :
 /**
diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
index 527780a..81783b6 100644
--- a/tests/phpunit/tests/functions.php
+++ b/tests/phpunit/tests/functions.php
@@ -626,4 +626,38 @@ class Tests_Functions extends WP_UnitTestCase {
 		$json = wp_json_encode( $data, 0, 1 );
 		$this->assertFalse( $json );
 	}
+    /**
+     * @ticket 28633
+     */
+    function test_wp_secure_rand() {
+        // Let's load a buffer
+        $buff = array();
+
+        // How size do we want our test array to be?
+
+        //$size = wp_rand(16,256);
+        $size = 16;
+
+        // We should expect
+        $to_run = 10;
+
+        //
+        for($i = 0; $i < $size; ++$i) {
+            $buff[$i] = 0;
+        }
+
+        $tests = $size * $to_run;
+
+        for($i = 0; $i < $tests; ++$i) {
+            ++$buff[wp_secure_rand(0, $size - 1)];
+        }
+
+        // With any luck, all of these should be near $to_run.
+        $passed = 0;
+        for($i = 0; $i < $size; ++$i) {
+            $this->assertNotEquals($buff[$i], 0);
+        }
+
+        // We can do statistical tests later to ensure the output is unbiased.
+    }
 }
-- 
1.9.5.msysgit.0

