Logo David Burdelak
Cybersecurity

Nagłówki bezpieczeństwa HTTP w praktyce – jak prosto zabezpieczyć serwer przed atakami?

Konfiguracja nagłówków bezpieczeństwa HTTP HSTS CSP X-Frame-Options na serwerze www

Nagłówki bezpieczeństwa to kilka linijek w konfiguracji serwera, które mówią przeglądarce, jak ma się obchodzić ze stroną: czy wolno ją osadzać w ramkach, skąd może ładować skrypty, czy zawsze ma łączyć się przez HTTPS. Same nie naprawią podatności w kodzie, ale potrafią zablokować skutki wielu ataków. W tym wpisie pokazuję, które nagłówki ustawiam w każdym projekcie, jak wdrażam Content-Security-Policy, żeby niczego nie zepsuć, i jak sprawdzam efekt.

Zestaw nagłówków, który ustawiam zawsze

NagłówekMoja wartośćPrzed czym chroni
Strict-Transport-Securitymax-age=31536000; includeSubDomainsprzed połączeniem przez HTTP i przechwyceniem ruchu
Content-Security-Policydopasowana do strony, zawsze z frame-ancestorsprzed wykonaniem wstrzykniętych skryptów i osadzaniem w ramkach
X-Content-Type-Optionsnosniffprzed zgadywaniem typu pliku przez przeglądarkę
X-Frame-OptionsSAMEORIGINprzed clickjackingiem w starszych przeglądarkach
Referrer-Policystrict-origin-when-cross-originprzed przekazywaniem pełnych adresów, np. z tokenami, innym serwisom
Permissions-Policycamera=(), microphone=(), geolocation=()przed użyciem funkcji urządzenia przez skrypty zewnętrzne

Nagłówka X-XSS-Protection już nie dodaję. Przeglądarki wycofały filtr, który obsługiwał, a w starszych wersjach mógł on wręcz tworzyć nowe luki. Jeśli serwer go wysyła, ustawiam wartość 0 albo usuwam go całkowicie.

HSTS bez ryzyka

HSTS sprawia, że przeglądarka przez określony czas w ogóle nie próbuje łączyć się ze stroną przez HTTP. To skuteczne, ale nieodwracalne po stronie użytkownika: jeśli któraś subdomena nie działa przez HTTPS, przestanie być dostępna dla każdego, kto odwiedził stronę główną. Dlatego zaczynam od krótkiego max-age, np. jednego dnia, sprawdzam wszystkie subdomeny, a dopiero potem ustawiam rok. Wcześniej upewniam się, że certyfikat działa poprawnie wszędzie i na stronie nie ma błędów certyfikatu SSL i mixed content.

Parametr preload i zgłoszenie domeny na listę wbudowaną w przeglądarki stosuję tylko wtedy, gdy klient jest świadomy, że wycofanie się z tej listy trwa miesiącami.

Content-Security-Policy krok po kroku

CSP to najważniejszy i najtrudniejszy z tych nagłówków. Określa, skąd strona może ładować skrypty, style, obrazy czy ramki. Dobrze ustawiona sprawia, że nawet jeśli atakujący wstrzyknie kod do strony, przeglądarka go nie wykona. To druga linia obrony, a pierwszą zawsze pozostaje zabezpieczenie formularzy i kodu strony przed XSS.

Problem w tym, że zbyt restrykcyjna polityka od razu psuje stronę: przestaje działać analityka, czat, mapy czy osadzone filmy. Dlatego wdrażam ją w trzech krokach:

  1. Tryb raportowania. Najpierw wysyłam nagłówek Content-Security-Policy-Report-Only. Przeglądarka nic nie blokuje, tylko zgłasza naruszenia w konsoli i na wskazany adres.
  2. Lista źródeł. Przez kilka dni zbieram zgłoszenia i na ich podstawie dopisuję domeny, z których strona naprawdę korzysta. Przy okazji zwykle znajduję skrypty, o których nikt w firmie już nie pamiętał.
  3. Włączenie blokowania. Gdy zgłoszenia dotyczą już tylko rzeczy, które faktycznie chcę blokować, zmieniam nagłówek na Content-Security-Policy.

Przykładowa polityka dla strony z Google Tag Managerem i YouTube:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://www.googletagmanager.com;
  img-src 'self' data: https:;
  style-src 'self' 'unsafe-inline';
  frame-src https://www.youtube-nocookie.com;
  frame-ancestors 'self';
  object-src 'none';
  base-uri 'self'

W nagłówku to jedna linia, tutaj podzielona dla czytelności. Unikam 'unsafe-inline' w script-src, bo w praktyce wyłącza ochronę przed XSS. Skrypty wstawione bezpośrednio w HTML przenoszę do plików albo oznaczam losowym atrybutem nonce generowanym przy każdym żądaniu. Dyrektywa frame-ancestors jest nowocześniejszym odpowiednikiem X-Frame-Options, ale ten drugi zostawiam dla starszych przeglądarek.

Konfiguracja na Apache, Nginx i w CDN

Na Apache dodaję nagłówki przez mod_headers w konfiguracji serwera lub w pliku .htaccess:

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
  Header always unset X-Powered-By
</IfModule>

Na Nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
server_tokens off;

W Nginx łatwo przeoczyć jedną pułapkę: add_header w bloku location zastępuje wszystkie nagłówki z bloku server, zamiast je uzupełniać. Jeśli w którejś lokalizacji dodaję np. nagłówki cache, powtarzam tam też nagłówki bezpieczeństwa. Przy stronie za CDN często wygodniej ustawić nagłówki właśnie tam, w regułach odpowiedzi, co pozwala zmienić je bez dostępu do serwera.

Jak sprawdzam efekt

  • SecurityHeaders.com i Mozilla Observatory pokazują, których nagłówków brakuje, i oceniają konfigurację.
  • Narzędzia deweloperskie przeglądarki: w zakładce Network sprawdzam nagłówki odpowiedzi dla HTML, a w konsoli komunikaty o naruszeniach CSP.
  • curl: curl -sI https://twojadomena.pl/ pozwala szybko sprawdzić także adresy, które zwracają przekierowania i błędy, bo tam nagłówki często znikają.

Nagłówki uzupełniają zabezpieczenia w samej aplikacji, a nie je zastępują. Akcje użytkowników nadal wymagają ochrony przed atakami CSRF i poprawnie ustawionych atrybutów ciasteczek sesji.