- Patch #575280 by mfb, carlos8f, chx, bleen18: impersonation when an https session exists.
parent
5f550ab80c
commit
e920fe34ef
|
@ -2134,15 +2134,7 @@ function _drupal_bootstrap_database() {
|
|||
// The user agent header is used to pass a database prefix in the request when
|
||||
// running tests. However, for security reasons, it is imperative that we
|
||||
// validate we ourselves made the request.
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
|
||||
if (!drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
// The first part of the user agent is the prefix itself.
|
||||
$test_prefix = $matches[1];
|
||||
|
||||
if ($test_prefix = drupal_valid_test_ua()) {
|
||||
// Set the test run id for use in other parts of Drupal.
|
||||
$test_info = &$GLOBALS['drupal_test_info'];
|
||||
$test_info['test_run_id'] = $test_prefix;
|
||||
|
@ -2221,22 +2213,40 @@ function drupal_get_bootstrap_phase() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validate the HMAC and timestamp of a user agent header from simpletest.
|
||||
* Checks the current User-Agent string to see if this is an internal request
|
||||
* from SimpleTest. If so, returns the test prefix for this test.
|
||||
*
|
||||
* @return
|
||||
* Either the simpletest prefix (the string "simpletest" followed by any
|
||||
* number of digits) or FALSE if the user agent does not contain a valid
|
||||
* HMAC and timestamp.
|
||||
*/
|
||||
function drupal_valid_test_ua($user_agent) {
|
||||
function drupal_valid_test_ua() {
|
||||
global $drupal_hash_salt;
|
||||
// No reason to reset this.
|
||||
static $test_prefix;
|
||||
|
||||
list($prefix, $time, $salt, $hmac) = explode(';', $user_agent);
|
||||
$check_string = $prefix . ';' . $time . ';' . $salt;
|
||||
// We use the salt from settings.php to make the HMAC key, since
|
||||
// the database is not yet initialized and we can't access any Drupal variables.
|
||||
// The file properties add more entropy not easily accessible to others.
|
||||
$filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
$key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath);
|
||||
$time_diff = REQUEST_TIME - $time;
|
||||
// Since we are making a local request a 5 second time window is allowed,
|
||||
// and the HMAC must match.
|
||||
return ($time_diff >= 0) && ($time_diff <= 5) && ($hmac == drupal_hmac_base64($check_string, $key));
|
||||
if (isset($test_prefix)) {
|
||||
return $test_prefix;
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
|
||||
list(, $prefix, $time, $salt, $hmac) = $matches;
|
||||
$check_string = $prefix . ';' . $time . ';' . $salt;
|
||||
// We use the salt from settings.php to make the HMAC key, since
|
||||
// the database is not yet initialized and we can't access any Drupal variables.
|
||||
// The file properties add more entropy not easily accessible to others.
|
||||
$key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
|
||||
$time_diff = REQUEST_TIME - $time;
|
||||
// Since we are making a local request a 5 second time window is allowed,
|
||||
// and the HMAC must match.
|
||||
if ($time_diff >= 0 && $time_diff <= 5 && $hmac == drupal_hmac_base64($check_string, $key)) {
|
||||
$test_prefix = $prefix;
|
||||
return $test_prefix;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2250,13 +2260,12 @@ function drupal_generate_test_ua($prefix) {
|
|||
// We use the salt from settings.php to make the HMAC key, since
|
||||
// the database is not yet initialized and we can't access any Drupal variables.
|
||||
// The file properties add more entropy not easily accessible to others.
|
||||
$filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
$key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath);
|
||||
$key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
|
||||
}
|
||||
// Generate a moderately secure HMAC based on the database credentials.
|
||||
$salt = uniqid('', TRUE);
|
||||
$check_string = $prefix . ';' . time() . ';' . $salt;
|
||||
return $check_string . ';' . drupal_hmac_base64($check_string, $key);
|
||||
// Generate a moderately secure HMAC based on the database credentials.
|
||||
$salt = uniqid('', TRUE);
|
||||
$check_string = $prefix . ';' . time() . ';' . $salt;
|
||||
return $check_string . ';' . drupal_hmac_base64($check_string, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -88,7 +88,10 @@ function _drupal_session_read($sid) {
|
|||
// a HTTPS session or we are about to log in so we check the sessions table
|
||||
// for an anonymous session with the non-HTTPS-only cookie.
|
||||
if ($is_https) {
|
||||
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject();
|
||||
// Ensure that an empty secure session ID cannot be selected.
|
||||
if ($sid) {
|
||||
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject();
|
||||
}
|
||||
if (!$user) {
|
||||
if (isset($_COOKIE[$insecure_session_name])) {
|
||||
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array(
|
||||
|
@ -180,21 +183,23 @@ function _drupal_session_write($sid, $value) {
|
|||
'timestamp' => REQUEST_TIME,
|
||||
);
|
||||
|
||||
// The "secure pages" setting allows a site to simultaneously use both
|
||||
// secure and insecure session cookies. If enabled and both cookies are
|
||||
// presented then use both keys. If not enabled but on HTTPS then use the
|
||||
// PHP session id as 'ssid'. If on HTTP then use the PHP session id as
|
||||
// 'sid'.
|
||||
// Use the session ID as 'sid' and an empty string as 'ssid' by default.
|
||||
// _drupal_session_read() does not allow empty strings so that's a safe
|
||||
// default.
|
||||
$key = array('sid' => $sid, 'ssid' => '');
|
||||
// On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
|
||||
if ($is_https) {
|
||||
$key['ssid'] = $sid;
|
||||
$insecure_session_name = substr(session_name(), 1);
|
||||
if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
|
||||
$key['sid'] = $_COOKIE[$insecure_session_name];
|
||||
// The "secure pages" setting allows a site to simultaneously use both
|
||||
// secure and insecure session cookies. If enabled and both cookies are
|
||||
// presented then use both keys.
|
||||
if (variable_get('https', FALSE)) {
|
||||
$insecure_session_name = substr(session_name(), 1);
|
||||
if (isset($_COOKIE[$insecure_session_name])) {
|
||||
$key['sid'] = $_COOKIE[$insecure_session_name];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$key['sid'] = $sid;
|
||||
}
|
||||
|
||||
db_merge('sessions')
|
||||
->key($key)
|
||||
|
|
|
@ -701,7 +701,7 @@ function update_fix_d7_requirements() {
|
|||
}
|
||||
|
||||
// Add ssid column and index.
|
||||
db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by PHP's Session API.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
|
||||
db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by Drupal's session handlers.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
|
||||
db_add_index('sessions', 'ssid', array('ssid'));
|
||||
// Drop existing primary key.
|
||||
db_drop_primary_key('sessions');
|
||||
|
|
|
@ -77,6 +77,43 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test validation of the User-Agent header we use to perform test requests.
|
||||
*/
|
||||
function testUserAgentValidation() {
|
||||
if (!$this->inCURL()) {
|
||||
global $base_url;
|
||||
$simpletest_path = $base_url . '/' . drupal_get_path('module', 'simpletest');
|
||||
$HTTP_path = $simpletest_path .'/tests/http.php?q=node';
|
||||
$https_path = $simpletest_path .'/tests/https.php?q=node';
|
||||
// Generate a valid simpletest User-Agent to pass validation.
|
||||
$this->assertTrue(preg_match('/simpletest\d+/', $this->databasePrefix, $matches), t('Database prefix contains simpletest prefix.'));
|
||||
$test_ua = drupal_generate_test_ua($matches[0]);
|
||||
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua);
|
||||
|
||||
// Test pages only available for testing.
|
||||
$this->drupalGet($HTTP_path);
|
||||
$this->assertResponse(200, t('Requesting http.php with a legitimate simpletest User-Agent returns OK.'));
|
||||
$this->drupalGet($https_path);
|
||||
$this->assertResponse(200, t('Requesting https.php with a legitimate simpletest User-Agent returns OK.'));
|
||||
|
||||
// Now slightly modify the HMAC on the header, which should not validate.
|
||||
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua . 'X');
|
||||
$this->drupalGet($HTTP_path);
|
||||
$this->assertResponse(403, t('Requesting http.php with a bad simpletest User-Agent fails.'));
|
||||
$this->drupalGet($https_path);
|
||||
$this->assertResponse(403, t('Requesting https.php with a bad simpletest User-Agent fails.'));
|
||||
|
||||
// Use a real User-Agent and verify that the special files http.php and
|
||||
// https.php can't be accessed.
|
||||
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12');
|
||||
$this->drupalGet($HTTP_path);
|
||||
$this->assertResponse(403, t('Requesting http.php with a normal User-Agent fails.'));
|
||||
$this->drupalGet($https_path);
|
||||
$this->assertResponse(403, t('Requesting https.php with a normal User-Agent fails.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that tests selected through the web interface are run and
|
||||
* that the results are displayed correctly.
|
||||
|
@ -274,10 +311,7 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase {
|
|||
* Check if the test is being run from inside a CURL request.
|
||||
*/
|
||||
function inCURL() {
|
||||
// We cannot rely on drupal_static('drupal_test_info') here, because
|
||||
// 'in_child_site' would be FALSE for the parent site when we are
|
||||
// executing the tests. Default to direct detection of the HTTP headers.
|
||||
return isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']);
|
||||
return (bool) drupal_valid_test_ua();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
// $Id$
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Fake an HTTP request, for use during testing.
|
||||
*/
|
||||
|
||||
// Set a global variable to indicate a mock HTTP request.
|
||||
$is_http_mock = !empty($_SERVER['HTTPS']);
|
||||
|
||||
// Change to HTTP.
|
||||
$_SERVER['HTTPS'] = NULL;
|
||||
ini_set('session.cookie_secure', FALSE);
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
$_SERVER[$key] = str_replace('modules/simpletest/tests/http.php', 'index.php', $value);
|
||||
$_SERVER[$key] = str_replace('https://', 'http://', $_SERVER[$key]);
|
||||
}
|
||||
|
||||
// Change current directory to the Drupal root.
|
||||
chdir('../../..');
|
||||
define('DRUPAL_ROOT', getcwd());
|
||||
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
|
||||
// Make sure this file can only be used by simpletest.
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
|
||||
if (!drupal_valid_test_ua()) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
|
||||
menu_execute_active_handler();
|
|
@ -6,23 +6,27 @@
|
|||
* Fake an https request, for use during testing.
|
||||
*/
|
||||
|
||||
// Negated copy of the condition in _drupal_bootstrap(). If the user agent is
|
||||
// not from simpletest then disallow access.
|
||||
if (!(isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE))) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Set a global variable to indicate a mock HTTPS request.
|
||||
$is_https_mock = empty($_SERVER['HTTPS']);
|
||||
|
||||
// Change to https.
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
|
||||
// Change to index.php.
|
||||
chdir('../../..');
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
$_SERVER[$key] = str_replace('modules/simpletest/tests/https.php', 'index.php', $value);
|
||||
$_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]);
|
||||
}
|
||||
|
||||
require_once 'index.php';
|
||||
// Change current directory to the Drupal root.
|
||||
chdir('../../..');
|
||||
define('DRUPAL_ROOT', getcwd());
|
||||
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
|
||||
|
||||
// Make sure this file can only be used by simpletest.
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
|
||||
if (!drupal_valid_test_ua()) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
|
||||
menu_execute_active_handler();
|
||||
|
|
|
@ -316,7 +316,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase {
|
|||
// Check insecure cookie is not set.
|
||||
$this->assertFalse(isset($this->cookies[$insecure_session_name]));
|
||||
$ssid = $this->cookies[$secure_session_name]['value'];
|
||||
$this->assertSessionIds('', $ssid, 'Session has NULL for SID and a correct secure SID.');
|
||||
$this->assertSessionIds($ssid, $ssid, 'Session has a non-empty SID and a correct secure SID.');
|
||||
$cookie = $secure_session_name . '=' . $ssid;
|
||||
|
||||
// Verify that user is logged in on secure URL.
|
||||
|
@ -326,12 +326,36 @@ class SessionHttpsTestCase extends DrupalWebTestCase {
|
|||
$this->assertResponse(200);
|
||||
|
||||
// Verify that user is not logged in on non-secure URL.
|
||||
if (!$is_https) {
|
||||
$this->curlClose();
|
||||
$this->drupalGet('admin/config', array(), array('Cookie: ' . $cookie));
|
||||
$this->assertNoText(t('Configuration'));
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
$this->curlClose();
|
||||
$this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie));
|
||||
$this->assertNoText(t('Configuration'));
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Verify that empty SID cannot be used on the non-secure site.
|
||||
$this->curlClose();
|
||||
$cookie = $insecure_session_name . '=';
|
||||
$this->drupalGet($this->httpUrl('admin/config'), array(), array('Cookie: ' . $cookie));
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Test HTTP session handling by altering the form action to submit the
|
||||
// login form through http.php, which creates a mock HTTP request on HTTPS
|
||||
// test environments.
|
||||
$this->curlClose();
|
||||
$this->drupalGet('user');
|
||||
$form = $this->xpath('//form[@id="user-login"]');
|
||||
$form[0]['action'] = $this->httpUrl('user');
|
||||
$edit = array('name' => $user->name, 'pass' => $user->pass_raw);
|
||||
$this->drupalPost(NULL, $edit, t('Log in'));
|
||||
$this->drupalGet($this->httpUrl('admin/config'));
|
||||
$this->assertResponse(200);
|
||||
$sid = $this->cookies[$insecure_session_name]['value'];
|
||||
$this->assertSessionIds($sid, '', 'Session has the correct SID and an empty secure SID.');
|
||||
|
||||
// Verify that empty secure SID cannot be used on the secure site.
|
||||
$this->curlClose();
|
||||
$cookie = $secure_session_name . '=';
|
||||
$this->drupalGet($this->httpsUrl('admin/config'), array(), array('Cookie: ' . $cookie));
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Clear browser cookie jar.
|
||||
$this->cookies = array();
|
||||
|
@ -458,9 +482,32 @@ class SessionHttpsTestCase extends DrupalWebTestCase {
|
|||
return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), $assertion_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URL for submitting a mock HTTPS request to HTTP test environments.
|
||||
*
|
||||
* @param $url
|
||||
* A Drupal path such as 'user'.
|
||||
*
|
||||
* @return
|
||||
* An absolute URL.
|
||||
*/
|
||||
protected function httpsUrl($url) {
|
||||
global $base_url;
|
||||
return $base_url . '/modules/simpletest/tests/https.php?q=' . $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URL for submitting a mock HTTP request to HTTPS test environments.
|
||||
*
|
||||
* @param $url
|
||||
* A Drupal path such as 'user'.
|
||||
*
|
||||
* @return
|
||||
* An absolute URL.
|
||||
*/
|
||||
protected function httpUrl($url) {
|
||||
global $base_url;
|
||||
return $base_url . '/modules/simpletest/tests/http.php?q=' . $url;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,12 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
|
|||
|
||||
// Force our way into the session of the child site.
|
||||
drupal_save_session(TRUE);
|
||||
// A session cannot be written without the ssid column which is missing on
|
||||
// Drupal 6 sites.
|
||||
db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by Drupal's session handlers.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''));
|
||||
_drupal_session_write($sid, '');
|
||||
// Remove the temporarily added ssid column.
|
||||
db_drop_field('sessions', 'ssid');
|
||||
drupal_save_session(FALSE);
|
||||
|
||||
// Restore necessary variables.
|
||||
|
|
|
@ -1469,14 +1469,13 @@ function system_schema() {
|
|||
'not null' => TRUE,
|
||||
),
|
||||
'sid' => array(
|
||||
'description' => "A session ID. The value is generated by PHP's Session API.",
|
||||
'description' => "A session ID. The value is generated by Drupal's session handlers.",
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'ssid' => array(
|
||||
'description' => "Secure session ID. The value is generated by PHP's Session API.",
|
||||
'description' => "Secure session ID. The value is generated by Drupal's session handlers.",
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
|
@ -2901,6 +2900,19 @@ function system_update_7064() {
|
|||
db_drop_field('menu_router', 'block_callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default value for sid.
|
||||
*/
|
||||
function system_update_7065() {
|
||||
$spec = array(
|
||||
'description' => "A session ID. The value is generated by Drupal's session handlers.",
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
);
|
||||
db_change_field('sessions', 'sid', 'sid', $spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup updates-6.x-to-7.x"
|
||||
* The next series of updates should start at 8000.
|
||||
|
|
Loading…
Reference in New Issue