Skip to content

Commit

Permalink
Drupal 6.35, 2015-03-18
Browse files Browse the repository at this point in the history
- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001.
  • Loading branch information
kwcoffman committed Apr 6, 2015
1 parent c384661 commit 72096ee
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.txt
@@ -1,4 +1,8 @@

Drupal 6.35, 2015-03-18
----------------------
- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-001.

Drupal 6.34, 2014-11-19
----------------------
- Fixed security issues (session hijacking). See SA-CORE-2014-006.
Expand Down
74 changes: 74 additions & 0 deletions includes/bootstrap.inc
Expand Up @@ -1514,6 +1514,34 @@ function _drupal_bootstrap($phase) {
// Initialize configuration variables, using values from settings.php if available.
$conf = variable_init(isset($conf) ? $conf : array());

// Sanitize the destination parameter (which is often used for redirects)
// to prevent open redirect attacks leading to other domains. Sanitize
// both $_GET['destination'] and $_REQUEST['destination'] to protect code
// that relies on either, but do not sanitize $_POST to avoid interfering
// with unrelated form submissions. $_REQUEST['edit']['destination'] is
// also sanitized since drupal_goto() will sometimes rely on it, and
// other code might therefore use it too. The sanitization happens here
// because menu_path_is_external() requires the variable system to be
// available.
if (isset($_GET['destination']) || isset($_REQUEST['destination']) || isset($_REQUEST['edit']['destination'])) {
require_once './includes/menu.inc';
drupal_load('module', 'filter');
// If the destination is an external URL, remove it.
if (isset($_GET['destination']) && menu_path_is_external($_GET['destination'])) {
unset($_GET['destination']);
unset($_REQUEST['destination']);
}
// If there's still something in $_REQUEST['destination'] that didn't
// come from $_GET, check it too.
if (isset($_REQUEST['destination']) && (!isset($_GET['destination']) || $_REQUEST['destination'] != $_GET['destination']) && menu_path_is_external($_REQUEST['destination'])) {
unset($_REQUEST['destination']);
}
// Check $_REQUEST['edit']['destination'] separately.
if (isset($_REQUEST['edit']['destination']) && menu_path_is_external($_REQUEST['edit']['destination'])) {
unset($_REQUEST['edit']['destination']);
}
}

$cache_mode = variable_get('cache', CACHE_DISABLED);
// Get the page from the cache, unless the cache is disabled or external.
if ($cache_mode != CACHE_DISABLED && $cache_mode != CACHE_EXTERNAL) {
Expand Down Expand Up @@ -2036,3 +2064,49 @@ function drupal_random_bytes($count) {
$bytes = substr($bytes, $count);
return $output;
}

/**
* Calculates a hexadecimal encoded sha-1 hmac.
*
* @param string $data
* String to be validated with the hmac.
* @param string $key
* A secret string key.
*
* See RFC2104 (http://www.ietf.org/rfc/rfc2104.txt). Note, the result of this
* must be identical to using hash_hmac('sha1', $data, $key); We don't use
* that function since PHP can be missing it if it was compiled with the
* --disable-hash switch.
*
* @return string
* A hexadecimal encoded sha-1 hmac.
*/
function drupal_hash_hmac_sha1($data, $key) {
// Keys longer than the 64 byte block size must be hashed first.
if (strlen($key) > 64) {
$key = pack("H*", sha1($key));
}
return sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . pack("H*", sha1((str_pad($key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . $data)));
}

/**
* Calculates a base-64 encoded, URL-safe sha-1 hmac.
*
* @param string $data
* String to be validated with the hmac.
* @param string $key
* A secret string key.
*
* @return string
* A base-64 encoded sha-1 hmac, with + replaced with -, / with _ and
* any = padding characters removed.
*/
function drupal_hmac_base64($data, $key) {
// 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(pack("H*", drupal_hash_hmac_sha1((string) $data, (string) $key)));
// Modify the hmac so it's safe to use in URLs.
return strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
}
33 changes: 26 additions & 7 deletions includes/common.inc
Expand Up @@ -323,7 +323,7 @@ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response
if ($destination) {
// Do not redirect to an absolute URL originating from user input.
$colonpos = strpos($destination, ':');
$absolute = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos)));
$absolute = strpos($destination, '//') === 0 || ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos)));
if (!$absolute) {
extract(parse_url(urldecode($destination)));
}
Expand Down Expand Up @@ -372,7 +372,10 @@ function drupal_not_found() {

// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
// Make sure that the current path is not interpreted as external URL.
if (!menu_path_is_external($_GET['q'])) {
$_REQUEST['destination'] = $_GET['q'];
}
}

$path = drupal_get_normal_path(variable_get('site_404', ''));
Expand Down Expand Up @@ -402,7 +405,10 @@ function drupal_access_denied() {

// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_REQUEST['destination'])) {
$_REQUEST['destination'] = $_GET['q'];
// Make sure that the current path is not interpreted as external URL.
if (!menu_path_is_external($_GET['q'])) {
$_REQUEST['destination'] = $_GET['q'];
}
}

$path = drupal_get_normal_path(variable_get('site_403', ''));
Expand Down Expand Up @@ -1466,12 +1472,20 @@ function url($path = NULL, $options = array()) {
'alias' => FALSE,
'prefix' => ''
);
// A duplicate of the code from menu_path_is_external() to avoid needing
// another function call, since performance inside url() is critical.
if (!isset($options['external'])) {
// Return an external link if $path contains an allowed absolute URL.
// Only call the slow filter_xss_bad_protocol if $path contains a ':' before
// any / ? or #.
// Return an external link if $path contains an allowed absolute URL. Avoid
// calling filter_xss_bad_protocol() if there is any slash (/), hash (#) or
// question_mark (?) before the colon (:) occurrence - if any - as this
// would clearly mean it is not a URL. If the path starts with 2 slashes
// then it is always considered an external URL without an explicit protocol
// part.
$colonpos = strpos($path, ':');
$options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
$options['external'] = (strpos($path, '//') === 0)
|| ($colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
&& filter_xss_bad_protocol($path, FALSE) == check_plain($path));
}

// May need language dependent rewriting if language.inc is present.
Expand Down Expand Up @@ -1501,6 +1515,11 @@ function url($path = NULL, $options = array()) {
return $path . $options['fragment'];
}

// Strip leading slashes from internal paths to prevent them becoming external
// URLs without protocol. /example.com should not be turned into
// //example.com.
$path = ltrim($path, '/');

global $base_url;
static $script;

Expand Down
10 changes: 9 additions & 1 deletion includes/menu.inc
Expand Up @@ -2486,8 +2486,16 @@ function _menu_router_build($callbacks) {
* Returns TRUE if a path is external (e.g. http://example.com).
*/
function menu_path_is_external($path) {
// Avoid calling filter_xss_bad_protocol() if there is any slash (/),
// hash (#) or question_mark (?) before the colon (:) occurrence - if any - as
// this would clearly mean it is not a URL. If the path starts with 2 slashes
// then it is always considered an external URL without an explicit protocol
// part.
$colonpos = strpos($path, ':');
return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path);
return (strpos($path, '//') === 0)
|| ($colonpos !== FALSE
&& !preg_match('![/?#]!', substr($path, 0, $colonpos))
&& filter_xss_bad_protocol($path, FALSE) == check_plain($path));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion modules/system/system.module
Expand Up @@ -8,7 +8,7 @@
/**
* The current system version.
*/
define('VERSION', '6.34');
define('VERSION', '6.35');

/**
* Core API compatibility.
Expand Down
28 changes: 25 additions & 3 deletions modules/user/user.module
Expand Up @@ -1486,11 +1486,33 @@ function user_external_login_register($name, $module) {
*/
function user_pass_reset_url($account) {
$timestamp = time();
return url("user/reset/$account->uid/$timestamp/". user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
return url("user/reset/$account->uid/$timestamp/". user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
}

function user_pass_rehash($password, $timestamp, $login) {
return md5($timestamp . $password . $login);
function user_pass_rehash($password, $timestamp, $login, $uid) {
// Backwards compatibility: Try to determine a $uid if one was not passed.
// (Since $uid is a required parameter to this function, a PHP warning will
// be generated if it's not provided, which is an indication that the calling
// code should be updated. But the code below will try to generate a correct
// hash in the meantime.)
if (!isset($uid)) {
$uids = array();
$result = db_query_range("SELECT uid FROM {users} WHERE pass = '%s' AND login = '%s' AND uid > 0", $password, $login, 0, 2);
while ($row = db_fetch_array($result)) {
$uids[] = $row['uid'];
}
// If exactly one user account matches the provided password and login
// timestamp, proceed with that $uid.
if (count($uids) == 1) {
$uid = reset($uids);
}
// Otherwise there is no safe hash to return, so return a random string
// that will never be treated as a valid token.
else {
return drupal_random_key();
}
}
return drupal_hmac_base64($timestamp . $login . $uid, drupal_get_private_key() . $password);
}

function user_edit_form(&$form_state, $uid, $edit, $register = FALSE) {
Expand Down
2 changes: 1 addition & 1 deletion modules/user/user.pages.inc
Expand Up @@ -106,7 +106,7 @@ function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action =
drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));
drupal_goto('user/password');
}
else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) {
else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) {
// First stage is a confirmation form, then login
if ($action == 'login') {
watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
Expand Down

0 comments on commit 72096ee

Please sign in to comment.