diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index d5e6059..66615fb 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -206,6 +206,9 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface { * from disk. Defaults to TRUE. * * @return static + * + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException + * In case the host name in the request is not trusted. */ public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE) { // Include our bootstrap file. @@ -222,6 +225,15 @@ public static function createFromRequest(Request $request, $class_loader, $envir $kernel->setSitePath($site_path); Settings::initialize(dirname($core_root), $site_path, $class_loader); + // Initialize our list of trusted HTTP Host headers to protect against + // header attacks + $hostPatterns = Settings::get('trusted_host_patterns', array()); + if (PHP_SAPI !== 'cli' && !empty($hostPatterns)) { + if (static::setupTrustedHosts($request, $hostPatterns) === FALSE) { + throw new BadRequestHttpException(); + } + } + // Redirect the user to the installation script if Drupal has not been // installed yet (i.e., if no $databases array has been defined in the // settings.php file) and we are not already installing. @@ -1313,4 +1325,46 @@ public static function validateHostname(Request $request) { return TRUE; } + /** + * Sets up the lists of trusted HTTP Host headers. + * + * Since the HTTP Host header can be set by the user making the request, it + * is possible to create an attack vectors against a site by overriding this. + * Symfony provides a mechanism for creating a list of trusted Host values. + * + * Host patterns (as regular expressions) can be configured throught + * settings.php for multisite installations, sites using ServerAlias without + * canonical redirection, or configurations where the site responds to default + * requests. For example, + * + * @code + * $settings['trusted_host_patterns'] = array( + * '^example\.com$', + * '^*.example\.com$', + * ); + * @endcode + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object + * @param array $hostPatterns + * The array of trusted host patterns + * + * @return boolean + * TRUE if the Host header is trusted, FALSE otherwise + * + * @see https://www.drupal.org/node/1992030 + */ + protected static function setupTrustedHosts(Request $request, $hostPatterns) { + $request->setTrustedHosts($hostPatterns); + + // Get the host, which will validate the current request. + try { + $request->getHost(); + } + catch (\UnexpectedValueException $e) { + return FALSE; + } + + return TRUE; + } } diff --git a/core/modules/system/src/Tests/System/TrustedHostsTest.php b/core/modules/system/src/Tests/System/TrustedHostsTest.php new file mode 100644 index 0000000..aac720f --- /dev/null +++ b/core/modules/system/src/Tests/System/TrustedHostsTest.php @@ -0,0 +1,62 @@ +drupalCreateUser(array( + 'administer site configuration', + )); + $this->drupalLogin($admin_user); + } + + /** + * Tests that the status page shows a warning when the trusted host setting + * is missing from settings.php + */ + public function testStatusPageWithoutConfiguration() { + $this->drupalGet('admin/reports/status'); + + $this->assertRaw(t('Trusted Host Settings')); + $this->assertRaw(t('The trusted_host_patterns setting is not configured in settings.php.')); + } + + /** + * Tests that the status page shows a warning when the trusted host setting + * is missing from settings.php + */ + public function testStatusPageWithConfiguration() { + $settings['settings']['trusted_host_patterns'] = (object) array( + 'value' => array('^' . preg_quote(\Drupal::request()->getHost()) . '$'), + 'required' => TRUE, + ); + + $this->writeSettings($settings); + + $this->drupalGet('admin/reports/status'); + $this->assertResponse(200, 'The status page is reachable.'); + + $this->assertRaw(t('Trusted Host Settings')); + $this->assertNoRaw(t('The trusted_host_patterns setting is not configured in settings.php.')); + } + +} diff --git a/core/modules/system/system.install b/core/modules/system/system.install index c9ae4c1..4ff27f3 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -611,6 +611,27 @@ function system_requirements($phase) { ); } } + + // See if trusted hostnames have been configured, and warn the user if not + // set. + if ($phase == 'runtime') { + $trusted_host_patterns = Settings::get('trusted_host_patterns'); + if (empty($trusted_host_patterns)) { + $requirements['trusted_host_patterns'] = array( + 'title' => t('Trusted Host Settings'), + 'value' => t('Not enabled'), + 'description' => t('The trusted_host_patterns setting is not configured in settings.php. This can lead to security vulnerabilities. It is highly recommended that you configure this. See Protecting against HTTP HOST Header attacks for more information.', array('@url' => 'https://www.drupal.org/node/1992030')), + 'severity' => REQUIREMENT_WARNING, + ); + } + else { + $requirements['trusted_host_patterns'] = array( + 'title' => t('Trusted Host Settings'), + 'value' => t('Enabled'), + ); + } + } + return $requirements; } diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 58a1734..b8c290f 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -611,8 +611,9 @@ public function renderPreview($display_id, $args = array()) { // Make view links come back to preview. - // Also override the current path so we get the pager. - $request = new Request(); + // Also override the current path so we get the pager, and make sure the + // Request object gets all of proper values from $_SERVER. + $request = Request::createFromGlobals(); $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form'); $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')->getRouteByName('entity.view.preview_form')); $request->attributes->set('view', $this->storage); diff --git a/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTrustedHostsTest.php b/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTrustedHostsTest.php new file mode 100644 index 0000000..150a724 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/DrupalKernel/DrupalKernelTrustedHostsTest.php @@ -0,0 +1,78 @@ + array( + '^example\.com$', + '^.+\.example\.com$', + '^example\.org', + '^.+\.example\.org', + ) + )); + + if (!empty($host)) { + $request->headers->set('HOST', $host); + } + + $request->server->set('SERVER_NAME', $server_name); + + $method = new \ReflectionMethod('Drupal\Core\DrupalKernel', 'setupTrustedHosts'); + $method->setAccessible(TRUE); + $valid_host = $method->invoke(null, $request, $settings->get('trusted_host_patterns', array())); + + $this->assertSame($expected, $valid_host, $message); + } + + /** + * Provides test data for testTrustedHosts(). + */ + public function providerTestTrustedHosts() { + $data = []; + + // Tests canonical URL + $data[] = ['www.example.com', 'www.example.com', 'canonical URL is trusted', TRUE]; + + // Tests missing hostname for HTTP/1.0 compatability where the Host + // header is optional + $data[] = [NULL, 'www.example.com', 'empty Host is valid', TRUE]; + + // Tests the additional paterns from the settings. + $data[] = ['example.com', 'www.example.com', 'host from settings is trusted', TRUE]; + $data[] = ['subdomain.example.com', 'www.example.com', 'host from settings is trusted', TRUE]; + $data[] = ['www.example.org', 'www.example.com', 'host from settings is trusted', TRUE]; + $data[] = ['example.org', 'www.example.com', 'host from settings is trusted', TRUE]; + + // Tests mismatches + $data[] = ['www.blackhat.com', 'www.example.com', 'unspecified host is untrusted', FALSE]; + + return $data; + } + +} diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 53d31d9..23bfea5 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -631,3 +631,40 @@ # if (file_exists(__DIR__ . '/settings.local.php')) { # include __DIR__ . '/settings.local.php'; # } + +/** + * Trusted host configuration. + * + * Drupal core can use the Symfony trusted host mechanism to prevent HTTP Host + * header spoofing. + * + * To enable the trusted host mechanism, you enable your allowable hosts + * in $settings['trusted_host_patterns']. This should be an array of regular + * expression paterns, without delimiters, representing the hosts you would like + * to allow. + * + * For example: + * @code + * $settings['trusted_host_patterns'] = array( + * '^www\.example\.com$', + * ); + * @endcode + * will allow the site to only run from www.example.com. + * + * If you are running multisite, or if you are running your site from + * different domain names (eg, you don't redirect http://www.example.com to + * http://example.com), you should specify all of the host patterns that are + * allowed by your site. + * + * For example: + * @code + * $settings['trusted_host_patterns'] = array( + * '^example\.com$', + * '^.+\.example\.com$', + * '^example\.org', + * '^.+\.example\.org', + * ); + * @endcode + * will allow the site to run off of all variants of example.com and + * example.org, with all subdomains included. + */