Utwardzanie infrastruktury internetowej: Kontrola obcych skryptów za pomocą Content Security Policy (CSP)

Spis treści
Przegląd architektury: Wektory ataków poprzez wstrzykiwanie obcych skryptów
Współczesne aplikacje internetowe i instalacje WordPressa regularnie korzystają ze skryptów JavaScript firm trzecich do analizy ruchu, zarządzania tagami, obsługi mediów i automatyzacji marketingu. Każde zewnętrzne źródło kodu wprowadzone do struktury Document Object Model (DOM) poszerza jednak powierzchnię ataku. Przejęte sieci dostarczania treści (CDN), ataki typu supply-chain na biblioteki JavaScript lub złośliwe rozszerzenia przeglądarek mogą niepostrzeżenie wstrzykiwać nieautoryzowane skrypty do aktywnej sesji użytkownika.
W przypadku braku architektonicznych zabezpieczeń na poziomie nagłówków HTTP, przeglądarki bezwarunkowo wykonują każdy skrypt obecny w drzewie DOM. Stwarza to poważne ryzyko ataków Cross-Site Scripting (XSS), skrapowania struktury DOM oraz nieautoryzowanej eksfiltracji danych (np. kradzieży ciasteczek lub wyłudzania danych z formularzy). Wdrożenie rygorystycznej polityki Content Security Policy (CSP) ustanawia niezmienny system zezwoleń w przeglądarce klienta, gwarantując, że kod mogą wykonywać wyłącznie jawnie zdefiniowane domeny i zweryfikowane skróty kryptograficzne.

googletagmanager.com jest na liście dozwolonych, wstrzyknięte skrypty i osadzenia object nie.Instrukcja wdrożenia krok po kroku
Krok 1: Przeprowadzenie pełnego audytu skryptów i punktów końcowych
Przed wdrożeniem blokującego nagłówka CSP konieczne jest skatalogowanie wszystkich uzasadnionych zależności domenowych. W standardowym środowisku WordPress v7.0.2 wyposażonym w zaawansowane zarządzanie tagami i analitykę server-side, audyt wykazuje zazwyczaj następujące kluczowe dyrektywy:
default-src 'self'– Ogranicza ładowanie zasobów domyślnych wyłącznie do domeny głównej.script-src 'self'– Zezwala na wykonywanie skryptów hostowanych na własnym serwerze.connect-src 'self'– Ogranicza połączenia REST API, Fetch i XHR do zaufanych punktów analitycznych.img-src 'self' data:– Dopuszcza ładowanie lokalnych grafik i osadzonych schematów URI base64.
Krok 2: Projektowanie polityki CSP o minimalnych uprawnieniach
W celu zablokowania obcych skryptów śledzących przy jednoczesnym zachowaniu sprawności autoryzowanych systemów (takich jak Google Tag Manager, GA4, Matomo lub własny punkt końcowy Server-Side GTM), należy precyzyjnie określić białe listy domen. Bezpieczna polityka bazowa dla środowisk analitycznych wygląda następująco:
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://cdn.matomo.cloud;
img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://matomo.cloud https://tracking.lukaswojcik.com;
frame-src 'self' https://www.youtube.com;
object-src 'none';
base-uri 'self';
form-action 'self';
Uwaga: Stosowanie parametru 'unsafe-inline' w dyrektywie script-src powinno w środowiskach o najwyższym rygorze bezpieczeństwa zostać zastąpione przez kryptograficzne wartości nonce lub skróty SHA-256.
Krok 3: Wdrożenie na poziomie serwera w konfiguracji Nginx
Ze względów wydajnościowych nagłówki CSP powinny być wysyłane bezpośrednio przez serwer WWW, a nie generowane w warstwie aplikacji PHP. Umieszczenie polityki w bloku serwera SSL programu Nginx zapewnia jej natychmiastowe egzekwowanie przed przetworzeniem kodu PHP:
# /etc/nginx/sites-available/lukaswojcik.com.conf
server {
listen 443 ssl http2;
server_name www.lukaswojcik.com lukaswojcik.com;
# Wymuszenie rygorystycznego nagłówka CSP
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://cdn.matomo.cloud; img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com; connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://matomo.cloud https://tracking.lukaswojcik.com; frame-src 'self' https://www.youtube.com; object-src 'none'; base-uri 'self'; form-action 'self';" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
}
Po modyfikacji konfiguracji serwera Nginx wymagane jest sprawdzenie składni oraz bezkolizyjne przeładowanie usługi:
nginx -t && systemctl reload nginx
Krok 4: Zabezpieczenie na poziomie aplikacji przez functions.php
W przypadku braku dostępu administracyjnego do konfiguracji serwera Nginx, identyczny nagłówek CSP może być wysyłany za pośrednictwem PHP podczas cyklu żądania WordPressa poprzez wdrożenie poniższego kodu do pliku functions.php aktywnego motywu:
/**
* Wdrożenie nagłówka Content Security Policy
* Cel: WordPress v7.0.2
*/
add_action( 'send_headers', 'lw_enforce_content_security_policy' );
function lw_enforce_content_security_policy() {
if ( ! is_admin() ) {
$csp = "default-src 'self'; " .
"script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://cdn.matomo.cloud; " .
"img-src 'self' data: https://www.google-analytics.com https://www.googletagmanager.com; " .
"connect-src 'self' https://www.google-analytics.com https://region1.google-analytics.com https://matomo.cloud https://tracking.lukaswojcik.com; " .
"frame-src 'self' https://www.youtube.com; " .
"object-src 'none'; " .
"base-uri 'self'; " .
"form-action 'self';";
header( 'Content-Security-Policy: ' . $csp );
}
}
Krok 5: Kontrola jakości i testowanie w trybie Report-Only
Przed wdrożeniem reguł blokujących w środowisku produkcyjnym zaleca się tymczasowe zastosowanie nagłówka Content-Security-Policy-Report-Only. Pozwala to na rejestrowanie naruszeń w konsoli przeglądarki bez wstrzymywania wykonywania skryptów.
Weryfikację należy przeprowadzić przy użyciu narzędzi deweloperskich (F12):
- Weryfikacja nagłówka sieciowego: Otwarcie zakładki Network, odświeżenie widoku strony i potwierdzenie obecności kompletnego ciągu
Content-Security-Policyw odpowiedzi HTTP. - Wykrywanie anomalii w konsoli: Monitoring zakładki Console. Nieautoryzowane skrypty śledzące natychmiast wygenerują czerwone komunikaty o błędach CSP. Skrypty treści (content scripts) rozszerzeń przeglądarki nie podlegają natomiast polityce CSP strony; sprawdzane jest tylko to, co wstawiają do strony jako elementy script.
- Walidacja punktów śledzących: Sprawdzenie, czy zapytania analityczne kierowane do własnej subdomeny server-side zwracają kod HTTP 200 bez blokad ze strony mechanizmów bezpieczeństwa.
Podsumowanie i mierzalna wartość dodana
Co da się osiągnąć dzięki instrukcji: Niekontrolowane wykonywanie dowolnych skryptów JavaScript w drzewie DOM zostaje zastąpione przez kryptograficznie egzekwowaną listę zaufanych domen, zarządzaną bezpośrednio przez silnik bezpieczeństwa przeglądarki.
Wynikająca z tego wartość dodana:
- Ochrona przed atakami Supply-Chain: Złamane zewnętrzne biblioteki JavaScript oraz obce skrypty są natychmiast blokowane przed uruchomieniem lub próbą eksfiltracji danych.
- Zgodność z RODO i wymogami prywatności: Nieautoryzowane piksele śledzące oraz tagi doczepiane są systemowo wstrzymywane przed nawiązaniem połączeń z zewnętrznymi sieciami reklamowymi.
- Zabezpieczenie formularzy i sesji: Ograniczenia dyrektyw
connect-srcorazform-actionuniemożliwiają przechwytywanie danych wejściowych z formularzy na wszystkich podstronach WordPressa.
Pytania i odpowiedzi
Dlaczego na niektórych adresach brakuje CSP z konfiguracji Nginx?
Z powodu zasady dziedziczenia add_header. Nginx przenosi dyrektywy add_header z bloku server tylko do tych bloków location, które same nie zawierają żadnej dyrektywy add_header. Jeśli blok location dla obrazów, czcionek albo PHP ustawia własny nagłówek, na przykład Cache-Control, znikają w nim wszystkie nagłówki z bloku server, łącznie z Content-Security-Policy, X-Content-Type-Options i X-Frame-Options.
Często długo pozostaje to niezauważone, bo kontrola z kroku 5 odbywa się zwykle na stronie HTML. Bezpieczniejsza jest próbka dla każdego bloku location, obejmująca także obrazy, skrypty i adresy obsługiwane przez PHP.
Rozwiązaniem jest powtórzenie nagłówków w każdym bloku location, który ma własne wiersze add_header, najprościej przez wspólny plik dołączany dyrektywą include.
Co się dzieje, gdy CSP wysyłają jednocześnie Nginx i functions.php?
Obowiązują wtedy obie polityki. Przeglądarka nie scala kilku nagłówków Content-Security-Policy w jedną politykę, lecz sprawdza każdy zasób względem każdej z nich, a ładuje tylko to, na co zezwalają wszystkie. Domena wpisana tylko w jednej z dwóch wersji pozostaje więc zablokowana. Łatwo o to, gdy źródło zostaje dopisane tylko w jednym miejscu, na przykład w konfiguracji Nginx, ale nie w functions.php.
Z drugiej strony może zabraknąć nagłówka z functions.php, choć kod jest poprawny. Pamięć podręczna stron, która wydaje gotowe pliki HTML bezpośrednio z serwera WWW bez uruchamiania PHP, dostarcza je bez tego nagłówka, o ile nie zapisuje razem z nimi nagłówków. Przy ustawianiu polityki przez PHP warto więc sprawdzić także stronę wydaną z pamięci podręcznej, a nie tylko świeżo wygenerowaną.
Jak zbierać naruszenia, które występują tylko u prawdziwych odwiedzających?
Przez adres zgłoszeń w polityce. Konsola pokazuje naruszenia tylko w przeglądarce osoby, która akurat sprawdza; z dyrektywą report-uri albo nowszą report-to razem z nagłówkiem Reporting-Endpoints przeglądarki odwiedzających wysyłają każde naruszenie jako JSON do punktu końcowego prowadzonego przez witrynę, także w trybie Report-Only. Takie zgłoszenia zawierają też szum, na przykład z rozszerzeń przeglądarki, dlatego warto analizować je według źródła, a nie patrzeć tylko na ich liczbę.