Jak ustawić nagłówki bezpieczeństwa w Joomla wtyczką HTTP Headers

Joomla ma wbudowaną wtyczkę do nagłówków bezpieczeństwa i jest ona włączona od pierwszego dnia po instalacji. Problem w tym, że w domyślnej konfiguracji wysyła trzy nagłówki, a nie komplet, którego spodziewa się większość osób po pierwszym teście w securityheaders.com.

Poniżej to, co wtyczka realnie robi — sprawdzone na kodzie źródłowym i na żywej instalacji Joomla 6.1, a nie przepisane z dokumentacji.

Co wtyczka wysyła zaraz po instalacji

Znajdziesz ją w System → Wtyczki, pod nazwą System - Nagłówki HTTP. W wersji polskiej szukaj po frazie „nagłówki", w angielskiej po „HTTP Headers".

Wtyczka jest domyślnie włączona i działa bez zapisywania jakichkolwiek ustawień. Jeśli nigdy jej nie otwierałeś, obowiązują wartości domyślne z manifestu. Odpowiedź serwera wygląda wtedy tak:

NagłówekWartośćPrzed czym chroni
X-Frame-Options SAMEORIGIN osadzenie strony w cudzej ramce (clickjacking)
Referrer-Policy strict-origin-when-cross-origin wyciek pełnych adresów URL do obcych domen
Cross-Origin-Opener-Policy same-origin dostęp obcych okien do kontekstu Twojej strony

I na tym koniec. Trzy nagłówki.

Częste nieporozumienie. Nagłówek X-Content-Type-Options: nosniff bardzo często pojawia się w odpowiedzi serwera Joomli, ale nie pochodzi z tej wtyczki. Jest ustawiony w pliku htaccess.txt, w linii 42.

Sprawdziłem to na dwóch instalacjach. Na tej z wyłączoną wtyczką nosniff nadal był wysyłany, a trzy nagłówki z tabeli zniknęły.

Wniosek jest praktyczny: jeśli nie zmieniłeś nazwy htaccess.txt na .htaccess, nie masz nosniff w ogóle. Wtyczka Ci go nie doda, bo nie ma go nawet na swojej liście obsługiwanych nagłówków.

Przy okazji zajrzyj do htaccess.txt niżej. Dwa przydatne nagłówki, Cross-Origin-Resource-Policy i Cross-Origin-Embedder-Policy, są tam gotowe, ale zakomentowane.

HSTS i dlaczego u Ciebie może nie działać

Druga zakładka to Strict-Transport-Security (HSTS). Nagłówek mówi przeglądarce, żeby przez zadany czas łączyła się z domeną wyłącznie po HTTPS, nawet jeśli użytkownik wpisze adres z http://.

Domyślnie jest wyłączony. Po włączeniu dostajesz trzy opcje:

  • max-age — czas w sekundach, domyślnie 31536000, czyli rok. Minimum przyjmowane przez pole to 300.
  • Również dla subdomen — dokłada includeSubDomains.
  • Preload — dokłada preload, czyli zgłoszenie domeny na listę wbudowaną w przeglądarki.

Tu pojawia się pułapka, która kosztuje ludzi sporo czasu. Wtyczka wysyła HSTS tylko wtedy, gdy żądanie przyszło po HTTPS. W kodzie jest jawny warunek sprawdzający SSL. Sprawdziłem to eksperymentalnie: przy identycznych ustawieniach nagłówek pojawił się pod https://, a pod http:// nie było go wcale.

Jeśli więc testujesz konfigurację lokalnie bez certyfikatu albo odpytujesz adres, który dopiero przekierowuje na HTTPS, zobaczysz pustkę i uznasz, że ustawienie nie działa. Działa. Po prostu patrzysz na złą odpowiedź.

Z opcją Preload obchodź się ostrożnie. Sama wtyczka ostrzega o tym w opisie pola i słusznie. Wpisanie domeny na listę preload jest praktycznie nieodwracalne w krótkim terminie, bo usunięcie wymaga aktualizacji przeglądarek u wszystkich użytkowników. Włączaj ją tylko wtedy, gdy masz pewność, że domena i wszystkie subdomeny będą miały ważny certyfikat przez lata. Lista preload wymaga też max-age ustawionego na 63072000, czyli dwa lata.

Content Security Policy krok po kroku

Trzecia zakładka to CSP i jest to najmocniejsze, a zarazem najbardziej ryzykowne narzędzie w tej wtyczce. CSP mówi przeglądarce, z jakich źródeł wolno ładować skrypty, style, obrazy i resztę zasobów. Dobrze ustawiony zabija większość ataków XSS.

Domyślnie CSP jest wyłączone. Po włączeniu masz do dyspozycji:

  • Dotyczy — witryna, administrator albo obie części.
  • Report-Onlydomyślnie włączone, i bardzo dobrze. Przeglądarka wtedy raportuje naruszenia do konsoli, ale niczego nie blokuje.
  • Nonce — losowy token doklejany do skryptów i stylów rejestrowanych przez API Joomli.
  • Script hashes i Style hashes — automatyczne skróty treści skryptów i stylów wbudowanych.
  • strict-dynamic — rozszerza zaufanie z nonce na skrypty ładowane przez zaufany skrypt.
  • frame-ancestors 'self'domyślnie włączone, dokłada dyrektywę blokującą ramkowanie.

Dyrektywy dodajesz niżej, w powtarzalnym formularzu. Do wyboru jest 28 pozycji, od default-src po worker-src. Dwie z nich, upgrade-insecure-requests i block-all-mixed-content, nie przyjmują wartości i pole wartości chowa się samo.

Kilka szczegółów, których nie widać w panelu, a wynikają z kodu:

  • Hashe liczone są wyłącznie algorytmem SHA-256. Spotkasz opracowania mówiące o SHA-384 i SHA-512 przy tej wtyczce. To nieprawda.
  • Nonce trafia tylko do dyrektyw script-src i style-src. Do żadnej innej.
  • strict-dynamic dokleja się wyłącznie do script-src, nie do script-src-elem.
  • Jeśli nie dodasz ani jednej poprawnej dyrektywy, nagłówek nie zostanie wysłany w ogóle.
  • Placeholder {nonce} w wartości dyrektywy to relikt sprzed wersji 4.2. Dziś jest zbędny i wtyczka sama go usuwa.

Tak wygląda nagłówek wygenerowany przez wtyczkę przy włączonym nonce i hashach. Wartości nonce skróciłem, w rzeczywistości ma 172 znaki:

content-security-policy-report-only: default-src 'self';
script-src 'sha256-g3JuXfL/pmmQ9d2W026E3rkIbgx/Vox0zUUqUle/XTQ='
  'sha256-/pxU7uIe56phiczcOPuBtk1C5bwVdLdI55T1xImlkmY='
  'nonce-NDMzYjVhMDY5MjUwMDMwMWY4ZGRhNDVi...' 'self' 'unsafe-inline';
style-src 'sha256-p7APz0n9JFF9E2UEr2UugVUAhvMMF33jcDbhRXXFoFI='
  'nonce-NDMzYjVhMDY5MjUwMDMwMWY4ZGRhNDVi...' 'self' 'unsafe-inline';
img-src 'self' data:; upgrade-insecure-requests; frame-ancestors 'self'

Zwróć uwagę na dwie rzeczy. Po pierwsze, frame-ancestors 'self' doklejło się samo, bo nie dodałem go ręcznie. Po drugie, nonce powtarza się dwa razy po 172 znaki, co przy każdym żądaniu dokłada do odpowiedzi ponad 350 bajtów. Przy włączonym cache stron to argument, żeby nie włączać nonce bez potrzeby.

Wymuś nagłówki HTTP i gdzie kończą się możliwości wtyczki

Na pierwszej zakładce, pod podstawowymi opcjami, jest sekcja Wymuś nagłówki HTTP Headers. Pozwala ustawić dowolną wartość dla wybranego nagłówka i wskazać, czy ma dotyczyć witryny, panelu, czy obu.

Lista jest zamknięta i liczy 11 pozycji: Content-Security-Policy, Content-Security-Policy-Report-Only, Cross-Origin-Opener-Policy, Expect-CT, Feature-Policy, NEL, Permissions-Policy, Referrer-Policy, Report-To, Strict-Transport-Security, X-Frame-Options.

Co z tego wynika w praktyce:

  • Permissions-Policy ustawisz tylko tutaj. Nie ma dla niego osobnego pola, a to on wyłącza kamerę, mikrofon i geolokalizację.
  • X-Content-Type-Options ustawisz przez .htaccess albo wcale. Nie ma go na liście, więc wtyczka go nie doda.
  • X-Frame-Options jest zabetonowany na SAMEORIGIN. Przełącznik na pierwszej zakładce daje tylko włącz albo wyłącz. Jeśli chcesz DENY, musisz użyć właśnie sekcji wymuszania.
  • Expect-CT i Feature-Policy są przestarzałe. Zostały dla zgodności wstecz, nie buduj na nich niczego nowego.

Jest jeszcze jeden problem, na który natrafiłem przy sprawdzaniu własnej strony. Jeśli te same nagłówki ustawia i .htaccess, i wtyczka, odpowiedź zawiera je dwa razy. W moim przypadku X-Frame-Options i Referrer-Policy przychodziły zdublowane — raz z serwera, raz z PHP. Przeglądarki zwykle biorą pierwszy, ale to zależy od nagłówka i nie warto na tym polegać. Wybierz jedno miejsce i trzymaj tam całą konfigurację.

Kolejność wdrożenia, żeby nie położyć strony

CSP potrafi wyłączyć stronę skuteczniej niż niejeden błąd w kodzie. Zablokowane skrypty oznaczają martwe menu, nieklikalny koszyk i puste mapy. Dlatego kolejność ma znaczenie.

  1. Zacznij od tego, co masz. Sprawdź obecny stan przez securityheaders.com albo lokalnie poleceniem curl -I https://twojadomena.pl.
  2. Włącz HSTS, ale bez preload. Zostaw max-age na roku i sprawdź, czy strona działa. Preload zostaw na później, gdy nabierzesz pewności.
  3. Dodaj Permissions-Policy przez sekcję wymuszania. Wartość geolocation=(), microphone=(), camera=() jest bezpiecznym startem dla większości stron firmowych.
  4. Włącz CSP w trybie Report-Only i zostaw go tak na kilka dni. Zacznij od default-src 'self' i dokładaj dyrektywy pod to, czego naprawdę używasz.
  5. Czytaj konsolę. Każde naruszenie ląduje w zakładce Console w narzędziach deweloperskich, z nazwą dyrektywy i zablokowanym adresem. To gotowa lista rzeczy do dopisania.
  6. Dopiero potem wyłącz Report-Only. I przeklikaj stronę jeszcze raz, łącznie z koszykiem i formularzami.

Największa pułapka na tym etapie to zewnętrzne skrypty. Google Analytics, mapy, czaty, piksele reklamowe i osadzone filmy potrzebują własnych wpisów w script-src, img-src, connect-src i frame-src. Każde narzędzie marketingowe dołożone później do strony wymaga aktualizacji polityki.

Jeśli strona korzysta z wielu zewnętrznych integracji, dopięcie CSP bywa robotą na kilka wieczorów. Wtedy rozsądniej jest zostawić politykę w trybie raportowania i zająć się nią osobno, niż wyłączyć ją w pośpiechu po pierwszym telefonie od klienta — albo zlecić to komuś, kto konfiguruje strony na Joomli na co dzień.

Podsumowanie

Wtyczka System - Nagłówki HTTP daje solidny fundament i nic nie kosztuje, bo jest w rdzeniu Joomli. Trzy domyślne nagłówki działają od razu. HSTS to kwestia jednego przełącznika i pięciu minut. CSP wymaga cierpliwości, ale to jedyna rzecz z tej listy, która realnie zatrzymuje XSS.

Zacznij od curl -I na własnej domenie. Zobaczysz w kilka sekund, na czym stoisz.

Chcesz mieć komplet nagłówków bez grzebania w konfiguracji?

Skonfiguruję nagłówki bezpieczeństwa i politykę CSP pod Twoją stronę na Joomli — tak, żeby wszystko dalej działało.

Ten adres pocztowy jest chroniony przed spamowaniem. Aby go zobaczyć, konieczne jest włączenie w przeglądarce obsługi JavaScript.