2

I have a Django app hosted on AWS. I use Elastic Beanstalk and use a .ebextensions/django.config file to input all my custom server side settings.

I have ALLOWED_HOSTS set up so that if anybody tries to connect to my site from an invalid host header they get blocked...by Django.

I get all kinds of Django error logging emails saying Invalid HTTP_HOST header: 123.456.789. -- essentially bots / scanners trying to connect and/or upload malicious content.

I'd like to block these bad requests at a server side as it seems more secure to have that extra blocking layer, and I don't like getting all the emails.

In the Django docs they write that "[they recommend] configuring your web server to ensure it validates incoming HTTP Host headers." I'd like to do that in my .ebextensions/django.config file.

Here is my current .ebextensions/django.config file:

container_commands:
  01_migrate:
    command: "python manage.py migrate --noinput"
  02_collectstatic:
    command: "python manage.py collectstatic --noinput"

option_settings:
  aws:elasticbeanstalk:container:python:
    WSGIPath: myapp/wsgi.py
  aws:elasticbeanstalk:container:python:staticfiles:
    /static/: static/

files:
  "/etc/httpd/conf.d/ssl_rewrite.conf":
      mode: "000644"
      owner: root
      group: root
      content: |
          WSGIApplicationGroup %{GLOBAL}
          RewriteEngine On
          <If "-n '%{HTTP:X-Forwarded-Proto}' && %{HTTP:X-Forwarded-Proto} != 'https'">
          RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
          </If>
          Header always set Referrer-Policy no-referrer
          Header always set Strict-Transport-Security "max-age=14400; includeSubdomains;"

I believe it is all apache config. I found this SO answer on this subject that writes "To deny requests with no HOST set you can use:"

SetEnvIfNoCase Host .+ VALID_HOST
Order Deny,Allow
Deny from All
Allow from env=VALID_HOST

However I'm unsure of what that code is doing (and if it's what I need), and how to translate it into .ebextensions.

Ultimately I'd like to find out what I can add to my .ebextensions file to make it validate HTTP_HOST headers before they reach Django.

bones225
  • 1,488
  • 2
  • 13
  • 33
  • Did you manage to find a solution for this? – Simone Aug 25 '20 at 13:19
  • 1
    @simone yes and no. I use AWS WAF to block the invalid hosts, which I believe better than at the server level. However, I did not answer this specific question, as it became unimportant once I enabled WAF. – bones225 Aug 25 '20 at 18:20
  • Cool stuff. I am reading the documentation and trying to understand if it only allows blocking hosts. I would like to block all the hosts but my domain name. – Simone Aug 25 '20 at 19:05
  • @Simone Which documentation? WAF or Apache? If WAF, you have to do a custom rule. If Apache .. well... I guess thats the question above xD – bones225 Aug 25 '20 at 19:54
  • I had a look at some info about WAF. I see, it seems that WAF can whitelist hosts as well, and block all non white listed. I wish I could write a config file for elastic beanstalk to do that as in here https://aws.amazon.com/premiumsupport/knowledge-center/elastic-beanstalk-host-attacks/ – Simone Aug 25 '20 at 20:46
  • 1
    Definitely cool if you can block in both places. To me there seems like 3 levels, Django, Apache, and WAF. Django blocks with allowed_hosts, which blocks the security problem, but still requires app bandwidth. WAF blocks the "10000 requests / second" spam bot problem, even if the custom WAF rule isnt perfect. That article seems like a replacement for using the UX to create the WAF rule, not an apache blocker. – bones225 Aug 25 '20 at 20:53

1 Answers1

-1

I think you need Django's middleware.

You should create middleware file in your app.

middleware.py (create this file in you django app)

from django.http import JsonResponse

MY_HEADER = 'HTTP_HOST'

class InputCheckMiddleware:

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # <add your logic here>, this is before your code reach django views.
        headers = {k: v for k, v in request.META.items() if k.startswith('HTTP_')} # this will return all http headers
        if MY_HEADER not in headers:
            # make all kinds of validations here
            # and return HttpResponse (or subclass. JsonResponse for example) here.
            # this case request does not reach in your view.
            return JsonResponse({'status_message': 'bad request'})
        response = self.get_response(request)
        return response

Now, you should add middleware class path in your settings.py file inside MIDDLEWARE list.

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # your own middleware
    'myapp.middleware.InputCheckMiddleware',
]

Middlewares are called per-request. If you use Django1.8, Middleware class structure is different, just check in docs.

gachdavit
  • 1,223
  • 1
  • 6
  • 7