Django's SuspiciousOperation Invalid HTTP_HOST header

96

Po aktualizacji do Django 1.5 zaczęły pojawiać się takie błędy:

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)}>

Ustawiłem ALLOWED_HOSTS = ['.derekkwok.net'] w moim pliku settings.py.

Co tu się dzieje? Czy ktoś udaje Google i uzyskuje dostęp do mojej witryny? A może jest to łagodny przypadek, gdy ktoś nieprawidłowo ustawił swój nagłówek HTTP_HOST?

Derek Kwok
źródło
Czy wiesz, jak to naprawić? W obliczu tego samego problemu. Codzienne rejestrowanie około stu takich błędów. Nie mam pojęcia, czy muszę się tym martwić.
ślepy
3
Ten post na blogu zapewnia dobry sposób na zatrzymanie e-maili: tiwoc.de/blog/2013/03/…
Derek Kwok

Odpowiedzi:

64

Jeżeli twój ALLOWED_HOSTS jest ustawiony prawidłowo, możliwe, że ktoś sonduje Twoją witrynę pod kątem luki, podszywając się pod nagłówek.

W tej chwili programiści Django dyskutują, aby zmienić to z wewnętrznego błędu serwera 500 na odpowiedź 400. Zobacz ten bilet .

Brian Neal
źródło
1
Myślę, że bardziej prawdopodobnym wyjaśnieniem jest to, że roboty sieciowe (roboty) po prostu indeksują publiczne adresy IP na porcie 80 - w takim przypadku chciałbyś na nie zezwolić.
markmnl
16
@markmnl Prawidłowy robot indeksujący nie powinien fałszować nagłówków hostów.
Brian Neal
1
To tylko połączenie przy użyciu adresu IP, a nie nazwy domeny, a adresu IP nie ma w ALLOWED_HOSTS - a przynajmniej tak się ze mną działo - mogłem to powtórzyć, wskazując moją przeglądarkę na adres IP.
markmnl
Tak. W każdej mało ruchliwej witrynie dzieje się to codziennie przez cały dzień. Naprawili to teraz, ale oto aplikacja typu „drop-in”, która sortuje to we wszystkich wersjach wraz z filtrem współczynnika błędów. github.com/litchfield/django-safelogging
s29
Po wdrożeniu mojej witryny w Internecie. Zauważyłem, że wiele osób próbuje uzyskać dostęp do mojej witryny przy użyciu nieprawidłowego hosta. Nie tylko przy użyciu adresu IP. Myślę, że mogą to być osoby próbujące znaleźć witrynę, która nie może obronić się przed atakiem CSRF.
ramwin
130

Jeśli używasz Nginx do przekazywania żądań do Django działającego na Gunicorn / Apache / uWSGI, możesz użyć poniższych opcji, aby zablokować złe żądania. Dzięki @PaulM za sugestię i ten post na blogu za przykład.

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
źródło
7
Byłoby wspaniale, gdybyśmy zobaczyli to jako ulepszenie podpowiedzi do dokumentacji :)
Paul McMillan
1
@webjunkie, Z Twojego linku, „Są przypadki, w których po prostu nie możesz uniknąć użycia if, na przykład jeśli chcesz przetestować zmienną, która nie ma równoważnej dyrektywy”. Mój przykład używa go poprawnie i dobrze działa w moim środowisku produkcyjnym. Podsumowując, zrób to w ten sposób! :)
Brent O'Connor
2
Cóż, możesz łatwo tego uniknąć: po prostu podaj tylko nazwę serwera, której potrzebujesz, a resztą zajmie się domyślny program obsługi serwera.
webjunkie
1
Zobacz tę odpowiedź dla podobnej konfiguracji Apache: stackoverflow.com/a/18792080
Denilson Sá Maia
1
Z linku udostępnionego przez webjunkie: „Dyrektywa, jeśli ma problemy, gdy jest używana w kontekście lokalizacji”. W przykładzie podanym przez Brenta zastosowano ifwnętrze serverbloku, a nie locationblok. Czy to oznacza, że ifw tym przypadku jest w porządku?
Brian Buck
31

Korzystając z Nginx, możesz skonfigurować swoje serwery w taki sposób, aby w pierwszej kolejności żądania wysyłane były tylko do hostów, które chcesz dostać do Django. To nie powinno już powodować żadnych błędów 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
źródło
2
Podoba mi się to podejście w porównaniu z ifpodejściem sugerowanym przez Brent, ale nie mogę go zmusić do pracy z portem 443. Próbowałem naśladować twoją sugestię (ze zmienionym portem nasłuchiwania), a moja rzeczywista witryna SSL nie ładuje się - to zostaje ujęty przez ten wpis, który dodałem. Jakieś pomysły, jak to naprawić?
Dolan Antenucci
1
Inny plakat na ServerFault.com miał podobne problemy, więc postępowałem zgodnie z jego zaleceniem dotyczącym podejścia if-statement tylko dla ruchu 443
Dolan Antenucci
1
Wygląda na to, że musisz podać ścieżkę do plików certyfikatów, jeśli chcesz również przechwytywać żądania SSL (nawet jeśli chcesz po prostu odrzucić): 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
Co zwróci Nginx, jeśli HOST żądania jest nieprawidłowy? 50x czy 40x?
laike9m,
Co jest więcej w tej konfiguracji? Mam ustawioną nazwę serwera zarówno w sekcji przekierowań, jak i aplikacji, nadal otrzymuję Invalid HTTP_HOST header(z Django 1.8.x)
Csaba Toth
16

Zostało to naprawione w nowszych wersjach Django, ale jeśli używasz wersji, której dotyczy problem (np. 1.5), możesz dodać filtr do programu obsługi rejestratora, aby się ich pozbyć, jak opisano w tym wpisie na blogu.

Spojler:

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
źródło
1
Czy jest jakiś link do poprawki lub wersji, gdzie zaimplementowano? Dzięki
Marc
1
Miałem to w wersji 2.0.5
mehmet
Nie jest to naprawione w nowszych wersjach Django. Używam Django 2.0.10
javidazac