Skip to content

Commit

Permalink
[1.4.X] Fixed a security issue in get_host.
Browse files Browse the repository at this point in the history
Full disclosure and new release forthcoming.
  • Loading branch information
apollo13 committed Dec 10, 2012
1 parent b2ae0a6 commit 319627c
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
4 changes: 3 additions & 1 deletion django/http/__init__.py
Expand Up @@ -126,6 +126,8 @@ def __init__(self, *args, **kwargs):
RESERVED_CHARS="!*'();:@&=+$,/?%#[]"

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):
pass
Expand Down Expand Up @@ -214,7 +216,7 @@ def get_host(self):
host = '%s:%s' % (host, server_port)

# Disallow potentially poisoned hostnames.
if set(';/?@&=+$,').intersection(host):
if not host_validation_re.match(host.lower()):
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)

return host
Expand Down
27 changes: 27 additions & 0 deletions docs/topics/security.txt
Expand Up @@ -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
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
==========================

Expand Down
11 changes: 8 additions & 3 deletions tests/regressiontests/requests/tests.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement

import time
Expand Down Expand Up @@ -154,13 +155,15 @@ def test_http_get_host(self):
'12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080',
'xn--4ca9at.com', # Punnycode for öäü.com
]

poisoned_hosts = [
'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld',
'example.com:someone@somestie.com:80',
'example.com:80/badpath'
'example.com:dr.frankenstein@evil.tld:80',
'example.com:80/badpath',
'example.com: recovermypassword.com',
]

for host in legit_hosts:
Expand Down Expand Up @@ -230,13 +233,15 @@ def test_http_get_host_with_x_forwarded_host(self):
'12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080',
'xn--4ca9at.com', # Punnycode for öäü.com
]

poisoned_hosts = [
'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld',
'example.com:dr.frankenstein@evil.tld:80',
'example.com:80/badpath'
'example.com:80/badpath',
'example.com: recovermypassword.com',
]

for host in legit_hosts:
Expand Down

0 comments on commit 319627c

Please sign in to comment.