summaryrefslogtreecommitdiff
path: root/django/http/request.py
diff options
context:
space:
mode:
authorCarl Meyer <carl@oddbird.net>2013-02-09 10:17:01 -0700
committerCarl Meyer <carl@oddbird.net>2013-02-19 11:23:29 -0700
commitd51fb74360b94f2a856573174f8aae3cd905dd35 (patch)
treec2d663edd49bfe6c09f8f492630f5dd344409ad6 /django/http/request.py
parent1add79bc4007fee658f193b65aea2af2347aab6b (diff)
Added a new required ALLOWED_HOSTS setting for HTTP host header validation.
This is a security fix; disclosure and advisory coming shortly.
Diffstat (limited to 'django/http/request.py')
-rw-r--r--django/http/request.py53
1 files changed, 48 insertions, 5 deletions
diff --git a/django/http/request.py b/django/http/request.py
index a8eb14d154..2c19e4ee8c 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -64,11 +64,12 @@ class HttpRequest(object):
if server_port != ('443' if self.is_secure() else '80'):
host = '%s:%s' % (host, server_port)
- # Disallow potentially poisoned hostnames.
- if not host_validation_re.match(host.lower()):
- raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
-
- return host
+ allowed_hosts = ['*'] if settings.DEBUG else settings.ALLOWED_HOSTS
+ if validate_host(host, allowed_hosts):
+ return host
+ else:
+ raise SuspiciousOperation(
+ "Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host)
def get_full_path(self):
# RFC 3986 requires query string arguments to be in the ASCII range.
@@ -450,3 +451,45 @@ def bytes_to_text(s, encoding):
return six.text_type(s, encoding, 'replace')
else:
return s
+
+
+def validate_host(host, allowed_hosts):
+ """
+ Validate the given host header value for this site.
+
+ Check that the host looks valid and matches a host or host pattern in the
+ given list of ``allowed_hosts``. Any pattern beginning with a period
+ matches a domain and all its subdomains (e.g. ``.example.com`` matches
+ ``example.com`` and any subdomain), ``*`` matches anything, and anything
+ else must match exactly.
+
+ Return ``True`` for a valid host, ``False`` otherwise.
+
+ """
+ # All validation is case-insensitive
+ host = host.lower()
+
+ # Basic sanity check
+ if not host_validation_re.match(host):
+ return False
+
+ # Validate only the domain part.
+ if host[-1] == ']':
+ # It's an IPv6 address without a port.
+ domain = host
+ else:
+ domain = host.rsplit(':', 1)[0]
+
+ for pattern in allowed_hosts:
+ pattern = pattern.lower()
+ match = (
+ pattern == '*' or
+ pattern.startswith('.') and (
+ domain.endswith(pattern) or domain == pattern[1:]
+ ) or
+ pattern == domain
+ )
+ if match:
+ return True
+
+ return False