Browser Support
Ataki typu cross-site scripting (XSS), czyli możliwość wstrzykiwania złośliwych skryptów do aplikacji internetowej, od ponad dekady stanowią jedną z największych luk w zabezpieczeniach sieci.
Content Security Policy (CSP) to dodatkowa warstwa zabezpieczeń, która pomaga ograniczyć ataki XSS. Aby skonfigurować CSP, dodaj nagłówek HTTP Content-Security-Policy do strony internetowej i ustaw wartości, które określają, jakie zasoby klient użytkownika może wczytywać na tej stronie.
Na tej stronie wyjaśniamy, jak używać standardu CSP opartego na liczbach jednorazowych lub skrótach, aby ograniczyć ataki XSS, zamiast powszechnie używanych standardów CSP opartych na liście dozwolonych hostów, które często narażają stronę na ataki XSS, ponieważ można je ominąć w większości konfiguracji.
Kluczowe pojęcie: liczba jednorazowa to losowa liczba używana tylko raz, której możesz użyć do oznaczenia tagu <script> jako zaufanego.
Kluczowe pojęcie: funkcja skrótu to funkcja matematyczna, która przekształca wartość wejściową w skompresowaną wartość liczbową nazywaną skrótem. Możesz użyć hasha (np. SHA-256), aby oznaczyć tag <script> w kodzie jako zaufany.
Standard Content Security Policy oparty na liczbach jednorazowych lub skrótach jest często nazywany ścisłym standardem CSP. Gdy aplikacja używa rygorystycznych zasad CSP, osoby, które znajdą błędy wstrzykiwania kodu HTML, zwykle nie mogą ich wykorzystać do wymuszenia na przeglądarce wykonania szkodliwych skryptów w dokumencie podatnym na ataki. Wynika to z faktu, że rygorystyczne zasady CSP zezwalają tylko na skrypty z hashem lub skrypty z prawidłową liczbą jednorazową wygenerowaną na serwerze, więc hakerzy nie mogą wykonać skryptu bez znajomości prawidłowej liczby jednorazowej dla danej odpowiedzi.
Dlaczego warto używać ścisłej polityki CSP?
Jeśli Twoja witryna ma już zasady CSP, które wyglądają jak script-src www.googleapis.com, prawdopodobnie nie są one skuteczne w przypadku ataków typu cross-site. Ten typ CSP jest nazywany listą dozwolonych CSP. Wymagają wielu dostosowań i mogą zostać ominięte przez atakujących.
Ścisłe zasady CSP oparte na kryptograficznych liczbach jednorazowych lub skrótach pozwalają uniknąć tych pułapek.
Ścisła struktura CSP
Podstawowa rygorystyczna (standard) Content Security Policy używa jednego z tych nagłówków odpowiedzi HTTP:
Rygorystyczne zasady CSP oparte na liczbie jednorazowej
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Ścisłe zasady CSP oparte na skrótach
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Te właściwości sprawiają, że CSP jest „ścisła”, a tym samym bezpieczna:
- Używa liczb jednorazowych
'nonce-{RANDOM}'lub skrótów'sha256-{HASHED_INLINE_SCRIPT}', aby wskazać, które tagi<script>deweloper witryny uważa za bezpieczne do wykonania w przeglądarce użytkownika. - Ustawia
'strict-dynamic', aby zmniejszyć nakład pracy związany z wdrażaniem CSP opartego na liczbie jednorazowej lub haszu przez automatyczne zezwalanie na wykonywanie skryptów utworzonych przez zaufany skrypt. Umożliwia to też korzystanie z większości bibliotek i widżetów JavaScriptu innych firm. - Nie opiera się na listach dozwolonych adresów URL, więc nie jest podatna na typowe obejścia CSP.
- Blokuje niezaufane skrypty wbudowane, takie jak wbudowane moduły obsługi zdarzeń czy identyfikatory URI
javascript:. - Ogranicza
object-src, aby wyłączyć niebezpieczne wtyczki, takie jak Flash. - Ogranicza
base-uri, aby blokować wstrzykiwanie tagów<base>. Zapobiega to zmianie przez hakerów lokalizacji skryptów wczytywanych ze względnych adresów URL.
Wdrażanie rygorystycznych zasad CSP
Aby zastosować rygorystyczne zasady CSP, musisz:
- Zdecyduj, czy aplikacja ma ustawiać zasady CSP oparte na liczbach jednorazowych czy na haszach.
- Skopiuj CSP z sekcji Struktura rygorystycznych CSP i ustaw go jako nagłówek odpowiedzi w aplikacji.
- Zrefaktoryzuj szablony HTML i kod po stronie klienta, aby usunąć wzorce niezgodne z CSP.
- Wdróż CSP.
W trakcie tego procesu możesz używać kontroli sprawdzonych metod w Lighthouse (w wersji 7.3.0 lub nowszej z flagą --preset=experimental), aby sprawdzić, czy Twoja witryna ma zasady CSP i czy są one wystarczająco rygorystyczne, aby skutecznie chronić przed atakami XSS.
Krok 1. Zdecyduj, czy potrzebujesz zasad CSP opartych na liczbach jednorazowych czy haszach
Oto jak działają 2 rodzaje rygorystycznych CSP:
Standard CSP oparty na liczbie jednorazowej
W przypadku CSP opartej na liczbie jednorazowej generujesz losową liczbę w czasie działania, uwzględniasz ją w CSP i powiązujesz z każdym tagiem skryptu na stronie. Atakujący nie może dołączyć ani uruchomić złośliwego skryptu na Twojej stronie, ponieważ musiałby odgadnąć prawidłową liczbę losową dla tego skryptu. Działa to tylko wtedy, gdy liczba nie jest możliwa do odgadnięcia i jest generowana na bieżąco dla każdej odpowiedzi.
Używaj zasad CSP opartych na liczbie jednorazowej w przypadku stron HTML renderowanych na serwerze. W przypadku tych stron możesz utworzyć nową losową liczbę dla każdej odpowiedzi.
Zasady CSP oparte na skrótach
W przypadku CSP opartych na hashu do CSP dodawany jest hash każdego tagu skryptu wbudowanego. Każdy skrypt ma inny hasz. Atakujący nie może dołączyć ani uruchomić złośliwego skryptu na Twojej stronie, ponieważ aby skrypt mógł zostać uruchomiony, jego skrót musiałby znajdować się w CSP.
Używaj zasad CSP opartych na skrótach w przypadku stron HTML wyświetlanych statycznie lub stron, które muszą być przechowywane w pamięci podręcznej. Możesz na przykład użyć CSP opartego na skrótach w przypadku jednostronicowych aplikacji internetowych utworzonych za pomocą platform takich jak Angular, React lub innych, które są udostępniane statycznie bez renderowania po stronie serwera.
Krok 2. Ustaw ścisłe zasady CSP i przygotuj skrypty
Podczas konfigurowania zasad CSP masz kilka opcji:
- Tryb tylko raportowania (
Content-Security-Policy-Report-Only) lub tryb egzekwowania (Content-Security-Policy). W trybie tylko raportowania CSP nie będzie jeszcze blokować zasobów, więc nic w Twojej witrynie nie przestanie działać, ale możesz zobaczyć błędy i otrzymać raporty dotyczące wszystkiego, co zostałoby zablokowane. Lokalnie, gdy ustawiasz CSP, nie ma to większego znaczenia, ponieważ oba tryby pokazują błędy w konsoli przeglądarki. Tryb egzekwowania może pomóc Ci znaleźć zasoby, które blokuje wersja robocza CSP, ponieważ zablokowanie zasobu może sprawić, że strona będzie wyglądać na uszkodzoną. Tryb „Tylko raport” jest najbardziej przydatny na późniejszym etapie procesu (patrz Krok 5). - Nagłówek lub tag HTML
<meta>. W przypadku programowania lokalnego tag<meta>może być wygodniejszy do dostosowywania zasad CSP i szybkiego sprawdzania, jak wpływają one na witrynę. Jednak:- Później, podczas wdrażania CSP w środowisku produkcyjnym, zalecamy ustawienie go jako nagłówka HTTP.
- Jeśli chcesz ustawić zasady CSP w trybie tylko raportowania, musisz ustawić je jako nagłówek, ponieważ metatagi CSP nie obsługują trybu tylko raportowania.
Ustaw w aplikacji ten nagłówek odpowiedzi HTTP Content-Security-Policy:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Generowanie liczby jednorazowej dla CSP
Liczba jednorazowa to losowa liczba używana tylko raz podczas wczytywania strony. Zasady CSP oparte na liczbie jednorazowej mogą zapobiegać atakom XSS tylko wtedy, gdy atakujący nie mogą odgadnąć wartości liczby jednorazowej. Liczba jednorazowa CSP musi:
- Silna kryptograficznie wartość losowa (najlepiej o długości co najmniej 128 bitów).
- generowany na nowo dla każdej odpowiedzi;
- Zakodowany w formacie Base64
Oto kilka przykładów dodawania liczby jednorazowej CSP w platformach po stronie serwera:
- Django (Python)
- Express (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Dodaj atrybut nonce do elementów <script>
W przypadku CSP opartej na liczbie jednorazowej każdy element <script> musi mieć atrybut nonce, który odpowiada losowej wartości liczby jednorazowej określonej w nagłówku CSP. Wszystkie skrypty mogą mieć tę samą liczbę jednorazową. Pierwszym krokiem jest dodanie tych atrybutów do wszystkich skryptów, aby CSP na nie zezwalała.
Ustaw w aplikacji ten nagłówek odpowiedzi HTTP Content-Security-Policy:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
W przypadku wielu skryptów wbudowanych składnia wygląda tak: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.
Dynamiczne wczytywanie skryptów źródłowych
Skrypty innych firm możesz wczytywać dynamicznie za pomocą skryptu wbudowanego.
<script>
var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl;
s.async = false; // to preserve execution order
document.head.appendChild(s);
});
</script>{HASHED_INLINE_SCRIPT}. Aby zmniejszyć liczbę skrótów, możesz scalić wszystkie skrypty wbudowane w jeden skrypt. Aby zobaczyć, jak to działa, zapoznaj się z tym przykładem i jego kodem.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
integrity, który pasuje do dozwolonego źródła.
Uwagi dotyczące wczytywania skryptów
Przykład skryptu wbudowanego dodaje s.async = false, aby zapewnić, że foo wykona się przed bar, nawet jeśli bar załaduje się jako pierwszy. W tym fragmencie kodu s.async = false nie blokuje parsera podczas wczytywania skryptów, ponieważ skrypty są dodawane dynamicznie. Parser zatrzymuje się tylko podczas wykonywania skryptów, tak jak w przypadku skryptów async. Pamiętaj jednak, że w przypadku tego fragmentu kodu:
-
Jeden lub oba skrypty mogą zostać wykonane, zanim dokument zostanie w pełni pobrany. Jeśli chcesz, aby dokument był gotowy do momentu wykonania skryptów, przed dołączeniem skryptów poczekaj na zdarzenie
DOMContentLoaded. Jeśli powoduje to problem z wydajnością, ponieważ skrypty nie zaczynają się pobierać wystarczająco wcześnie, użyj tagów wstępnego wczytywania wcześniej na stronie. -
defer = truenie wykonuje żadnych działań. Jeśli potrzebujesz takiego działania, uruchamiaj skrypt ręcznie, gdy jest to konieczne.
Krok 3. Refaktoryzacja szablonów HTML i kodu po stronie klienta
Do uruchamiania skryptów można używać wbudowanych modułów obsługi zdarzeń (takich jak onclick="…", onerror="…") i identyfikatorów URI JavaScriptu (<a href="javascript:…">). Oznacza to, że haker, który znajdzie błąd XSS, może wstrzyknąć taki kod HTML i wykonać szkodliwy kod JavaScript. Zasady CSP oparte na liczbach jednorazowych lub haszach zabraniają używania tego rodzaju znaczników.
Jeśli Twoja witryna używa któregoś z tych wzorców, musisz je przekształcić w bezpieczniejsze alternatywy.
Jeśli w poprzednim kroku włączono CSP, za każdym razem, gdy CSP zablokuje niezgodny wzorzec, w konsoli będą widoczne naruszenia CSP.
W większości przypadków rozwiązanie jest proste:
Refaktoryzacja wbudowanych modułów obsługi zdarzeń
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.getElementById('things').addEventListener('click', doThings);
</script><span onclick="doThings();">A thing.</span>
Refaktoryzuj identyfikatory URI javascript:
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo').addEventListener('click', linkClicked);
</script><a href="javascript:linkClicked()">foo</a>
Usuń eval() z JavaScriptu
Jeśli Twoja aplikacja używa funkcji eval() do konwertowania serializacji ciągów JSON na obiekty JS, musisz refaktoryzować te instancje na JSON.parse(), co jest również szybsze.
Jeśli nie możesz usunąć wszystkich wystąpień eval(), nadal możesz ustawić rygorystyczną politykę CSP opartą na liczbie jednorazowej, ale musisz użyć słowa kluczowego CSP 'unsafe-eval', co sprawi, że polityka będzie nieco mniej bezpieczna.
Te i inne przykłady takiej refaktoryzacji znajdziesz w tym ćwiczeniu z programowania dotyczącym ścisłej polityki CSP:
Krok 4 (opcjonalny). Dodaj kreacje zastępcze, aby obsługiwać starsze wersje przeglądarek
Browser Support
Jeśli chcesz obsługiwać starsze wersje przeglądarek:
- Używanie
strict-dynamicwymaga dodaniahttps:jako kreacji zastępczej dla wcześniejszych wersji Safari. Gdy to zrobisz:- Wszystkie przeglądarki, które obsługują
strict-dynamic, ignorują zastępczyhttps:, więc nie zmniejszy to skuteczności zasady. - W starszych przeglądarkach skrypty z zewnętrznych źródeł mogą być wczytywane tylko wtedy, gdy pochodzą ze źródła HTTPS. Jest to mniej bezpieczne niż ścisła zasada CSP, ale nadal zapobiega niektórym typowym przyczynom ataków XSS, takim jak wstrzykiwanie identyfikatorów URI
javascript:.
- Wszystkie przeglądarki, które obsługują
- Aby zapewnić zgodność z bardzo starymi wersjami przeglądarek (sprzed ponad 4 lat), możesz dodać
unsafe-inlinejako rozwiązanie zastępcze. Wszystkie najnowsze przeglądarki ignorująunsafe-inline, jeśli obecna jest liczba jednorazowa lub hasz CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Krok 5. Wdróż CSP
Po potwierdzeniu, że CSP nie blokuje żadnych prawidłowych skryptów w lokalnym środowisku programistycznym, możesz wdrożyć CSP w środowisku przejściowym, a następnie w środowisku produkcyjnym:
- (Opcjonalnie) Wdróż CSP w trybie tylko do raportowania, używając nagłówka
Content-Security-Policy-Report-Only. Tryb tylko do raportowania jest przydatny do testowania potencjalnie szkodliwych zmian, takich jak nowe zasady CSP w środowisku produkcyjnym, przed rozpoczęciem egzekwowania ograniczeń CSP. W trybie „tylko raportowanie” zasady CSP nie wpływają na działanie aplikacji, ale przeglądarka nadal generuje błędy konsoli i raporty o naruszeniach, gdy napotka wzorce niezgodne z zasadami CSP. Dzięki temu możesz zobaczyć, co mogłoby nie działać u użytkowników. Więcej informacji znajdziesz w artykule Reporting API. - Gdy masz pewność, że zasady CSP nie spowodują problemów z witryną u użytkowników, wdróż je za pomocą nagłówka odpowiedzi
Content-Security-Policy. Zalecamy ustawienie CSP za pomocą nagłówka HTTP po stronie serwera, ponieważ jest to bezpieczniejsze niż tag<meta>. Po wykonaniu tego kroku CSP zacznie chronić aplikację przed XSS.
Ograniczenia
Ścisłe przestrzeganie standardu CSP zapewnia zwykle silną dodatkową warstwę zabezpieczeń, która pomaga ograniczyć ataki XSS. W większości przypadków CSP znacznie zmniejsza powierzchnię ataku, odrzucając niebezpieczne wzorce, takie jak identyfikatory URI javascript:. Jednak w zależności od typu używanej polityki CSP (liczby jednorazowe, hasze, z dyrektywą 'strict-dynamic' lub bez niej) mogą wystąpić przypadki, w których polityka CSP nie chroni aplikacji:
- Jeśli użyjesz liczby jednorazowej w skrypcie, ale nastąpi wstrzyknięcie bezpośrednio do treści lub parametru
srcelementu<script>. - Jeśli wstrzyknięcia występują w lokalizacjach skryptów tworzonych dynamicznie (
document.createElement('script')), w tym w funkcjach bibliotecznych, które tworzą węzły DOMscriptna podstawie wartości swoich argumentów. Obejmuje to niektóre popularne interfejsy API, takie jak.html()w jQuery, a także.get()i.post()w jQuery < 3.0. - Jeśli w starych aplikacjach AngularJS występują wstrzyknięcia szablonów. Osoba przeprowadzająca atak, która może wstrzyknąć kod do szablonu AngularJS, może go użyć do wykonania dowolnego kodu JavaScriptu.
- Jeśli zasada zawiera wartość
'unsafe-eval', wstrzykiwanie doeval(),setTimeout()i kilku innych rzadko używanych interfejsów API.
Deweloperzy i inżynierowie ds. bezpieczeństwa powinni zwracać szczególną uwagę na takie wzorce podczas inspekcji kodu i audytów bezpieczeństwa. Więcej informacji o tych przypadkach znajdziesz w artykule Content Security Policy: A Successful Mess Between Hardening and Mitigation (Zasady bezpieczeństwa treści: udany bałagan między wzmacnianiem a ograniczaniem).
Więcej informacji
- CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy
- CSP Evaluator
- Konferencja LocoMoco: zasady bezpieczeństwa treści – udany bałagan między wzmacnianiem a ograniczaniem
- Prezentacja z konferencji Google I/O: zabezpieczanie aplikacji internetowych za pomocą nowoczesnych funkcji platformy