Nagłówki bezpieczeństwa HTTP prosto wyjaśnione: które chronią, a które tylko są

Spis treści
Każda strona, którą serwer dostarcza, przychodzi z zestawem nagłówków – linijek biegnących przed treścią i mówiących przeglądarce, jak potraktować to, co nastąpi. Garść z nich dotyczy bezpieczeństwa.
Dają się łatwo sprawdzić i łatwo sprawdzić źle. Policzenie, które nagłówki są obecne, trwa sekundę i daje liczbę wyglądającą jak wynik. Ta liczba nie mówi prawie nic, bo nagłówek może być obecny, poprawnie zapisany i całkowicie bez działania.

Czym są nagłówki ochronne
Cztery z nich niosą główny ciężar i każdy odpowiada na inne pytanie.
Content-Security-Policy wypisuje, skąd strona może wczytywać kod. Wszystkiego, czego na liście nie ma, przeglądarka odmawia – to najważniejsza obrona przed podrzuconymi skryptami.
Strict-Transport-Security poleca przeglądarce brać odtąd połączenie szyfrowane, a nigdy otwarte – także wtedy, gdy odnośnik, zakładka albo wpisany adres mówią inaczej.
X-Frame-Options rozstrzyga, czy strona może zostać osadzona w ramce na cudzej witrynie, i tym samym zapobiega klikaniu w coś niewidocznego.
Referrer-Policy rozstrzyga, ile z bieżącego adresu zostaje przekazane, gdy odnośnik prowadzi ze strony dalej.
Piąty, Permissions-Policy, wyłącza funkcje przeglądarki, których strona nie potrzebuje – kamerę, mikrofon, położenie. Jest najrzadszy z pięciu i najmniej skłonny cokolwiek popsuć.
Reguła, którą znosi unsafe-inline
Content-Security-Policy jest najsilniejszy z pięciu i najczęściej przegrywa z własną wartością.
Zagrożeniem, dla którego istnieje, jest podrzucony skrypt: coś, co ktoś wpisał albo napastnik podłożył, ląduje w stronie i działa, jakby do niej należało. CSP zapobiega temu, wymieniając źródła, z których przeglądarce wolno wykonywać kod – a skrypt wpisany wprost w stronę nie ma źródła, które dałoby się wymienić.
Właśnie na to 'unsafe-inline' znowu zezwala. Słowo nie stoi w wartości bez powodu – mówi, co robi – a stoi tam dlatego, że strona pełna małych osadzonych skryptów bez tego zezwolenia przestaje działać. Dodanie go to najszybszy sposób, by zepsuta strona znów ruszyła, i oddaje dokładnie to zezwolenie, którego odmówienie było sensem reguły.
Reguła z 'unsafe-inline' blokuje jeszcze kilka rzeczy: na przykład kod doczytywany z obcych adresów. Przeciw atakowi, dla którego CSP głównie istnieje, nie robi prawie nic.
'unsafe-eval' to ta sama historia w węższym przypadku, a default-src równe * to ta sama historia całkiem bez pozorów.
Jak długi musi być okres HSTS
Strict-Transport-Security ma jedną liczbę, od której wszystko zależy: max-age, w sekundach.
Mówi ona, jak długo przeglądarka ma pamiętać polecenie. Do upływu tego czasu żadne otwarte, nieszyfrowane zapytanie do tej domeny już nie idzie – przeglądarka przepisuje je, zanim opuści komputer. To cała ochrona i trwa dokładnie tyle, co pamięć.
max-age równe 300 pamięta przez pięć minut. Formalnie poprawne, obecne w każdym sprawdzeniu liczącym nazwy i bezużyteczne dla odwiedzin następnego dnia. Zwykle zaleca się rok, zapisany jako 31536000.
Liczą się dwa dodatki. includeSubDomains rozciąga regułę na każdą poddomenę i zamyka tym samym lukę, przez którą zapomniany system testowy w tej samej domenie zostaje osiągalny bez szyfrowania. A preload prosi o wbudowanie domeny wprost w przeglądarki, tak by ochrona objęła i pierwsze odwiedziny – tę jedną chwilę, w której HSTS inaczej pomóc nie może, bo nic jeszcze nie zostało zapamiętane.
Do tego ostatniego kroku należy ostrzeżenie: wpisy trudno usunąć, a zanim usunięcie dotrze do przeglądarek, mijają miesiące. Pasuje do domeny, której szyfrowanie jest ustalone, a nie do takiej, która dopiero jest porządkowana.

Gdy dwa nagłówki regulują to samo
Osadzanie w ramkach uregulowane jest podwójnie, przez dwa różnej daty nagłówki, a reguła tego, który wygrywa, regularnie zaskakuje.
X-Frame-Options jest starszy, rozumiany wszędzie, i oferuje DENY albo SAMEORIGIN. CSP ma frame-ancestors, który wykonuje to samo zadanie, ale listą adresów zamiast dwiema stałymi możliwościami.
Gdy obecne są oba, wygrywa frame-ancestors, a X-Frame-Options zostaje całkowicie pominięty. Nie scalony, nie połączony – pominięty. Stronę z X-Frame-Options: DENY i z CSP zawierającym frame-ancestors * może osadzić każdy, a surowszy z dwóch nagłówków nie ma nic do powiedzenia.
To błąd kryjący się najlepiej, bo oba nagłówki są obecne, oba poprawnie zapisane, a sprawdzenie liczące nazwy zgłasza dwa zabezpieczenia tam, gdzie jest jedno.
Co idzie razem z każdym kliknięciem
Referrer-Policy jest najcichszy z pięciu i ma najbardziej codzienne następstwa.
Gdy odnośnik prowadzi ze strony dalej, przeglądarka mówi celowi, skąd przyszły odwiedziny. Ile powie, ustala ten nagłówek. unsafe-url przekazuje pełny adres wraz ze ścieżką, no-referrer nie przekazuje nic, a strict-origin-when-cross-origin przekazuje pełny adres w obrębie tej samej witryny, a obcym tylko domenę.
W ścieżce siedzi to drażliwe. /pl/konto/zamowienia/48120 zdradza numer klienta, odnośnik do zresetowania hasła zdradza jednorazowy klucz, a strona wyszukiwania zdradza szukane słowo. Wszystko to idzie do witryny, do której prowadzi odnośnik, w zupełnie zwyczajnym nagłówku, bez tego, żeby cokolwiek poszło źle.
Ostatnia z trzech wartości to rozsądne ustawienie domyślne i nowoczesne przeglądarki stosują je same, gdy nagłówka brak. Tym samym wyraźnie ustawione unsafe-url jest gorsze niż brak nagłówka: zastępuje dobre ustawienie domyślne złą decyzją.
Co pokazuje jedno pobranie
Wszystko to jest jawne. Nagłówki przychodzą z każdą odpowiedzią, dla dowolnego adresu, a odczytanie ich kosztuje jedno zapytanie.
Warte zadania są cztery pytania. Czy CSP istnieje i czy jego wartość oddaje to, co zabrał. Czy okres HSTS jest dość długi, by przetrwać między dwiema wizytami. Czy dwa nagłówki od ramek nie przeczą sobie i czy ten wygrywający mówi to, co zamierzono. Oraz czy ustawienie referrera przekazuje więcej, niż cel potrzebuje.
Sprawdzarka nagłówków bezpieczeństwa HTTP odpowiada na wszystkie cztery dla dowolnego adresu. Pobiera stronę raz, odczytuje nagłówki tak, jak czyta je przeglądarka, śledzi po drodze przekierowania i ocenia wartości, zamiast liczyć nazwy.
Użyteczny wynik nie jest oceną. Jest krótką listą nagłówków, które są obecne i nic nie robią – bo właśnie na te nikt nie spojrzy drugi raz.
Pytania i odpowiedzi
Jak pozbyć się 'unsafe-inline’ bez przenoszenia każdego osadzonego skryptu poza stronę?
Za pomocą wartości nonce albo skrótów (hashy). Nonce to losowa wartość, która stoi w CSP jako 'nonce-…’ oraz jako atrybut każdego dozwolonego skryptu; przeglądarka wykonuje wtedy tylko te osadzone skrypty, które niosą tę wartość. Skrót (’sha256-…’) dopuszcza skrypt o dokładnie tej treści. Podrzucony skrypt ani nie zna tej wartości, ani nie pasuje do skrótu, i właśnie to przywraca ochronę, którą 'unsafe-inline’ zniosło.
Gdy tylko w regule pojawi się nonce albo skrót, aktualne przeglądarki ignorują stojące obok 'unsafe-inline’. Może ono więc zostać jako rozwiązanie awaryjne dla bardzo starych przeglądarek, nie osłabiając ochrony w pozostałych.
Wynikają z tego dwa ograniczenia. Nonce musi być tworzony na nowo dla każdej odpowiedzi; pamięć podręczna stron, która wszystkim podaje ten sam HTML z tym samym nonce, czyni go znanym, a więc bezwartościowym, i dlatego skróty lepiej współpracują z pamięcią podręczną. Poza tym atrybutów zdarzeń, takich jak onclick, nonce w ogóle nie obejmuje, a skróty tylko z dodatkowym słowem kluczowym 'unsafe-hashes’; czystszą drogą jest przeniesienie ich do skryptów. Dopóki wszystko nie jest gotowe, nagłówek Content-Security-Policy-Report-Only pokazuje, co reguła by zablokowała, niczego nie blokując.
Czy nagłówek HSTS przesłany nieszyfrowanym połączeniem działa?
Nie. Przeglądarki uwzględniają Strict-Transport-Security tylko w odpowiedziach przez HTTPS, a w odpowiedzi nieszyfrowanej pomijają ten nagłówek, bo każdy po drodze mógłby go tam wstawić albo usunąć. Odpowiedź pod http:// powinna więc jedynie przekierowywać na HTTPS, a nagłówek należy umieścić w odpowiedzi, do której prowadzi to przekierowanie.
Co się dzieje, gdy CSP wysyłają jednocześnie serwer WWW i aplikacja?
Obowiązują obie reguły. Przeglądarka nie scala ich w jedną wspólną, lecz sprawdza każdą osobno, a zasób zostaje wczytany tylko wtedy, gdy zezwalają na niego wszystkie. Druga, luźniejsza CSP nie może więc osłabić surowej; zapomniana surowa reguła, na przykład w konfiguracji serwera WWW, blokuje natomiast rzeczy, na które aplikacja wyraźnie zezwala we własnej regule.
Ze Strict-Transport-Security jest inaczej: gdy nagłówek przychodzi dwa razy, przeglądarka zgodnie z RFC 6797 przetwarza tylko pierwszy. Przy nagłówkach ustawianych w kilku miejscach, na przykład w serwerze WWW, we wtyczce i w CDN, sprawdzać należy faktycznie dostarczoną odpowiedź, a nie poszczególne konfiguracje.
Czy frame-ancestors da się ustawić także w elemencie meta?
Nie. CSP w elemencie meta nie obsługuje frame-ancestors, podobnie jak report-uri i sandbox, a X-Frame-Options również działa tylko jako prawdziwy nagłówek. Ochrona przed osadzaniem wymaga więc zawsze ustawienia na serwerze albo w CDN.