Issue #2417075 by tstoeckler, David4514, dawehner, sachbearbeiter, mpdonadio: Trusted host verification is incompatible with URIs with the "internal" scheme

8.0.x
webchick 2015-03-07 11:15:15 +11:00
parent 6238aabe9e
commit 0a84d315ab
7 changed files with 235 additions and 1 deletions

View File

@ -20,6 +20,7 @@ use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\YamlFileLoader;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\File\MimeType\MimeTypeGuesser;
use Drupal\Core\Http\TrustedHostsRequestFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\PageCache\RequestPolicyInterface;
use Drupal\Core\PhpStorage\PhpStorageFactory;
@ -1297,13 +1298,24 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
* TRUE if the Host header is trusted, FALSE otherwise.
*
* @see https://www.drupal.org/node/1992030
* @see \Drupal\Core\Http\TrustedHostsRequestFactory
*/
protected static function setupTrustedHosts(Request $request, $host_patterns) {
$request->setTrustedHosts($host_patterns);
// Get the host, which will validate the current request.
try {
$request->getHost();
$host = $request->getHost();
// Fake requests created through Request::create() without passing in the
// server variables from the main request have a default host of
// 'localhost'. If 'localhost' does not match any of the trusted host
// patterns these fake requests would fail the host verification. Instead,
// TrustedHostsRequestFactory makes sure to pass in the server variables
// from the main request.
$request_factory = new TrustedHostsRequestFactory($host);
Request::setFactory([$request_factory, 'createRequest']);
}
catch (\UnexpectedValueException $e) {
return FALSE;

View File

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains Drupal\Core\Http\TrustedHostsRequestFactory.
*/
namespace Drupal\Core\Http;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a request factory for requests using host verification.
*
* Because the trusted host patterns for requests are stored statically, they
* are consulted even for fake request created with Request::create(), whose
* host is 'localhost' by default. Such requests would fail host verification
* unless 'localhost' matches one of the trusted host patterns. To circumvent
* this problem, this factory injects the server variables from the main request
* into each newly created request, so that the host is correctly set even for
* fake requests and they properly pass host verification.
*
* @see \Drupal\Core\DrupalKernel::setupTrustedHosts()
*/
class TrustedHostsRequestFactory {
/**
* The host of the main request.
*
* @var string
*/
protected $host;
/**
* Creates a new TrustedHostsRequestFactory.
*
* @param string $host
* The host of the main request.
*/
public function __construct($host) {
$this->host = (string) $host;
}
/**
* Creates a new request object.
*
* @param array $query
* (optional) The query (GET) or request (POST) parameters.
* @param array $request
* (optional) An array of request variables.
* @param array $attributes
* (optioanl) An array of attributes.
* @param array $cookies
* (optional) The request cookies ($_COOKIE).
* @param array $files
* (optional) The request files ($_FILES).
* @param array $server
* (optional) The server parameters ($_SERVER).
* @param string $content
* (optional) The raw body data.
*
* @return \Symfony\Component\HttpFoundation\Request
* A new request object.
**/
public function createRequest(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) {
if (empty($server['HTTP_HOST']) || ($server['HTTP_HOST'] === 'localhost' && $this->host !== 'localhost')) {
$server['HTTP_HOST'] = $this->host;
}
return new Request($query, $request, $attributes, $cookies, $files, $server, $content);
}
}

View File

@ -8,7 +8,10 @@
namespace Drupal\system\Tests\System;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
use Drupal\user\Entity\User;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests output on the status overview page.
@ -59,4 +62,55 @@ class TrustedHostsTest extends WebTestBase {
$this->assertRaw(t('The trusted_host_patterns setting is set to allow'));
}
/**
* Tests that fake requests have the proper host configured.
*
* @see \Drupal\Core\Http\TrustedHostsRequestFactory
*/
public function testFakeRequests() {
$this->container->get('module_installer')->install(['trusted_hosts_test']);
$host = $this->container->get('request_stack')->getCurrentRequest()->getHost();
$settings['settings']['trusted_host_patterns'] = (object) array(
'value' => array('^' . preg_quote($host) . '$'),
'required' => TRUE,
);
$this->writeSettings($settings);
$this->drupalGet('trusted-hosts-test/fake-request');
$this->assertText('Host: ' . $host);
}
/**
* Tests that shortcut module works together with host verification.
*/
public function testShortcut() {
$this->container->get('module_installer')->install(['block', 'shortcut']);
$this->rebuildContainer();
/** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
$entity_manager = $this->container->get('entity.manager');
$shortcut_storage = $entity_manager->getStorage('shortcut');
$shortcut = $shortcut_storage->create([
'title' => $this->randomString(),
'link' => 'internal:/admin/reports/status',
'shortcut_set' => 'default',
]);
$shortcut_storage->save($shortcut);
// Grant the current user access to see the shortcuts.
$role_storage = $entity_manager->getStorage('user_role');
$roles = $this->loggedInUser->getRoles(TRUE);
/** @var \Drupal\user\RoleInterface $role */
$role = $role_storage->load(reset($roles));
$role->grantPermission('access shortcuts')->save();
$this->drupalPlaceBlock('shortcuts');
$this->drupalGet('');
$this->assertLink($shortcut->label());
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* @file
* Contains Drupal\trusted_hosts_test\Controller\TrustedHostsTestController.
*/
namespace Drupal\trusted_hosts_test\Controller;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a test controller for testing the trusted hosts setting.
*/
class TrustedHostsTestController {
/**
* Creates a fake request and prints out its host.
*/
public function fakeRequestHost() {
$request = Request::create('/');
return ['#markup' => 'Host: ' . $request->getHost()];
}
}

View File

@ -0,0 +1,5 @@
type: module
name: 'Trusted hosts test module'
core: 8.x
package: Testing
version: VERSION

View File

@ -0,0 +1,6 @@
trusted_hosts_test.fake_request:
path: '/trusted-hosts-test/fake-request'
defaults:
_controller: 'Drupal\trusted_hosts_test\Controller\TrustedHostsTestController::fakeRequestHost'
requirements:
_access: 'TRUE'

View File

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains Drupal\Tests\Core\Http\TrustedHostsRequestFactoryTest.
*/
namespace Drupal\Tests\Core\Http;
use Drupal\Core\Http\TrustedHostsRequestFactory;
use Drupal\Tests\UnitTestCase;
/**
* Tests the trusted hosts request factory.
*
* @coversDefaultClass \Drupal\Core\Http\TrustedHostsRequestFactory
* @group Http
*/
class TrustedHostsRequestFactoryTest extends UnitTestCase {
/**
* Tests TrustedHostsRequestFactory::createRequest().
*
* @param string $host
* The host to pass into TrustedHostsRequestFactory.
* @param array $server
* The server array to pass into
* TrustedHostsRequestFactory::createRequest().
* @param string $expected
* The expected host of the created request.
*
* @covers ::createRequest
* @dataProvider providerTestCreateRequest
*/
public function testCreateRequest($host, $server, $expected) {
$request_factory = new TrustedHostsRequestFactory($host);
$request = $request_factory->createRequest([], [], [], [], [], $server, []);
$this->assertEquals($expected, $request->getHost());
}
/**
* Provides data for testCreateRequest().
*
* @return array
* An array of test cases, where each test case is an array with the
* following values:
* - A string containing the host to pass into TrustedHostsRequestFactory.
* - An array containing the server array to pass into
* TrustedHostsRequestFactory::createRequest().
* - A string containing the expected host of the created request.
*/
public function providerTestCreateRequest() {
$tests = [];
$tests[] = ['example.com', [], 'example.com'];
$tests[] = ['localhost', [], 'localhost'];
$tests[] = ['localhost', ['HTTP_HOST' => 'localhost'], 'localhost'];
$tests[] = ['example.com', ['HTTP_HOST' => 'localhost'], 'example.com'];
return $tests;
}
}