30

I know that there are a lot of questions like this on SO, but none of them appear to answer my particular issue.

I understand that Django's ALLOWED_HOSTS value is blocking any requests to port 80 at my IP that do not come with the appropriate Host: value, and that when a request comes in that doesn't have the right value, Django is dropping me an email. I also know about the slick Nginx hack to make this problem go away, but I'm trying to understand the nature of one such request and determine whether this is a security issue I need to worry about.

Requests like these make sense:

[Django] ERROR: Invalid HTTP_HOST header: '203.0.113.1'. You may need to add u'203.0.113.1' to ALLOWED_HOSTS.

But this one kind of freaks me out:

[Django] ERROR: Invalid HTTP_HOST header: u'/run/my_project_name/gunicorn.sock:'.

Doesn't this mean that the requestor sent Host: /run/my_project_name/gunicorn.sock to the server? If so, how do they have the path name for my .sock file? Is my server somehow leaking this information?

Additionally, as I'm running Django 1.6.5, I don't understand why I'm receiving these emails at all, as this ticket has been marked fixed for some time now.

Can someone shed some light on what I'm missing?

This is my settings.LOGGING variable:

{
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}
    },
    'formatters': {
        'simple': {'format': '%(levelname)s %(message)s'},
        'verbose': {'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'}
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
            'level': 'DEBUG'
        },
        'mail_admins': {
            'class': 'django.utils.log.AdminEmailHandler',
            'filters': ['require_debug_false'],
            'level': 'ERROR'
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True
        },
        'my_project_name': {
            'handlers': ['console'], 
            'level': 'DEBUG'
        }
    },
    'version': 1
}

And here's my nginx config:

worker_processes 1;
pid /run/nginx.pid;
error_log /var/log/myprojectname/nginx.error.log debug;
events {
}
http {
  include mime.types;
  default_type application/octet-stream;
  access_log /var/log/myprojectname/nginx.access.log combined;
  sendfile on;
  gzip on;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_min_length 500;
  gzip_disable "MSIE [1-6]\.";
  gzip_types text/plain text/html text/xml text/css
             text/comma-separated-values
             text/javascript application/x-javascript
             application/atom+xml;
  upstream app_server {
    server unix:/run/myprojectname/gunicorn.sock fail_timeout=0;
  }
  server {
    listen 80 default;
    listen [::]:80 default;
    client_max_body_size 4G;
    server_name myprojectname.mydomain.tld;
    keepalive_timeout 5;
    root /var/www/myprojectname;
    location / {
      try_files $uri @proxy_to_app;
    }
    location @proxy_to_app {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /tmp;
    }
  }
}

Lastly, I found this in my nginx access log. It corresponds to the emails coming through that complain about /run/myprojectname/gunicorn.sock being an invalid HTTP_HOST header.*

This was all on one line of course:

2014/09/05 20:38:56 [info] 12501#0: *513 epoll_wait() reported that client
prematurely closed connection, so upstream connection is closed too while sending
request to upstream, client: 54.84.192.68, server: myproject.mydomain.tld, request:
"HEAD / HTTP/1.0", upstream: "http://unix:/run/myprojectname/gunicorn.sock:/"

Obviously I still don't know what this means though :-(

  • Update #1: Added my settings.LOGGING
  • Update #2: Added my nginx config
  • Update #3: Added an interesting line from my nginx log
  • Update #4: Updated my nginx config
Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
Daniel Quinn
  • 6,010
  • 6
  • 38
  • 61

3 Answers3

29

Seems like

proxy_set_header Host $http_host

should be changed to

proxy_set_header Host $host

and server_name should be set appropriately to the address used to access the server. If you want it to catch all, you should use server_name www.domainname.com "" (doc here).

I'm not certain, but I think what you're seeing happens if the client doesn't send a Host: header. Since nginx receives no Host: header, no Host: header gets passed up to gunicorn. At this point, I think gunicorn fills in the Host: as the socket path and tells Django this, since that's the connection used. Using $host and setting the server_name in nginx should ensure the Host: is correctly passed to gunicorn and resolve this problem.

As for the email, according to the commit in the ticket you linked, it looks like emails are still being sent for disallowed hosts. Added to the doc was also a suggested a way to disable the emails being sent:

    'loggers': {
        'django.security.DisallowedHost': {
        'handlers': ['null'],
        'propagate': False,
        }
    },
Tobit
  • 406
  • 7
  • 19
user193130
  • 8,009
  • 4
  • 36
  • 64
  • 2
    This sounds plausible, but I'd like to test such a request to see if I can reproduce it. Running `curl 203.0.113.1` currently results in an email claiming that 203.0.113.1 is an invalid host header, and `curl 203.0.113.1 -H 'Host:'` results in no mail at all. Can you explain how I might reproduce the request that's giving me that gunicorn email? – Daniel Quinn Sep 07 '14 at 09:28
  • 1
    And to the second part of the question, why are these emails coming through at all? If ALLOWED_HOSTS is rejecting them already, why is Django sending me emails about it? – Daniel Quinn Sep 07 '14 at 09:28
  • I managed to recreate the gunicorn email by using telnet and sending `HEAD / HTTP/1.0`. So I'm no longer worried about my config leaking things. However, even after setting `server_name` to `myproject.mydomain.tld` and setting `proxy_set_header Host $host`, the emails still keep coming in :-( I've updated the nginx config to show the new values. – Daniel Quinn Sep 07 '14 at 10:10
  • @DanielQuinn Yeah, pretty sure it's not leaking the socket path outside... it's something passed along internally somewhere. So do you still see something like `[Django] ERROR: Invalid HTTP_HOST header: u'/run/my_project_name/gunicorn.sock:'.`? I would have expected that it won't show that error anymore after updating the settings according to the above. Also, updated answer for how to stop emails from being sent for this error. – user193130 Sep 08 '14 at 05:58
  • 1
    Thanks for the Django snippet. I had no idea that I needed to explicitly suppress these sorts of messages. And you're right that the gunicorn emails have stopped coming in, though I figured out why: now issuing a HEAD request to my ip for `/` just returns my website's front page head data. You'd think that ALLOWED_HOSTS would reject HEAD requests to disallowed hosts too. – Daniel Quinn Sep 08 '14 at 07:01
  • and don't forget to complete `proxy_set_header Host $host` with semicolon. I just wasted few hours to realize where the problem was – Max L Apr 29 '17 at 23:50
  • I had the same issue, and I was able to reproduce it reliably with `curl -I https:// --http1.0 -H "Host:" --insecure`. Note http**s**. – Wisco crew Dec 24 '20 at 04:45
19

I have come across some comments that suggest that suppressing the emails is not a good idea because it does not directly address the issue. The most effective solution I have found is to addd the following to your nginx settings:

server {

    ...

    ## Deny illegal Host headers
    if ($host !~* ^(mydomain.com|www.mydomain.com)$ ) {
        return 444;
    }
}

For more information: https://snakeycode.wordpress.com/2015/05/31/django-error-invalid-http_host-header/

The blog post references this question.

Ernest Jumbe
  • 758
  • 9
  • 21
  • 2
    This is the best answer I've found. Thanks. – Teekin Jun 18 '19 at 10:38
  • 1
    @Teekin I suggest you take a look at this answer https://stackoverflow.com/a/44118369/785808 since it's from the official docs. – Ernest Jumbe Jun 19 '19 at 12:02
  • 2
    I still like your answer better, because it contains the solution in the file of the problematic website itself, without regard for its environment. It's something that can be included in a template for setting up new instances, for example. – Teekin Jun 19 '19 at 13:33
  • 1
    Someone was sneaking into our server and this solution worked like charm in blocking unwanted incoming requests with malformed headers. – Om Prakash Jul 03 '21 at 18:02
9

I know this is an old question, but the issue happened to me just today. The recommended solution on Django docs is to add a "catch all" nginx server in your nginx config:

server {
    listen 80 default_server;
    return 444;
}

The official nginx docs recommend the same solution, give or take some syntax nuances.

This way, the request doesn't go to django, the connection gets shutdown immediately when nginx receives a malformed request.

Laurent S
  • 4,106
  • 3
  • 26
  • 50
  • 3
    This should be the accepted answer, especially since it references the solution in the Django docs, and makes far more sense than suppressing emails / logging. Updated link: https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/#environment-specific-settings – shacker Mar 31 '19 at 07:04
  • I had the "Deny illegal Host headers" solution from [Ernest Jumbe's answer](https://stackoverflow.com/a/37235323/777980) in my nginx config, and still was getting errors from gunicorn. This answer fixed it for good. Test it with [this.](https://stackoverflow.com/questions/25370868/django-error-invalid-http-host-header-u-run-myprojectname-gunicorn-sock#comment115684708_25708262) – Wisco crew Dec 24 '20 at 04:50
  • I had this upvoted from years ago, but for some reason trying it now it shuts down all of my hosts and not just the undefined ones like I would expect. – Chase Roberts Mar 10 '22 at 22:06