Fixed a security issue in get_host.

Full disclosure and new release forthcoming.
This commit is contained in:
Florian Apolloner 2012-11-27 22:19:37 +01:00
parent a2f2a39956
commit 27560924ec
3 changed files with 34 additions and 4 deletions

View File

@ -25,6 +25,7 @@ from django.utils.encoding import force_bytes, force_text, force_str, iri_to_uri
RAISE_ERROR = object() RAISE_ERROR = object()
absolute_http_url_re = re.compile(r"^https?://", re.I) absolute_http_url_re = re.compile(r"^https?://", re.I)
host_validation_re = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$")
class UnreadablePostError(IOError): class UnreadablePostError(IOError):
@ -64,7 +65,7 @@ class HttpRequest(object):
host = '%s:%s' % (host, server_port) host = '%s:%s' % (host, server_port)
# Disallow potentially poisoned hostnames. # Disallow potentially poisoned hostnames.
if set(';/?@&=+$,').intersection(host): if not host_validation_re.match(host.lower()):
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host) raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
return host return host

View File

@ -185,6 +185,31 @@ recommend you ensure your Web server is configured such that:
Additionally, as of 1.3.1, Django requires you to explicitly enable support for Additionally, as of 1.3.1, Django requires you to explicitly enable support for
the ``X-Forwarded-Host`` header if your configuration requires it. the ``X-Forwarded-Host`` header if your configuration requires it.
Configuration for Apache
------------------------
The easiest way to get the described behavior in Apache is as follows. Create
a `virtual host`_ using the ServerName_ and ServerAlias_ directives to restrict
the domains Apache reacts to. Please keep in mind that while the directives do
support ports the match is only performed against the hostname. This means that
the ``Host`` header could still contain a port pointing to another webserver on
the same machine. The next step is to make sure that your newly created virtual
host is not also the default virtual host. Apache uses the first virtual host
found in the configuration file as default virtual host. As such you have to
ensure that you have another virtual host which will act as catch-all virtual
host. Just add one if you do not have one already, there is nothing special
about it aside from ensuring it is the first virtual host in the configuration
file. Debian/Ubuntu users usually don't have to take any action, since Apache
ships with a default virtual host in ``sites-available`` which is linked into
``sites-enabled`` as ``000-default`` and included from ``apache2.conf``. Just
make sure not to name your site ``000-abc``, since files are included in
alphabetical order.
.. _virtual host: http://httpd.apache.org/docs/2.2/vhosts/
.. _ServerName: http://httpd.apache.org/docs/2.2/mod/core.html#servername
.. _ServerAlias: http://httpd.apache.org/docs/2.2/mod/core.html#serveralias
.. _additional-security-topics: .. _additional-security-topics:
Additional security topics Additional security topics

View File

@ -116,13 +116,15 @@ class RequestsTests(unittest.TestCase):
'12.34.56.78:443', '12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080', '[2001:19f0:feee::dead:beef:cafe]:8080',
'xn--4ca9at.com', # Punnycode for öäü.com
] ]
poisoned_hosts = [ poisoned_hosts = [
'example.com@evil.tld', 'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld', 'example.com:dr.frankenstein@evil.tld',
'example.com:someone@somestie.com:80', 'example.com:dr.frankenstein@evil.tld:80',
'example.com:80/badpath' 'example.com:80/badpath',
'example.com: recovermypassword.com',
] ]
for host in legit_hosts: for host in legit_hosts:
@ -186,13 +188,15 @@ class RequestsTests(unittest.TestCase):
'12.34.56.78:443', '12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]', '[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080', '[2001:19f0:feee::dead:beef:cafe]:8080',
'xn--4ca9at.com', # Punnycode for öäü.com
] ]
poisoned_hosts = [ poisoned_hosts = [
'example.com@evil.tld', 'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld', 'example.com:dr.frankenstein@evil.tld',
'example.com:dr.frankenstein@evil.tld:80', 'example.com:dr.frankenstein@evil.tld:80',
'example.com:80/badpath' 'example.com:80/badpath',
'example.com: recovermypassword.com',
] ]
for host in legit_hosts: for host in legit_hosts: