#19867 closed Bug (wontfix)
get_host shouldn't apply validation to server-set values
Reported by: | anonymous | Owned by: | nobody |
---|---|---|---|
Component: | HTTP handling | Version: | 1.4 |
Severity: | Normal | Keywords: | get_host security |
Cc: | Triage Stage: | Accepted | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Currently get_host will fall back to SERVER_NAME if there is no usable host header. If that fails, it uses SERVER_NAMEm which is a value set by the server itself. In this case there is no need for validation since the value is not from the request.
We recently ran into this issue when updating to django 1.4.2. We have HAProxy sending requests to nginx, which then connects to gunicorn, which runs our django application, via a unix socket. HAProxy health checks by default include no Host header and so, since the request came through a unix socket, the unix socket is in SERVER_NAME, which then causes the SuspiciousOperation exception to be raised.
I have a patch which I will send a pull request for and will post a link here to that.
Change History (7)
comment:1 by , 12 years ago
comment:2 by , 12 years ago
Triage Stage: | Unreviewed → Accepted |
---|
Thanks for the report! I agree that it isn't necessary to validate a host value reconstructed from SERVER_NAME
and
SERVER_PORT
(although, is it possible that some misconfigured WSGI gateways might put something untrusted into
SERVER_NAME
under some circumstance?).
Made a few comments on the pull request.
comment:3 by , 12 years ago
I suppose a misconfigured WSGI gateway could allow injection, but I believe this value is defined as being set by the WSGI gateway and hence should have different rules applied to it than the client-passed headers. I suppose the question would be, should django trust the WSGI gateway? I'd say yes.
comment:4 by , 12 years ago
There must be a bit stronger evidence that the ServerName can be trusted. Otherwise there will be need to do yet another host spoofing security fix.
Also, turning down this protection for ServerName means users will need to configure both the frontend server and ALLOWED_HOSTS correctly to actually get any protection. I know the configuration I started with would allow ServerName spoofing (Apache without virtualhost). I believe many old configurations were done like that, not least because Django's docs said that was the right thing: https://docs.djangoproject.com/en/1.3/howto/deployment/modwsgi/
Requests without Host header should hopefully be somewhat rare. Any HTTP 1.1 client must include the Host header. Maybe waiting for some time to see if this is more common problem would be a good idea?
comment:5 by , 12 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
On Apache, by default, SERVER_NAME
in under the control of the client:
See the docs for UseCanonicalName:
The CGI variables SERVER_NAME and SERVER_PORT will be constructed from the client supplied values as well.
There's also this big warning:
If CGIs make assumptions about the values of SERVER_NAME they may be broken by this option. The client is essentially free to give whatever value they want as a hostname. But if the CGI is only using SERVER_NAME to construct self-referential URLs then it should be just fine.
Django isn't going to make assumptions that create security vulnerabilities by default.
comment:6 by , 12 years ago
Independent of security vulnerabilities or not, Django relies on the value of get_host for URL reconstruction, so having only a SERVER_NAME as unix socket and no HOST header will break redirects anyways…
comment:7 by , 12 years ago
I didn't realize that SERVER vars could be under client control.
For anyone else having an issue with this kind of error, here's an explanation of what we encountered and how we fixed the problem without this patch:
Upgrade to django 1.4.2 caused SuspiciousOperation to be raised for HAProxy health checks as they had no Host header, nginx was not configured to automatically add a Host header when one was not included, and django was running on a unix socket, which gunicorn put in SERVER_NAME.
Original nginx conf used:
proxy_set_header Host $http_host;
We then tried:
proxy_set_header Host $host;
which fixed the issue with HAProxy but caused redirects to remove the port and hence break our application since we're running some services on ports other than port 80 and $host in nginx doesn't include the port, but $http_host does. The final fix is to use $host with $server_port:
proxy_set_header Host "${host}:${server_port}";
This way, nginx injects a Host header when needed and django doesn't choke on the "non-standard" SERVER_NAME which is the unix socket path.
Patch in pull request here: https://github.com/django/django/pull/744