-
-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Added the documentation for the trusted_hosts option #3876
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added the documentation for the trusted_hosts option #3876
Conversation
javiereguiluz
commented
May 26, 2014
Q | A |
---|---|
Doc fix? | no |
New docs? | yes |
Applies to | 2.3+ |
Fixed tickets | #3586 |
@@ -114,6 +115,69 @@ services related to testing your application (e.g. ``test.client``) are loaded. | |||
This setting should be present in your ``test`` environment (usually via | |||
``app/config/config_test.yml``). For more information, see :doc:`/book/testing`. | |||
|
|||
.. _reference-framework-trusted-hosts: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did you add this? Did you want to add a link to this section, but you forgot to commit that link?
|
||
The value of the ``$_SERVER['HOST']`` parameter cannot be safely trusted because | ||
users can manipulate it. This option whitelists the hosts that your Symfony | ||
application can respond to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "can respond to"? It sounds like the app will not work unless you put your domain name here. I don't think this is what we mean - I actually don't know what this feature does tbh :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To understand the feature, it's better to see the code:
1. When booting, FrameworkBundle sets the trusted hosts of the Request:
class FrameworkBundle extends Bundle
{
public function boot()
{
// ...
if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) {
Request::setTrustedHosts($trustedHosts);
}
}
// ...
}
2. The Request uses this configuration value to define the trusted hosts 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();
}
3. When dealing with the Request, one of the called methods is prepareRequestUri()
:
protected function prepareRequestUri()
{
$requestUri = '';
if ($this->headers->has('X_ORIGINAL_URL')) {
// ...
} elseif ($this->headers->has('X_REWRITE_URL')) {
// ...
} elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
// ...
} elseif ($this->server->has('REQUEST_URI')) {
$requestUri = $this->server->get('REQUEST_URI');
// HTTP proxy reqs setup request URI with scheme and host
// [and port] + the URL path, only use URL path
$schemeAndHttpHost = $this->getSchemeAndHttpHost();
if (strpos($requestUri, $schemeAndHttpHost) === 0) {
$requestUri = substr($requestUri, strlen($schemeAndHttpHost));
}
} elseif ($this->server->has('ORIG_PATH_INFO')) {
// ...
}
// normalize the request URI to ease creating sub-requests from this request
$this->server->set('REQUEST_URI', $requestUri);
return $requestUri;
}
4. Indirectly, the getSchemeAndHttpHost()
method executes the getHost()
method, which will throw an exception if the host of the user request doesn't match the trusted host patterns:
public function getHost()
{
// ...
// as the host can come from the user (HTTP_HOST and depending on the
// configuration, SERVER_NAME too can come from the user)
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
if ($host && !preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host)) {
throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $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(sprintf('Untrusted Host "%s"', $host));
}
return $host;
}
So, if you leave this option blank, everything works as previously did. If you set any value, the application will thro an exception if the host provided by the user request doesn't match the configured regular expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so the purpose of the feature is so that I can "restrict my application to respond to only a sub-set of hosts". Is that accurate? What exactly is the security risk? Or said differently, why would a developer care about this?
I would like to re-word the first few sentences to address the use-case. This is totally invented (since I don't know the real use-case), but for example:
If you use the HTTP host for security (e.g., you can only access certain URLs from a specific host), then you should explicitly make sure your application only works for the host names that you're expecting.
That's weak wording... but you get the idea. I'm purposefully being "dumb" (and not looking up more detailson) so that if I understand this feature, then we're good :).
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ping @javiereguiluz!
configuration option is useful. The source of this information is the content of this security advisory: http://symfony.com/blog/security-releases-symfony-2-0-24-2-1-12-2-2-5-and-2-3-3-released#cve-2013-4752-request-gethost-poisoning
I would add a note in @javiereguiluz ´s explanation about the scenario where you run a Symfony app in a cloud environment where you can trust the ELB on top of your servers (provided there is no direct access to them) and can be sorted using this piece of code:
I added a similar comment here #2946 (comment) This seems to be a recurrent issue for people who start using Symfony since they don´t know how to make methods like $request->isSecure() work |
@ricardclau maybe you can also shed some light to this issue: symfony/symfony#11583 |
@javiereguiluz I have 2 quick questions left on this:
It's looking really good! It's nice that I haven't looked at the code for this yet - I can read it as a user and see what I don't understand :). Thanks! |
@ricardclau I have no real experience with those cloud scenarios, so I really cannot add the note that you are suggesting :( Could you please prepare that note so I can add it to this explanation? Thanks! 1. The default behavior is explained at the end:
2. What will happen if you set The code executed in that case will be https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/Request.php#L1177-1193, which will throw a |
@javiereguiluz there is a small issue though: the client will receive a 500 error in this case, while a 400 might be better. |
@javiereguiluz I misread the whole thing, I was talking about trusted_proxies, no trusted_hosts, sorry about the confusion. A cloud environment affects the proxies but the hosts stay the same. Sorry again! |
The Symfony Request::getHost() method might be vulnerable to some of these attacks | ||
because it depends on the configuration of your web server. One simple solution | ||
to avoid these attacks is to whitelist the hosts that your Symfony application | ||
can respond to. That's the purpose of this ``trusted_hosts`` option: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add one more sentence at the end. Something like this:
If the incoming request's hostname doesn't match one in this list, the
application won't respond and the user will receive a 500 response:
I wasn't sure initially if maybe if your host wasn't in this list that Symfony still responded but used a different absolute URL (or something like that). This is clear that literally your app won't work at all.
@javiereguiluz I added one more small request - thanks for your patience :). It will be very clear how this works now (even to me!). |
I copied this PR into #4121 (unfortunately, because you've removed this branch on your repo, I couldn't give you credits for it). |
@wouterj you can fetch the code of any PR on a repo, even if the source repo was removed. github uses references in a separate namespace than branches to store the code of PRs: $ git fetch https://github.com/symfony/symfony-docs.git refs/pull/3878
$ git checkout FETCH_HEAD For open PRs which are mergeable (i.e. no conflict), it is even possible to fetch |
wow, thanks @stof. I didn't knew that :) Btw, I always use hub, so I this case I did |
@wouterj hub was probably written before this existed in Github in a public way. Thus, given it creates a branch tracking the upstream one, it might make sense to do it the way they do. |