From ea94f01cc16ee123ab4621ba2e1f942645aaef7a Mon Sep 17 00:00:00 2001 From: Alex Dehnert Date: Wed, 11 May 2011 20:20:50 -0400 Subject: [PATCH] Add DNS rebinding protection. --- django/conf/global_settings.py | 7 ++++ django/middleware/hostmatch.py | 42 +++++++++++++++++++++ django/views/hostmatch.py | 79 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 0 deletions(-) create mode 100644 django/middleware/hostmatch.py create mode 100644 django/views/hostmatch.py diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 8638aee..c8b552b 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -497,6 +497,13 @@ CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' CSRF_COOKIE_NAME = 'csrftoken' CSRF_COOKIE_DOMAIN = None +############# +# HOSTMATCH # +############# + +HOSTMATCH_FAILURE_VIEW = 'django.views.hostmatch.hostmatch_failure' +HOSTMATCH_ALLOWED_HOSTNAMES = None + ############ # MESSAGES # ############ diff --git a/django/middleware/hostmatch.py b/django/middleware/hostmatch.py new file mode 100644 index 0000000..9eab6e1 --- /dev/null +++ b/django/middleware/hostmatch.py @@ -0,0 +1,42 @@ +""" +Host-matching middleware. + +This module provides a middleware that implements protection +against DNS rebinding. +""" + +from django.conf import settings +from django.core.urlresolvers import get_callable + +REASON_BAD_HOST = "Host checking failed - %s not an allowed hostname." + +def _get_failure_view(): + """ + Returns the view to be used for host-matching rejections + """ + return get_callable(settings.HOSTMATCH_FAILURE_VIEW) + +class HostMatchMiddleware(object): + """ + Middleware that requires that, if sent, the Host header + be a valid Host header for this site. + """ + + def _accept(self, request): + return None + + def _reject(self, request, reason): + return _get_failure_view()(request, reason=reason) + + def host_match(self, name): + if settings.HOSTMATCH_ALLOWED_HOSTNAMES is None: + return True + else: + return name in settings.HOSTMATCH_ALLOWED_HOSTNAMES + + def process_view(self, request, callback, callback_args, callback_kwargs): + used_host = request.get_host().split(':')[0] + if self.host_match(used_host): + return self._accept(request) + else: + return self._reject(request, REASON_BAD_HOST % (used_host, )) diff --git a/django/views/hostmatch.py b/django/views/hostmatch.py new file mode 100644 index 0000000..1ff281c --- /dev/null +++ b/django/views/hostmatch.py @@ -0,0 +1,79 @@ +from django.http import HttpResponseForbidden +from django.template import Context, Template +from django.conf import settings + +# We include the template inline since we need to be able to reliably display +# this error message, especially for the sake of developers, and there isn't any +# other way of making it available independent of what is in the settings file. + +HOSTMATCH_FAILURE_TEMPLATE = """ + + + + + + 403 Forbidden + + + +
+

Forbidden (403)

+

Hostname matching failed. Request aborted.

+ +

The Host header your browser supplied did not match the hostnames that this site is configured to serve.

+ +
+{% if DEBUG %} +
+

Help

+ {% if reason %} +

Reason given for failure:

+
+    {{ reason }}
+    
+ {% endif %} + +

In general, this can occur when there is a genuine DNS rebinding attack, or when + Django's + host matching has not been used correctly. Make sure you have + appropriately configured settings.HOSTMATCH_ALLOWED_HOSTNAMES.

+ +

You're seeing the help section of this page because you have DEBUG = + True in your Django settings file. Change that to False, + and only the initial error message will be displayed.

+ +

You can customize this page using the HOSTMATCH_FAILURE_VIEW setting.

+
+{% else %} +
+

More information is available with DEBUG=True.

+
+{% endif %} + + +""" + +def hostmatch_failure(request, reason=""): + """ + Default view used when request fails hostmatch protection + """ + from django.middleware.csrf import REASON_NO_REFERER + t = Template(HOSTMATCH_FAILURE_TEMPLATE) + c = Context({'DEBUG': settings.DEBUG, + 'reason': reason, + }) + return HttpResponseForbidden(t.render(c), mimetype='text/html') -- 1.7.2.3