﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
23295	ALLOWED_HOSTS setting is done in the wrong place, should be in a custom middleware	Mark Jones	Collin Anderson	"Right now ALLOWED_HOST checking is done in Request.get_host() which is called by CommonMiddleware. And then called again in fix_location_header(request, response) which means it is impossible to redirect an attacker to fbi.gov and at the same time record their attack because on a redirect get_host is called twice!

Raising a suspicious operation just does not belong in Request.get_host(), it really isn't part of the function that gives you the host back, it is something you do after you get the host information.

This is what I've had to write to work around this problem:

{{{#!python
class CommonMiddleware(DjangoCommonMiddleware):
    """""" In an effort to stop the deluge of ALLOWED_HOSTS emails sent at our software
    by very stupid pentesters, I have decided to record what they are doing, and then
    send them to the fbi.gov site (out of spite).  It shall be interesting to see
    if they actually figure it out or not. (But right now I can't do that because of
    limitations within Django""""""

    def process_request(self, request):

        def get_client_ip(request):
            x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
            if x_forwarded_for:
                ip = x_forwarded_for.split(',')[0]
            else:
                ip = request.META.get('REMOTE_ADDR')
            return ip

        try:
            return super(CommonMiddleware, self).process_request(request)
        except SuspiciousOperation as xcpt:
            if 'ALLOWED_HOSTS' in str(xcpt):
                # We try three options, in order of decreasing preference.
                if settings.USE_X_FORWARDED_HOST and (
                    'HTTP_X_FORWARDED_HOST' in request.META):
                    host = request.META['HTTP_X_FORWARDED_HOST']
                elif 'HTTP_HOST' in request.META:
                    host = request.META['HTTP_HOST']
                else:
                    # Reconstruct the host using the algorithm from PEP 333.
                    host = request.META['SERVER_NAME']
                    server_port = str(request.META['SERVER_PORT'])
                    if server_port != ('443' if request.is_secure() else '80'):
                        host = '%s:%s' % (host, server_port)

                obj = AllowedHostViolation.objects.create(
                    host_attacked=host,
                    url_attacked=request.get_full_path(),
                    attacker=get_client_ip(request)
                )
                response = HttpResponse(""You were hoping to have breached security!""
                                        "" Not today though!""
                                        "" Now smile for the camera, because you've been busted!\n"",
                                        content_type='application/text', status=418)
                response.allowed_host_violation = True
                return response
            raise

    def process_response(self, request, response):
        """""" Have to also do this to keep it from throwing another Suspicious Operation """"""
        if response.allowed_host_violation:
            return response

        return super(CommonMiddleware, self).process_response(request, response)

}}}

This bug is also present in 1.6 but in that case you get a differentiated Exception thrown.  You still can't return a HttpResponseRedirect to send them all to fbi.gov though because the logic is in the wrong place."	Bug	closed	Uncategorized	1.5	Normal	fixed			Unreviewed	1	0	0	0	0	0
