summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
authorDavid Rothstein <drothstein@gmail.com>2013-11-20 15:45:59 -0500
committerDavid Rothstein <drothstein@gmail.com>2013-11-20 15:45:59 -0500
commit782d1155c62c0a879bf587c7e40c3a13bcf6879c (patch)
tree380060c81a7ebd76870cfd7fb566933b3a7c6efd /includes
parentbf704d6ffe55d66a440a55a9d43e8846d46d2440 (diff)
downloadbrdo-782d1155c62c0a879bf587c7e40c3a13bcf6879c.tar.gz
brdo-782d1155c62c0a879bf587c7e40c3a13bcf6879c.tar.bz2
Drupal 7.24
Diffstat (limited to 'includes')
-rw-r--r--includes/bootstrap.inc105
-rw-r--r--includes/common.inc8
-rw-r--r--includes/file.inc55
-rw-r--r--includes/form.inc10
-rw-r--r--includes/install.core.inc11
-rw-r--r--includes/session.inc8
6 files changed, 146 insertions, 51 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index d27f8d14f..0bd4bcc1e 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
-define('VERSION', '7.23');
+define('VERSION', '7.24');
/**
* Core API compatibility.
@@ -1933,6 +1933,33 @@ function drupal_block_denied($ip) {
}
/**
+ * Returns a URL-safe, base64 encoded string of highly randomized bytes (over the full 8-bit range).
+ *
+ * @param $byte_count
+ * The number of random bytes to fetch and base64 encode.
+ *
+ * @return string
+ * The base64 encoded result will have a length of up to 4 * $byte_count.
+ */
+function drupal_random_key($byte_count = 32) {
+ return drupal_base64_encode(drupal_random_bytes($byte_count));
+}
+
+/**
+ * Returns a URL-safe, base64 encoded version of the supplied string.
+ *
+ * @param $string
+ * The string to convert to base64.
+ *
+ * @return string
+ */
+function drupal_base64_encode($string) {
+ $data = base64_encode($string);
+ // Modify the output so it's safe to use in URLs.
+ return strtr($data, array('+' => '-', '/' => '_', '=' => ''));
+}
+
+/**
* Returns a string of highly randomized bytes (over the full 8-bit range).
*
* This function is better than simply calling mt_rand() or any other built-in
@@ -1945,38 +1972,34 @@ function drupal_block_denied($ip) {
*/
function drupal_random_bytes($count) {
// $random_state does not use drupal_static as it stores random bytes.
- static $random_state, $bytes, $php_compatible;
- // Initialize on the first call. The contents of $_SERVER includes a mix of
- // user-specific and system information that varies a little with each page.
- if (!isset($random_state)) {
- $random_state = print_r($_SERVER, TRUE);
- if (function_exists('getmypid')) {
- // Further initialize with the somewhat random PHP process ID.
- $random_state .= getmypid();
- }
- $bytes = '';
- }
- if (strlen($bytes) < $count) {
+ static $random_state, $bytes, $has_openssl;
+
+ $missing_bytes = $count - strlen($bytes);
+
+ if ($missing_bytes > 0) {
// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
// locking on Windows and rendered it unusable.
- if (!isset($php_compatible)) {
- $php_compatible = version_compare(PHP_VERSION, '5.3.4', '>=');
+ if (!isset($has_openssl)) {
+ $has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
}
- // /dev/urandom is available on many *nix systems and is considered the
- // best commonly available pseudo-random source.
- if ($fh = @fopen('/dev/urandom', 'rb')) {
+
+ // openssl_random_pseudo_bytes() will find entropy in a system-dependent
+ // way.
+ if ($has_openssl) {
+ $bytes .= openssl_random_pseudo_bytes($missing_bytes);
+ }
+
+ // Else, read directly from /dev/urandom, which is available on many *nix
+ // systems and is considered cryptographically secure.
+ elseif ($fh = @fopen('/dev/urandom', 'rb')) {
// PHP only performs buffered reads, so in reality it will always read
// at least 4096 bytes. Thus, it costs nothing extra to read and store
// that much so as to speed any additional invocations.
- $bytes .= fread($fh, max(4096, $count));
+ $bytes .= fread($fh, max(4096, $missing_bytes));
fclose($fh);
}
- // openssl_random_pseudo_bytes() will find entropy in a system-dependent
- // way.
- elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) {
- $bytes .= openssl_random_pseudo_bytes($count - strlen($bytes));
- }
- // If /dev/urandom is not available or returns no bytes, this loop will
+
+ // If we couldn't get enough entropy, this simple hash-based PRNG will
// generate a good set of pseudo-random bytes on any system.
// Note that it may be important that our $random_state is passed
// through hash() prior to being rolled into $output, that the two hash()
@@ -1984,9 +2007,23 @@ function drupal_random_bytes($count) {
// the microtime() - is prepended rather than appended. This is to avoid
// directly leaking $random_state via the $output stream, which could
// allow for trivial prediction of further "random" numbers.
- while (strlen($bytes) < $count) {
- $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
- $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+ if (strlen($bytes) < $count) {
+ // Initialize on the first call. The contents of $_SERVER includes a mix of
+ // user-specific and system information that varies a little with each page.
+ if (!isset($random_state)) {
+ $random_state = print_r($_SERVER, TRUE);
+ if (function_exists('getmypid')) {
+ // Further initialize with the somewhat random PHP process ID.
+ $random_state .= getmypid();
+ }
+ $bytes = '';
+ }
+
+ do {
+ $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+ $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+ }
+ while (strlen($bytes) < $count);
}
}
$output = substr($bytes, 0, $count);
@@ -1997,17 +2034,21 @@ function drupal_random_bytes($count) {
/**
* Calculates a base-64 encoded, URL-safe sha-256 hmac.
*
- * @param $data
+ * @param string $data
* String to be validated with the hmac.
- * @param $key
+ * @param string $key
* A secret string key.
*
- * @return
+ * @return string
* A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and
* any = padding characters removed.
*/
function drupal_hmac_base64($data, $key) {
- $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE));
+ // Casting $data and $key to strings here is necessary to avoid empty string
+ // results of the hash function if they are not scalar values. As this
+ // function is used in security-critical contexts like token validation it is
+ // important that it never returns an empty string.
+ $hmac = base64_encode(hash_hmac('sha256', (string) $data, (string) $key, TRUE));
// Modify the hmac so it's safe to use in URLs.
return strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
}
diff --git a/includes/common.inc b/includes/common.inc
index 262e1c57b..0ab9c39e2 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -5042,7 +5042,7 @@ function drupal_json_output($var = NULL) {
*/
function drupal_get_private_key() {
if (!($key = variable_get('drupal_private_key', 0))) {
- $key = drupal_hash_base64(drupal_random_bytes(55));
+ $key = drupal_random_key();
variable_set('drupal_private_key', $key);
}
return $key;
@@ -5081,7 +5081,7 @@ function drupal_get_token($value = '') {
*/
function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
global $user;
- return (($skip_anonymous && $user->uid == 0) || ($token == drupal_get_token($value)));
+ return (($skip_anonymous && $user->uid == 0) || ($token === drupal_get_token($value)));
}
function _drupal_bootstrap_full() {
@@ -5114,6 +5114,10 @@ function _drupal_bootstrap_full() {
module_load_all();
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
+ // Ensure mt_rand is reseeded, to prevent random values from one page load
+ // being exploited to predict random values in subsequent page loads.
+ $seed = unpack("L", drupal_random_bytes(4));
+ mt_srand($seed[1]);
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site'])) {
diff --git a/includes/file.inc b/includes/file.inc
index 06657cfbe..d52d02915 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -470,8 +470,11 @@ function file_ensure_htaccess() {
* @param $private
* FALSE indicates that $directory should be an open and public directory.
* The default is TRUE which indicates a private and protected directory.
+ * @param $force_overwrite
+ * Set to TRUE to attempt to overwrite the existing .htaccess file if one is
+ * already present. Defaults to FALSE.
*/
-function file_create_htaccess($directory, $private = TRUE) {
+function file_create_htaccess($directory, $private = TRUE, $force_overwrite = FALSE) {
if (file_uri_scheme($directory)) {
$directory = file_stream_wrapper_uri_normalize($directory);
}
@@ -480,19 +483,12 @@ function file_create_htaccess($directory, $private = TRUE) {
}
$htaccess_path = $directory . '/.htaccess';
- if (file_exists($htaccess_path)) {
+ if (file_exists($htaccess_path) && !$force_overwrite) {
// Short circuit if the .htaccess file already exists.
return;
}
- if ($private) {
- // Private .htaccess file.
- $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nDeny from all\nOptions None\nOptions +FollowSymLinks";
- }
- else {
- // Public .htaccess file.
- $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
- }
+ $htaccess_lines = file_htaccess_lines($private);
// Write the .htaccess file.
if (file_put_contents($htaccess_path, $htaccess_lines)) {
@@ -505,6 +501,45 @@ function file_create_htaccess($directory, $private = TRUE) {
}
/**
+ * Returns the standard .htaccess lines that Drupal writes to file directories.
+ *
+ * @param $private
+ * (Optional) Set to FALSE to return the .htaccess lines for an open and
+ * public directory. The default is TRUE, which returns the .htaccess lines
+ * for a private and protected directory.
+ *
+ * @return
+ * A string representing the desired contents of the .htaccess file.
+ *
+ * @see file_create_htaccess()
+ */
+function file_htaccess_lines($private = TRUE) {
+ $lines = <<<EOF
+# Turn off all options we don't need.
+Options None
+Options +FollowSymLinks
+
+# Set the catch-all handler to prevent scripts from being executed.
+SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
+<Files *>
+ # Override the handler again if we're run later in the evaluation list.
+ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003
+</Files>
+
+# If we know how to do it safely, disable the PHP engine entirely.
+<IfModule mod_php5.c>
+ php_flag engine off
+</IfModule>
+EOF;
+
+ if ($private) {
+ $lines = "Deny from all\n\n" . $lines;
+ }
+
+ return $lines;
+}
+
+/**
* Loads file objects from the database.
*
* @param $fids
diff --git a/includes/form.inc b/includes/form.inc
index 8ca048a90..fcfc79653 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -462,7 +462,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
$form['#build_id'] = $old_form['#build_id'];
}
else {
- $form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand());
+ $form['#build_id'] = 'form-' . drupal_random_key();
}
// #action defaults to request_uri(), but in case of Ajax and other partial
@@ -977,7 +977,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
// @see drupal_build_form()
// @see drupal_rebuild_form()
if (!isset($form['#build_id'])) {
- $form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand());
+ $form['#build_id'] = 'form-' . drupal_random_key();
}
$form['form_build_id'] = array(
'#type' => 'hidden',
@@ -1129,6 +1129,12 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+
+ // Stop here and don't run any further validation handlers, because they
+ // could invoke non-safe operations which opens the door for CSRF
+ // vulnerabilities.
+ $validated_forms[$form_id] = TRUE;
+ return;
}
}
diff --git a/includes/install.core.inc b/includes/install.core.inc
index 7a694e9bb..83f18735a 100644
--- a/includes/install.core.inc
+++ b/includes/install.core.inc
@@ -766,6 +766,15 @@ function install_system_module(&$install_state) {
// Install system.module.
drupal_install_system();
+ // Call file_ensure_htaccess() to ensure that all of Drupal's standard
+ // directories (e.g., the public and private files directories) have
+ // appropriate .htaccess files. These directories will have already been
+ // created by this point in the installer, since Drupal creates them during
+ // the install_verify_requirements() task. Note that we cannot call
+ // file_ensure_htaccess() any earlier than this, since it relies on
+ // system.module in order to work.
+ file_ensure_htaccess();
+
// Enable the user module so that sessions can be recorded during the
// upcoming bootstrap step.
module_enable(array('user'), FALSE);
@@ -981,7 +990,7 @@ function install_settings_form_submit($form, &$form_state) {
'required' => TRUE,
);
$settings['drupal_hash_salt'] = array(
- 'value' => drupal_hash_base64(drupal_random_bytes(55)),
+ 'value' => drupal_random_key(),
'required' => TRUE,
);
drupal_rewrite_settings($settings);
diff --git a/includes/session.inc b/includes/session.inc
index 16727df6d..9589e06fc 100644
--- a/includes/session.inc
+++ b/includes/session.inc
@@ -263,10 +263,10 @@ function drupal_session_initialize() {
// Less random sessions (which are much faster to generate) are used for
// anonymous users than are generated in drupal_session_regenerate() when
// a user becomes authenticated.
- session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE)));
+ session_id(drupal_random_key());
if ($is_https && variable_get('https', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
- $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE));
+ $session_id = drupal_random_key();
$_COOKIE[$insecure_session_name] = $session_id;
}
}
@@ -360,7 +360,7 @@ function drupal_session_regenerate() {
$old_insecure_session_id = $_COOKIE[$insecure_session_name];
}
$params = session_get_cookie_params();
- $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55));
+ $session_id = drupal_random_key();
// If a session cookie lifetime is set, the session will expire
// $params['lifetime'] seconds from the current request. If it is not set,
// it will expire when the browser is closed.
@@ -372,7 +372,7 @@ function drupal_session_regenerate() {
if (drupal_session_started()) {
$old_session_id = session_id();
}
- session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)));
+ session_id(drupal_random_key());
if (isset($old_session_id)) {
$params = session_get_cookie_params();