From fa13d0b974f313394c8f538d2a75b882cbbe26c1 Mon Sep 17 00:00:00 2001
From: Scott Arciszewski <scott@resonantcore.net>
Date: Thu, 02 Feb 2014 11:09:01 -0500
Subject: [PATCH 1/1] Patch 28633 redux -- we now have a CSPRNG for PHP 5.2.x
 on Windows systems.

---
 src/wp-admin/includes/ms.php      |   4 +-
 src/wp-admin/includes/schema.php  |   2 +-
 src/wp-includes/functions.php     |   4 +-
 src/wp-includes/ms-functions.php  |   4 +-
 src/wp-includes/pluggable.php     | 142 +++++++++++++++++++++++++++++++++++++-
 tests/phpunit/tests/functions.php |  35 ++++++++++
 6 files changed, 183 insertions(+), 8 deletions(-)

diff --git a/src/wp-admin/includes/ms.php b/src/wp-admin/includes/ms.php
index a9c62bb..39f2243 100644
--- a/src/wp-admin/includes/ms.php
+++ b/src/wp-admin/includes/ms.php
@@ -245,7 +245,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
@@ -323,7 +323,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 296a699..db0b2e6 100644
--- a/src/wp-admin/includes/schema.php
+++ b/src/wp-admin/includes/schema.php
@@ -1016,7 +1016,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/functions.php b/src/wp-includes/functions.php
index 76013c9..12393ba 100644
--- a/src/wp-includes/functions.php
+++ b/src/wp-includes/functions.php
@@ -1668,9 +1668,9 @@ function wp_is_writable( $path ) {
 function win_is_writable( $path ) {
 
 	if ( $path[strlen( $path ) - 1] == '/' ) // if it looks like a directory, check a random file within the directory
-		return win_is_writable( $path . uniqid( mt_rand() ) . '.tmp');
+		return win_is_writable( $path . uniqid( bin2hex(wp_random_bytes(16)) ) . '.tmp');
 	else if ( is_dir( $path ) ) // If it's a directory (and not a file) check a random file within the directory
-		return win_is_writable( $path . '/' . uniqid( mt_rand() ) . '.tmp' );
+		return win_is_writable( $path . '/' . uniqid( bin2hex(wp_random_bytes(16)) ) . '.tmp' );
 
 	// check tmp file for read/write capabilities
 	$should_delete_tmp_file = !file_exists( $path );
diff --git a/src/wp-includes/ms-functions.php b/src/wp-includes/ms-functions.php
index 5537fb3..82c2023 100644
--- a/src/wp-includes/ms-functions.php
+++ b/src/wp-includes/ms-functions.php
@@ -713,7 +713,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(
@@ -748,7 +748,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 8f3a5de..d3daf21 100644
--- a/src/wp-includes/pluggable.php
+++ b/src/wp-includes/pluggable.php
@@ -1991,7 +1991,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);
 	}
 
 	/**
@@ -2005,6 +2005,145 @@ function wp_generate_password( $length = 12, $special_chars = true, $extra_speci
 }
 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 = '';
+    /**
+     * 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');
+       stream_set_read_buffer($fp, 0);
+		if ($fp !== false) {
+			$buf = fread($fp, $bytes);
+			fclose($fp);
+			if ($buf !== FALSE) {
+				return $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;
+		}
+	}
+    /**
+     * This is available since PHP 5.3.0 on every PHP install.
+     */
+	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.
+     *
+     * When we stop supporting unsupported versions of PHP,
+     * (i.e. less than 5.5) this should be easily removable.
+     */
+    if (class_exists('\\COM', false)) {
+        try {
+            $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 (strlen($buf) > $bytes) {
+                    return substr($buf, 0, $bytes);
+                }
+            }
+        } catch (\Exception($e)) {
+            unset($e); // Let's not let CAPICOM errors kill our app
+        }
+    }
+    /**
+     * 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_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;
+    }
+    $rem = (PHP_INT_MAX - $range + 1) % $range;
+    do {
+        $val = wp_random_positive_int();
+        if ($val === false) {
+            // RNG failure
+            return false;
+        }
+    } while ($val < $rem);
+    return (int) ($min + $val % $range);
+}
+
+endif;
+
 if ( !function_exists('wp_rand') ) :
 /**
  * Generates a random number
diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php
index 1edd143..2f4f5ad 100644
--- a/tests/phpunit/tests/functions.php
+++ b/tests/phpunit/tests/functions.php
@@ -611,4 +611,39 @@ 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.1

