[1.4.X] Fixed a security issue in get_host.

Full disclosure and new release forthcoming.
This commit is contained in:
Florian Apolloner 2012-11-27 22:26:29 +01:00
parent b2ae0a63ae
commit 319627c184
3 changed files with 38 additions and 4 deletions

View File

@ -126,6 +126,8 @@ from django.utils import timezone
RESERVED_CHARS="!*'();:@&=+$,/?%#[]" RESERVED_CHARS="!*'();:@&=+$,/?%#[]"
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 Http404(Exception): class Http404(Exception):
pass pass
@ -214,7 +216,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

@ -167,6 +167,33 @@ 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
========================== ==========================

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
import time import time
@ -154,13 +155,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:
@ -230,13 +233,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: