Cabeçalho HTTP_HOST inválido do Django's SuspiciousOperation

95

Depois de atualizar para Django 1.5, comecei a receber erros como este:

Traceback (most recent call last):

File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 92, in get_response
response = middleware_method(request)

File "/usr/local/lib/python2.7/dist-packages/django/middleware/common.py", line 57, in process_request
host = request.get_host()

File "/usr/local/lib/python2.7/dist-packages/django/http/request.py", line 72, in get_host
"Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host)

SuspiciousOperation: Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): www.google.com

<WSGIRequest
path:/,
GET:<QueryDict: {}>,
POST:<QueryDict: {}>,
COOKIES:{},
META:{'CONTENT_LENGTH': '',
'CONTENT_TYPE': '',
'DOCUMENT_ROOT': '/etc/nginx/html',
'HTTP_ACCEPT': 'text/html',
'HTTP_HOST': 'www.google.com',
'HTTP_PROXY_CONNECTION': 'close',
'HTTP_USER_AGENT': 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
'PATH_INFO': u'/',
'QUERY_STRING': '',
'REMOTE_ADDR': '210.245.91.104',
'REMOTE_PORT': '49347',
'REQUEST_METHOD': 'GET',
'REQUEST_URI': '/',
u'SCRIPT_NAME': u'',
'SERVER_NAME': 'www.derekkwok.net',
'SERVER_PORT': '80',
'SERVER_PROTOCOL': 'HTTP/1.0',
'uwsgi.node': 'derekkwok',
'uwsgi.version': '1.4.4',
'wsgi.errors': <open file 'wsgi_errors', mode 'w' at 0xb6d99c28>,
'wsgi.file_wrapper': <built-in function uwsgi_sendfile>,
'wsgi.input': <uwsgi._Input object at 0x953e698>,
'wsgi.multiprocess': True,
'wsgi.multithread': False,
'wsgi.run_once': False,
'wsgi.url_scheme': 'http',
'wsgi.version': (1, 0)}>

Eu defini ALLOWED_HOSTS = ['.derekkwok.net'] no meu arquivo settings.py.

O que está acontecendo aqui? É alguém fingindo ser Google e acessando meu site? Ou é um caso benigno de alguém configurando seu cabeçalho HTTP_HOST incorretamente?

Derek Kwok
fonte
Você descobriu como consertar isso? Enfrentando o mesmo problema. Registrando cerca de cem desses erros todos os dias. Não tenho ideia se é algo com que eu preciso me preocupar.
blinduck
3
Esta postagem do blog fornece uma boa maneira de interromper os e-mails: tiwoc.de/blog/2013/03/…
Derek Kwok

Respostas:

64

Se o seu ALLOWED_HOSTSestiver definido corretamente, é possível que alguém esteja investigando a vulnerabilidade em seu site falsificando o cabeçalho.

Há uma discussão agora pelos desenvolvedores do Django para mudar isso de um erro interno de 500 para uma resposta 400. Veja este tíquete .

Brian Neal
fonte
1
Acho que uma explicação mais provável são os rastreadores da web (robôs) simplesmente rastreando endereços IP públicos na porta 80 - nesse caso, você desejaria permiti-los.
markmnl
16
@markmnl Um rastreador legítimo da web não deve forjar cabeçalhos de host.
Brian Neal de
1
É só conectar usando o endereço IP, não o nome do domínio, e o endereço IP não está no ALLOWED_HOSTS - ou pelo menos é o que estava acontecendo comigo - eu poderia reproduzi-lo apontando meu navegador para o endereço IP.
markmnl
Sim. E em qualquer site meio ocupado, isso acontece o dia todo, todos os dias. Eles consertaram agora, mas aqui está um aplicativo "drop-in" que classifica todas as versões junto com um filtro de taxa de erro. github.com/litchfield/django-safelogging
s29
Depois de implantar meu site na internet. Descobri que muitas pessoas estão tentando acessar meu site usando um host inválido. Não usando apenas o endereço IP. Acho que podem ser algumas pessoas tentando encontrar um site que não pode defender um ataque csrf.
ramwin
130

Se você estiver usando Nginx para encaminhar solicitações ao Django em execução no Gunicorn / Apache / uWSGI, poderá usar o seguinte para bloquear solicitações incorretas. Obrigado a @PaulM pela sugestão e esta postagem no blog por um exemplo.

upstream app_server {
    server unix:/tmp/gunicorn_mydomain.com.sock fail_timeout=0;
}

server {

    ...

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

    location  / {
        proxy_pass               http://app_server;
        ...
    }

}
Brent O'Connor
fonte
7
Seria excelente ver isso como uma melhoria na dica da dica de documentos :)
Paul McMillan
1
@webjunkie, em seu link, "Existem casos em que você simplesmente não pode evitar o uso de um if, por exemplo, se você precisar testar uma variável que não possui uma diretiva equivalente." Meu exemplo o usa corretamente e funciona bem em meu ambiente de produção. Portanto, para concluir, FAÇA assim! :)
Brent O'Connor
2
Bem, você pode evitá-lo facilmente: apenas especifique apenas o server_name de que você precisa e deixe o resto ser tratado por um manipulador de servidor padrão.
webjunkie
1
Veja esta resposta para uma configuração Apache semelhante: stackoverflow.com/a/18792080
Denilson Sá Maia
1
Do link fornecido por webjunkie: "A diretiva se tem problemas quando usada no contexto de localização". O exemplo dado por Brent usa o ifdentro do serverbloco e não no locationbloco. Isso significa que ifestá tudo bem neste caso?
brian buck,
31

Ao usar o Nginx, você pode configurar seus servidores de uma forma que apenas faça solicitações aos hosts que você deseja que o Django chegue. Isso não deve gerar mais erros SuspiciousOperation.

server {
    # default server

    listen 80;
    server_name _ default;

    return 444;
}
server {
    # redirects

    listen 80;
    server_name example.com old.stuff.example.com;

    return 301 http://www.example.com$request_uri;
}
server {
    # app

    listen 80;
    server_name www.example.com; # only hosts in ALLOWED_HOSTS here

    location  / {
        # ...
    }
    # ... your config/proxy stuff
}
webjunkie
fonte
2
Eu gosto dessa abordagem em vez de usar a ifabordagem sugerida por Brent, mas não consigo fazê-la funcionar com a porta 443. Tentei imitar sua sugestão (com a porta de escuta alterada) e meu site SSL real não carrega - ele é capturado por esta entrada que eu adicionei. Alguma ideia de como consertar?
Dolan Antenucci
1
Outro postador no ServerFault.com teve problemas semelhantes, então segui sua recomendação sobre a abordagem da instrução if para tráfego 443 apenas
Dolan Antenucci
1
Parece que você tem que especificar o caminho para os arquivos de certificado se quiser capturar solicitações SSL também (mesmo que você queira apenas descartar): server { listen 80 default_server; listen 443; server_name _; ssl_certificate /path/to/file.crt; ssl_certificate_key /path/to/file.key; return 444; }
n__o
O que o Nginx retornará se o HOST da solicitação for inválido? 50x ou 40x?
laike9m
Qual é o extra nesta configuração? Eu tenho o nome do servidor definido nos redirecionamentos e na seção do aplicativo, ainda consigo Invalid HTTP_HOST header(com Django 1.8.x)
Csaba Toth
16

Isso é corrigido nas versões mais recentes do Django, mas se você estiver usando uma versão afetada (por exemplo, 1.5), você pode adicionar um filtro ao seu manipulador de log para se livrar deles, conforme descrito neste post de blog.

Spoiler:

from django.core.exceptions import SuspiciousOperation

def skip_suspicious_operations(record):
  if record.exc_info:
    exc_value = record.exc_info[1]
    if isinstance(exc_value, SuspiciousOperation):
      return False
  return True

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse',
        },
        # Define filter
        'skip_suspicious_operations': {
            '()': 'django.utils.log.CallbackFilter',
            'callback': skip_suspicious_operations,
        },
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            # Add filter to list of filters
            'filters': ['require_debug_false', 'skip_suspicious_operations'],
            'class': 'django.utils.log.AdminEmailHandler'
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}
mgalgs
fonte
1
Qualquer link para a correção ou versão onde implementado? Thx
Marc
1
Eu tinha na versão 2.0.5
mehmet
Isso não é corrigido nas versões mais recentes do Django. Estou usando o Django 2.0.10
javidazac