diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 37ed0e97a68d9fd88e848c292fbbf6f63503f3cb..fec1be9ad3a6163744a6cfde69965417b3121608 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -20,6 +20,7 @@ use Drupal\Core\Http\TrustedHostsRequestFactory; use Drupal\Core\Installer\InstallerRedirectTrait; use Drupal\Core\Language\Language; +use Drupal\Core\Security\RequestSanitizer; use Drupal\Core\Site\Settings; use Drupal\Core\Test\TestDatabase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -542,6 +543,12 @@ public function loadLegacyIncludes() { * {@inheritdoc} */ public function preHandle(Request $request) { + // Sanitize the request. + $request = RequestSanitizer::sanitize( + $request, + (array) Settings::get(RequestSanitizer::SANITIZE_WHITELIST, []), + (bool) Settings::get(RequestSanitizer::SANITIZE_LOG, FALSE) + ); $this->loadLegacyIncludes(); diff --git a/core/lib/Drupal/Core/Security/RequestSanitizer.php b/core/lib/Drupal/Core/Security/RequestSanitizer.php new file mode 100644 index 0000000000000000000000000000000000000000..8ba17b95cf51c7b2007af4210ae044324554c551 --- /dev/null +++ b/core/lib/Drupal/Core/Security/RequestSanitizer.php @@ -0,0 +1,99 @@ +<?php + +namespace Drupal\Core\Security; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Sanitizes user input. + */ +class RequestSanitizer { + + /** + * Request attribute to mark the request as sanitized. + */ + const SANITIZED = '_drupal_request_sanitized'; + + /** + * The name of the setting that configures the whitelist. + */ + const SANITIZE_WHITELIST = 'sanitize_input_whitelist'; + + /** + * The name of the setting that determines if sanitized keys are logged. + */ + const SANITIZE_LOG = 'sanitize_input_logging'; + + /** + * Strips dangerous keys from user input. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The incoming request to sanitize. + * @param string[] $whitelist + * An array of keys to whitelist as safe. See default.settings.php. + * @param bool $log_sanitized_keys + * (optional) Set to TRUE to log an keys that are sanitized. + * + * @return \Symfony\Component\HttpFoundation\Request + * The sanitized request. + */ + public static function sanitize(Request $request, $whitelist, $log_sanitized_keys = FALSE) { + if (!$request->attributes->get(self::SANITIZED, FALSE)) { + // Process query string parameters. + $get_sanitized_keys = []; + $request->query->replace(static::stripDangerousValues($request->query->all(), $whitelist, $get_sanitized_keys)); + if ($log_sanitized_keys && !empty($get_sanitized_keys)) { + trigger_error(sprintf('Potentially unsafe keys removed from query string parameters (GET): %s', implode(', ', $get_sanitized_keys))); + } + + // Request body parameters. + $post_sanitized_keys = []; + $request->request->replace(static::stripDangerousValues($request->request->all(), $whitelist, $post_sanitized_keys)); + if ($log_sanitized_keys && !empty($post_sanitized_keys)) { + trigger_error(sprintf('Potentially unsafe keys removed from request body parameters (POST): %s', implode(', ', $post_sanitized_keys))); + } + + // Cookie parameters. + $cookie_sanitized_keys = []; + $request->cookies->replace(static::stripDangerousValues($request->cookies->all(), $whitelist, $cookie_sanitized_keys)); + if ($log_sanitized_keys && !empty($cookie_sanitized_keys)) { + trigger_error(sprintf('Potentially unsafe keys removed from cookie parameters: %s', implode(', ', $cookie_sanitized_keys))); + } + + if (!empty($get_sanitized_keys) || !empty($post_sanitized_keys) || !empty($cookie_sanitized_keys)) { + $request->overrideGlobals(); + } + $request->attributes->set(self::SANITIZED, TRUE); + } + return $request; + } + + /** + * Strips dangerous keys from $input. + * + * @param mixed $input + * The input to sanitize. + * @param string[] $whitelist + * An array of keys to whitelist as safe. + * @param string[] $sanitized_keys + * An array of keys that have been removed. + * + * @return mixed + * The sanitized input. + */ + protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) { + if (is_array($input)) { + foreach ($input as $key => $value) { + if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) { + unset($input[$key]); + $sanitized_keys[] = $key; + } + else { + $input[$key] = static::stripDangerousValues($input[$key], $whitelist, $sanitized_keys); + } + } + } + return $input; + } + +} diff --git a/core/modules/update/update.module b/core/modules/update/update.module index 0545aef8b9aea17776dfe468b5296b631fceabdb..2b9d651a7490ca67f9a8ccf30ae08c33d3f92e44 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -404,9 +404,56 @@ function update_get_available($refresh = FALSE) { $available = \Drupal::keyValueExpirable('update_available_releases')->getAll(); } + // Check for security releases that are covered under the same security + // advisories as the site's current release, and override the update status + // data so that those releases are not flagged as needed security updates. + // Any security releases beyond those specific releases will still be shown + // as required security updates. + + // @todo This is a temporary fix to allow minor-version backports of security + // fixes to be shown as secure. It should not be included in the codebase of + // any release or branch other than such backports. Replace this with + // https://www.drupal.org/project/drupal/issues/2766491. + foreach (_update_equivalent_security_releases() as $equivalent_release) { + if (!empty($available['drupal']['releases'][$equivalent_release]['terms']['Release type'])) { + $security_release_key = array_search('Security update', $available['drupal']['releases'][$equivalent_release]['terms']['Release type']); + if ($security_release_key !== FALSE) { + unset($available['drupal']['releases'][$equivalent_release]['terms']['Release type'][$security_release_key]); + } + } + } return $available; } +/** + * Identifies equivalent security releases with a hardcoded list. + * + * Generally, only the latest minor version of Drupal 8 is supported. However, + * when security fixes are backported to an old branch, and the site owner + * updates to the release containing the backported fix, they should not + * see "Security update required!" again if the only other security releases + * are releases for the same advisories. + * + * @return string[] + * A list of security release numbers that are equivalent to this release + * (i.e. covered by the same advisory), for backported security fixes only. + * + * @todo This is a temporary fix to allow minor-version backports of security + * fixes to be shown as secure. It should not be included in the codebase of + * any release or branch other than such backports. Replace this with + * https://www.drupal.org/project/drupal/issues/2766491. + */ +function _update_equivalent_security_releases() { + switch (\Drupal::VERSION) { + case '8.4.5': + return ['8.5.0-rc1']; + case '8.4.6': + return ['8.5.1']; + } + + return []; +} + /** * Adds a task to the queue for fetching release history data for a project. *