Last active
April 29, 2018 14:25
-
-
Save nevergone/0dfd1d1f30b4acc984474268d98c7f04 to your computer and use it in GitHub Desktop.
D5-SA-CORE-2018-004.patch (SA-CORE-2018-002 fix included)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc | |
index 5b2e5ab..3d93104 100644 | |
--- a/includes/bootstrap.inc | |
+++ b/includes/bootstrap.inc | |
@@ -911,6 +911,7 @@ function _drupal_bootstrap($phase) { | |
drupal_unset_globals(); | |
// Initialize the configuration | |
conf_init(); | |
+ _drupal_bootstrap_sanitize_request(); | |
break; | |
case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: | |
@@ -942,6 +943,36 @@ 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'])) { | |
+ // 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']); | |
+ } | |
+ // Ensure that the destination's query parameters are not dangerous. | |
+ if (isset($_GET['destination'])) { | |
+ _drupal_bootstrap_clean_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']); | |
+ } | |
+ } | |
+ | |
_drupal_cache_init($phase); | |
// Start a page timer: | |
@@ -1024,3 +1055,166 @@ function get_t() { | |
} | |
return $t; | |
} | |
+ | |
+/** | |
+ * Sanitizes unsafe keys from the request. | |
+ */ | |
+function _drupal_bootstrap_sanitize_request() { | |
+ global $conf; | |
+ static $sanitized; | |
+ | |
+ if (!$sanitized) { | |
+ // Ensure the whitelist array exists. | |
+ if (!isset($conf['sanitize_input_whitelist']) || !is_array($conf['sanitize_input_whitelist'])) { | |
+ $conf['sanitize_input_whitelist'] = array(); | |
+ } | |
+ | |
+ $sanitized_keys = _drupal_bootstrap_sanitize_input($_GET, $conf['sanitize_input_whitelist']); | |
+ $sanitized_keys = array_merge($sanitized_keys, _drupal_bootstrap_sanitize_input($_POST, $conf['sanitize_input_whitelist'])); | |
+ $sanitized_keys = array_merge($sanitized_keys, _drupal_bootstrap_sanitize_input($_REQUEST, $conf['sanitize_input_whitelist'])); | |
+ $sanitized_keys = array_merge($sanitized_keys, _drupal_bootstrap_sanitize_input($_COOKIE, $conf['sanitize_input_whitelist'])); | |
+ $sanitized_keys = array_unique($sanitized_keys); | |
+ | |
+ if (count($sanitized_keys) && !empty($conf['sanitize_input_logging'])) { | |
+ trigger_error(check_plain(sprintf('Potentially unsafe keys removed from request parameters: %s', implode(', ', $sanitized_keys)), E_USER_WARNING)); | |
+ } | |
+ | |
+ $sanitized = TRUE; | |
+ } | |
+} | |
+ | |
+/** | |
+ * Sanitizes unsafe keys from user input. | |
+ * | |
+ * @param mixed $input | |
+ * Input to sanitize. | |
+ * @param array $whitelist | |
+ * Whitelist of values. | |
+ * @return array | |
+ */ | |
+function _drupal_bootstrap_sanitize_input(&$input, $whitelist = array()) { | |
+ $sanitized_keys = array(); | |
+ | |
+ if (is_array($input)) { | |
+ foreach ($input as $key => $value) { | |
+ if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) { | |
+ unset($input[$key]); | |
+ $sanitized_keys[] = $key; | |
+ } | |
+ elseif (is_array($input[$key])) { | |
+ $sanitized_keys = array_merge($sanitized_keys, _drupal_bootstrap_sanitize_input($input[$key], $whitelist)); | |
+ } | |
+ } | |
+ } | |
+ | |
+ return $sanitized_keys; | |
+} | |
+ | |
+/** | |
+ * Removes the destination if it is dangerous. | |
+ * | |
+ * Note this can only be called after common.inc has been included. | |
+ * | |
+ * @return bool | |
+ * TRUE if the destination has been removed from $_GET, FALSE if not. | |
+ */ | |
+function _drupal_bootstrap_clean_destination() { | |
+ $dangerous_keys = array(); | |
+ | |
+ $log_sanitized_keys = variable_get('sanitize_input_logging', FALSE); | |
+ | |
+ $parts = _drupal_parse_url($_GET['destination']); | |
+ if (!empty($parts['query'])) { | |
+ $whitelist = variable_get('sanitize_input_whitelist', array()); | |
+ $log_sanitized_keys = variable_get('sanitize_input_logging', FALSE); | |
+ | |
+ $dangerous_keys = _drupal_bootstrap_sanitize_input($parts['query'], $whitelist); | |
+ if (!empty($dangerous_keys)) { | |
+ // The destination is removed rather than sanitized to mirror the | |
+ // handling of external destinations. | |
+ unset($_GET['destination']); | |
+ unset($_REQUEST['destination']); | |
+ if ($log_sanitized_keys) { | |
+ trigger_error(sprintf('Potentially unsafe destination removed from query string parameters (GET) because it contained the following keys: %s', implode(', ', $dangerous_keys))); | |
+ } | |
+ return TRUE; | |
+ } | |
+ } | |
+ return FALSE; | |
+} | |
+ | |
+/** | |
+ * Backport of drupal_parse_url() from Drupal 7. | |
+ */ | |
+function _drupal_parse_url($url) { | |
+ $options = array( | |
+ 'path' => NULL, | |
+ 'query' => array(), | |
+ 'fragment' => '', | |
+ ); | |
+ | |
+ // External URLs: not using parse_url() here, so we do not have to rebuild | |
+ // the scheme, host, and path without having any use for it. | |
+ if (strpos($url, '://') !== FALSE) { | |
+ | |
+ // Split off everything before the query string into 'path'. | |
+ $parts = explode('?', $url); | |
+ $options['path'] = $parts[0]; | |
+ | |
+ // If there is a query string, transform it into keyed query parameters. | |
+ if (isset($parts[1])) { | |
+ $query_parts = explode('#', $parts[1]); | |
+ parse_str($query_parts[0], $options['query']); | |
+ | |
+ // Take over the fragment, if there is any. | |
+ if (isset($query_parts[1])) { | |
+ $options['fragment'] = $query_parts[1]; | |
+ } | |
+ } | |
+ } | |
+ else { | |
+ | |
+ // parse_url() does not support relative URLs, so make it absolute. E.g. the | |
+ // relative URL "foo/bar:1" isn't properly parsed. | |
+ $parts = parse_url('http://example.com/' . $url); | |
+ | |
+ // Strip the leading slash that was just added. | |
+ $options['path'] = substr($parts['path'], 1); | |
+ if (isset($parts['query'])) { | |
+ parse_str($parts['query'], $options['query']); | |
+ } | |
+ if (isset($parts['fragment'])) { | |
+ $options['fragment'] = $parts['fragment']; | |
+ } | |
+ } | |
+ | |
+ // The 'q' parameter contains the path of the current page if clean URLs are | |
+ // disabled. It overrides the 'path' of the URL when present, even if clean | |
+ // URLs are enabled, due to how Apache rewriting rules work. The path | |
+ // parameter must be a string. | |
+ if (isset($options['query']['q']) && is_string($options['query']['q'])) { | |
+ $options['path'] = $options['query']['q']; | |
+ unset($options['query']['q']); | |
+ } | |
+ return $options; | |
+} | |
+ | |
+/** | |
+ * Backport of menu_path_is_external() from Drupal 6. | |
+ */ | |
+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. Leading control characters may be ignored or mishandled by browsers, | |
+ // so assume such a path may lead to an external location. The range matches | |
+ // all UTF-8 control characters, class Cc. | |
+ $colonpos = strpos($path, ':'); | |
+ // Some browsers treat \ as / so normalize to forward slashes. | |
+ $path = str_replace('\\', '/', $path); | |
+ return (strpos($path, '//') === 0) || (preg_match('/^[\x00-\x1F\x7F-\x9F]/u', $path) !== 0) | |
+ || ($colonpos !== FALSE | |
+ && !preg_match('![/?#]!', substr($path, 0, $colonpos)) | |
+ && filter_xss_bad_protocol($path, FALSE) == check_plain($path)); | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment