Django ERROR: Invalid HTTP_HOST header: u'/run

2019-02-04 09:26发布

问题:

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

回答1:

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 fix 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,
    },


回答2:

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.



回答3:

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.