diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index fe3c3501d5..185a48bd16 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -68,6 +68,13 @@ class Configuration implements ConfigurationInterface ->scalarNode('ide')->defaultNull()->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() + ->arrayNode('trusted_hosts') + ->beforeNormalization() + ->ifTrue(function($v) { return is_string($v); }) + ->then(function($v) { return array($v); }) + ->end() + ->prototype('scalar')->end() + ->end() ->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index d2d044b29c..b4decdfefc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -67,6 +67,7 @@ class FrameworkExtension extends Extension } $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); + $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); // @deprecated, to be removed in 2.3 $container->setParameter('kernel.trust_proxy_headers', $config['trust_proxy_headers']); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index a255d120b9..2919146d10 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -46,6 +46,10 @@ class FrameworkBundle extends Bundle } elseif ($this->container->getParameter('kernel.trust_proxy_headers')) { Request::trustProxyData(); // @deprecated, to be removed in 2.3 } + + if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) { + Request::setTrustedHosts($trustedHosts); + } } public function build(ContainerBuilder $container) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 3c6c0ea475..8f9ecf9cb4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -22,7 +22,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase $config = $processor->processConfiguration(new Configuration(), array(array('secret' => 's3cr3t'))); $this->assertEquals( - array_merge(array('secret' => 's3cr3t'), self::getBundleDefaultConfig()), + array_merge(array('secret' => 's3cr3t', 'trusted_hosts' => array()), self::getBundleDefaultConfig()), $config ); } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 1d8b65c326..ec65aa844b 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -39,6 +39,16 @@ class Request protected static $trustedProxies = array(); + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + /** * Names for headers that can be trusted when * using trusted proxies. @@ -501,6 +511,32 @@ class Request return self::$trustedProxies; } + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', str_replace('}', '\\}', $hostPattern)); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns. + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + /** * Sets the name for trusted headers. * @@ -1050,6 +1086,24 @@ class Request throw new \UnexpectedValueException('Invalid Host'); } + if (count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + throw new \UnexpectedValueException('Untrusted Host'); + } + return $host; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 1cf43377dc..57d06b736a 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1515,6 +1515,37 @@ class RequestTest extends \PHPUnit_Framework_TestCase ) ); } + + public function testTrustedHosts() + { + // create a request + $request = Request::create('/'); + + // no trusted host set -> no host check + $request->headers->set('host', 'evil.com'); + $this->assertEquals('evil.com', $request->getHost()); + + // add a trusted domain and all its subdomains + Request::setTrustedHosts(array('.*\.?trusted.com$')); + + // untrusted host + $request->headers->set('host', 'evil.com'); + try { + $request->getHost(); + $this->fail('Request::getHost() should throw an exception when host is not trusted.'); + } catch (\UnexpectedValueException $e) { + $this->assertEquals('Untrusted Host', $e->getMessage()); + } + + // trusted hosts + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $request->headers->set('host', 'subdomain.trusted.com'); + $this->assertEquals('subdomain.trusted.com', $request->getHost()); + + // reset request for following tests + Request::setTrustedHosts(array()); + } } class RequestContentProxy extends Request