Commit 3a29ee48 authored by Dries's avatar Dries

- Patch #3518404 by bopombatower: lock down DB config based on simpletest UA headers.

parent 4ddecc05
......@@ -1343,6 +1343,13 @@ function _drupal_bootstrap($phase) {
break;
case 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']) && (strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) && !drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
// Initialize the database system. Note that the connection
// won't be initialized until it is actually requested.
require_once DRUPAL_ROOT . '/includes/database/database.inc';
......@@ -1428,6 +1435,45 @@ function _drupal_bootstrap($phase) {
}
}
/**
* Validate the HMAC and timestamp of a user agent header from simpletest.
*/
function drupal_valid_test_ua($user_agent) {
global $databases;
list($prefix, $time, $salt, $hmac) = explode(';', $user_agent);
$check_string = $prefix . ';' . $time . ';' . $salt;
// We use the database credentials 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 = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
$time_diff = REQUEST_TIME - $time;
// Since we are making a local request, a 2 second time window is allowed,
// and the HMAC must match.
return (($time_diff >= 0) && ($time_diff < 3) && ($hmac == base64_encode(hash_hmac('sha1', $check_string, $key, TRUE))));
}
/**
* Generate a user agent string with a HMAC and timestamp for simpletest.
*/
function drupal_generate_test_ua($prefix) {
global $databases;
static $key;
if (!isset($key)) {
// We use the database credentials to make the HMAC key, since we
// check the HMAC before the database is initialized. filectime()
// and fileinode() are not easily determined from remote.
$filepath = DRUPAL_ROOT . '/includes/bootstrap.inc';
$key = sha1(serialize($databases) . filectime($filepath) . fileinode($filepath), TRUE);
}
// Generate a moderately secure HMAC based on the database credentials.
$salt = uniqid('', TRUE);
$check_string = $prefix . ';' . time() . ';' . $salt;
return $check_string . ';' . base64_encode(hash_hmac('sha1', $check_string, $key, TRUE));
}
/**
* Enables use of the theme system without requiring database access.
*
......
......@@ -552,8 +552,8 @@ function drupal_http_request($url, array $options = array()) {
// user-agent is used to ensure that multiple testing sessions running at the
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
if (is_string($db_prefix) && preg_match("/^simpletest\d+/", $db_prefix, $matches)) {
$options['headers']['User-Agent'] = $matches[0];
if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
$options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
}
$request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
......@@ -809,7 +809,7 @@ function _drupal_log_error($error, $fatal = FALSE) {
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
......
......@@ -1347,9 +1347,10 @@ public static function addConnectionInfo($key, $target, $info) {
}
// We need to pass around the simpletest database prefix in the request
// and we put that in the user_agent header.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
$db_prefix .= $_SERVER['HTTP_USER_AGENT'];
// and we put that in the user_agent header. The header HMAC was already
// validated in bootstrap.inc.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
$db_prefix .= $matches[1];
}
return $new_connection;
}
......
......@@ -28,7 +28,7 @@ function install_main() {
// 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 no
// installation be permitted using such a prefix.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) {
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
exit;
}
......
......@@ -1194,6 +1194,7 @@ protected function tearDown() {
*/
protected function curlInitialize() {
global $base_url, $db_prefix;
if (!isset($this->curlHandle)) {
$this->curlHandle = curl_init();
$curl_options = $this->additionalCurlOptions + array(
......@@ -1206,9 +1207,6 @@ protected function curlInitialize() {
CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https.
CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
);
if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
$curl_options[CURLOPT_USERAGENT] = $matches[0];
}
if (isset($this->httpauth_credentials)) {
$curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
}
......@@ -1217,6 +1215,11 @@ protected function curlInitialize() {
// By default, the child session name should be the same as the parent.
$this->session_name = session_name();
}
// We set the user agent header on each request so as to use the current
// time and a new uniqid.
if (preg_match('/simpletest\d+/', $db_prefix, $matches)) {
curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
}
}
/**
......
......@@ -130,6 +130,7 @@ function simpletest_requirements($phase) {
$t = get_t();
$has_curl = function_exists('curl_init');
$has_hash = function_exists('hash_hmac');
$has_domdocument = class_exists('DOMDocument');
$requirements['curl'] = array(
......@@ -140,6 +141,14 @@ function simpletest_requirements($phase) {
$requirements['curl']['severity'] = REQUIREMENT_ERROR;
$requirements['curl']['description'] = $t('Simpletest could not be installed because the PHP <a href="@curl_url">cURL</a> library is not available.', array('@curl_url' => 'http://php.net/manual/en/curl.setup.php'));
}
$requirements['hash'] = array(
'title' => $t('hash'),
'value' => $has_hash ? $t('Enabled') : $t('Not found'),
);
if (!$has_hash) {
$requirements['hash']['severity'] = REQUIREMENT_ERROR;
$requirements['hash']['description'] = $t('Simpletest could not be installed because the PHP <a href="@hash_url">hash</a> extension is disabled.', array('@hash_url' => 'http://php.net/manual/en/book.hash.php'));
}
$requirements['php_domdocument'] = array(
'title' => $t('PHP DOMDocument class'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment