Cross-site scripting (XSS), czyli możliwość wstrzyknięcia szkodliwych skryptów do aplikacji internetowej, od ponad dekady jest jedną z największych luk w zabezpieczeniach sieci.
Content Security Policy (CSP) to dodatkowa warstwa zabezpieczeń, która pomaga ograniczać ataki typu XSS. Aby skonfigurować CSP, dodaj nagłówek HTTP Content-Security-Policy
do strony internetowej i ustaw wartości, które określają, jakie zasoby może wczytywać klient użytkownika na tej stronie.
Na tej stronie wyjaśniamy, jak zamiast powszechnie stosowanych CSP z użyciem listy dozwolonych hostów, które często pozostawiają stronę podatną na ataki XSS, ponieważ można je ominąć w większości konfiguracji, używać CSP opartego na wartościach nonce lub haszach, aby ograniczyć ryzyko ataków XSS.
Termin kluczowy: nonce to losowy numer używany tylko raz, który możesz wykorzystać do oznaczenia tagu <script>
jako zaufany.
Termin kluczowy: funkcja szyfrująca to funkcja matematyczna, która zamienia wartość wejściową na skompresowaną wartość liczbową zwaną szyfrem. Aby oznaczyć tag <script>
jako zaufany, możesz użyć skrótu (np. SHA-256).
Zasady Content Security Policy oparte na wartościach nonce lub haszach są często nazywane surowymi zasadami CSP. Jeśli aplikacja używa rygorystycznych zasad CSP, hakerzy, którzy znajdą luki w zabezpieczeniach umożliwiające wstrzykiwanie kodu HTML, nie mogą ich wykorzystać do wymuszenia na przeglądarce wykonania złośliwych skryptów w dokumentach zawierających luki. Wynika to z faktu, że rygorystyczne zasady CSP dopuszczają tylko zaszyfrowane skrypty lub skrypty z poprawną wartością jednorazową wygenerowaną na serwerze, więc osoby przeprowadzające atak nie mogą wykonać skryptu bez znajomości poprawnej wartości jednorazowej danej odpowiedzi.
Dlaczego warto używać rygorystycznego CSP?
Jeśli w Twojej witrynie jest już standard CSP script-src www.googleapis.com
, prawdopodobnie nie jest on skuteczny w przypadku innych witryn. Ten typ CSP nazywamy listą dozwolonych CSP. Wymagają one wielu dostosowań i mogą być omijane przez atakujących.
Ścisłe zasady CSP oparte na kryptograficznych identyfikatorach losowych lub haszach unikają tych pułapek.
Ścisła struktura CSP
Podstawowa rygorystyczna zasada Content Security Policy używa jednego z tych nagłówków odpowiedzi HTTP:
Sztywne reguły CSP oparte na szyfrze nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Szczegółowe zasady CSP oparte na haśle
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, taki jak ten, jest „ścisły”, a tym samym bezpieczny:
- Używa ona losowych liczb
'nonce-{RANDOM}'
lub haszy'sha256-{HASHED_INLINE_SCRIPT}'
, aby wskazać, które tagi<script>
są uruchamiane w przeglądarce użytkownika przez zaufanego dewelopera witryny. - Ustawia ona wartość
'strict-dynamic'
, aby zmniejszyć nakład pracy związany z wdrażaniem CSP na podstawie liczby jednorazowej lub hasza, automatycznie zezwalając na wykonywanie skryptów utworzonych przez zaufany skrypt. Pozwala to też na korzystanie z większości zewnętrznych bibliotek i widżetów JavaScriptu. - Nie opiera się na listach dozwolonych adresów URL, więc nie jest podatna na częste obejścia CSP.
- Blokuje niesprawdzone skrypty wstawiane inline, takie jak moduły obsługi zdarzeń wstawiane inline czy identyfikatory URI danych.
- Ogranicza ona
object-src
do wyłączania niebezpiecznych wtyczek, takich jak Flash. - Ogranicza działanie usługi
base-uri
do blokowania wstrzykiwania<base>
tagów. Zapobiega to zmianie przez atakujących lokalizacji skryptów wczytywanych z względnych adresów URL.
Stosowanie ścisłych zasad CSP
Aby wdrożyć rygorystyczny CSP, musisz:
- Zdecyduj, czy aplikacja ma ustawiać CSP na podstawie liczby jednorazowej czy hasza.
- Skopiuj CSP z sekcji Rygorystyczna struktura 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 ramach tego procesu możesz użyć Lighthouse (wersja 7.3.0 lub nowsza z flagą --preset=experimental
), Sprawdzone metody podczas tego procesu, aby sprawdzić, czy witryna ma CSP i czy jest ona wystarczająco rygorystyczna, aby była skuteczna w odniesieniu do XSS.
Krok 1. Zdecyduj, czy potrzebujesz CSP opartego na liczbie losowym lub na haszu
Oto jak działają 2 rodzaje ścisłych zasad CSP:
CSP oparty na niepowtarzalnych identyfikatorach (nonce)
W przypadku CSP opartego na nonce generujesz losową liczbę w czasie wykonywania, umieszczasz ją w swojej polityce CSP i kojarzysz z każdym tagiem skryptu na stronie. Atakujący nie może umieścić na Twojej stronie ani uruchomić złośliwego skryptu, ponieważ musiałby odgadnąć prawidłową liczbę losową dla tego skryptu. Ta metoda działa tylko wtedy, gdy numer nie jest łatwy do odgadnięcia i jest generowany na nowo w czasie wykonywania dla każdej odpowiedzi.
Używaj CSP opartego na nonce w przypadku stron HTML renderowanych na serwerze. W przypadku tych stron możesz utworzyć nową losową liczbę dla każdej odpowiedzi.
Standard CSP oparty na haśle
W przypadku CSP opartych na haszach do CSP jest dodawany hasz każdego wbudowanego tagu skryptu. Każdy skrypt ma inny ciąg znaków. Atakujący nie może umieścić na Twojej stronie ani uruchomić złośliwego skryptu, ponieważ do jego uruchomienia potrzebny jest hasz skryptu w Twoim CSP.
Używaj CSP opartego na haśle 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żywać CSP opartego na haśle w przypadku jednostronicowych aplikacji internetowych utworzonych za pomocą frameworków takich jak Angular, React lub innych, które są wyświetlane statycznie bez renderowania po stronie serwera.
Krok 2. Skonfiguruj ścisłe zasady CSP i przygotuj skrypty
Podczas konfigurowania CSP masz do wyboru kilka opcji:
- Tryb tylko raportów (
Content-Security-Policy-Report-Only
) lub tryb egzekwowania (Content-Security-Policy
). W trybie tylko raportów CSP nie blokuje jeszcze zasobów, więc nic w Twojej witrynie się nie zepsuje, ale możesz zobaczyć błędy i otrzymać raporty dotyczące wszystkiego, co zostałoby zablokowane. Podczas konfigurowania CSP lokalnie nie ma to większego znaczenia, ponieważ oba tryby wyświetlają błędy w konsoli przeglądarki. W razie potrzeby tryb egzekwowania pomoże Ci znaleźć zasoby blokowane przez CSP w wersji roboczej, ponieważ zablokowanie zasobu może sprawić, że strona będzie wyglądała na uszkodzone. Tryb tylko do raportowania jest najbardziej przydatny w późniejszej fazie procesu (patrz Krok 5). - nagłówku lub tagu HTML
<meta>
. W przypadku programowania lokalnego tag<meta>
może być wygodniejszy do dostosowywania CSP i szybkiego sprawdzania, jak wpływa on na Twoją witrynę. Pamiętaj jednak, że:- Później przy wdrażaniu CSP w środowisku produkcyjnym zalecamy ustawienie go jako nagłówka HTTP.
- Jeśli chcesz ustawić CSP w trybie tylko do raportowania, musisz go ustawić jako nagłówek, ponieważ metatagi CSP nie obsługują trybu tylko do raportowania.
W aplikacji ustaw ten nagłówek odpowiedzi HTTP Content-Security-Policy
:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Generowanie nonce’a dla CSP
Liczba jednorazowa to liczba losowa używana tylko raz na wczytanie strony. CSP oparty na wartościach nonce może ograniczyć XSS tylko wtedy, gdy atakujący nie może odgadnąć wartości nonce. Liczba jednorazowa CSP musi być:
- wartość losowa o wysokiej odporności na szyfrowanie (najlepiej o długości co najmniej 128 bitów);
- nowo generowane w przypadku każdej odpowiedzi;
- zakodowany w formacie Base64,
Oto kilka przykładów dodawania liczby jednorazowej CSP w ramach platform 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 opartego na szyfrowaniu symetrycznym każdy element <script>
musi mieć atrybut nonce
, który odpowiada losowej wartości nonce określonej w nagłówku CSP. Wszystkie skrypty mogą mieć ten sam nonce. Najpierw dodaj te atrybuty do wszystkich skryptów, aby CSP zezwolił na ich używanie.
W aplikacji ustaw 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 w ciele wiadomości 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>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Uwagi na temat wczytywania skryptu
Przykład skryptu w ciele wiadomości dodaje s.async = false
, aby zapewnić, że skrypt foo
zostanie wykonany przed skryptem bar
, nawet jeśli skrypt bar
zostanie załadowany 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ć uruchomione, zanim dokument zostanie pobrany. Jeśli chcesz, aby dokument był gotowy do momentu wykonania skryptów, zaczekaj na zdarzenie
DOMContentLoaded
, zanim dołączysz skrypty. Jeśli powoduje to problemy z wydajnością, ponieważ skrypty nie zaczynają się wczytywać odpowiednio wcześnie, użyj tagów wstępnego wczytania wcześniej na stronie. -
defer = true
nic nie robi. W razie potrzeby uruchom skrypt ręcznie.
Krok 3. Refaktoryzacja szablonów HTML i kodu po stronie klienta
Do uruchamiania skryptów można używać wbudowanych obciążników zdarzeń (takich jak onclick="…"
czy onerror="…"
) oraz adresów URI kodu JavaScript (<a href="javascript:…">
). Oznacza to, że atakujący, który znajdzie błąd XSS, może wstrzyknąć ten rodzaj kodu HTML i wykonać złośliwy kod JavaScript. Zasady CSP oparte na liczbie losowym lub haśle zabraniają używania tego rodzaju znaczników.
Jeśli Twoja witryna korzysta z któregoś z tych wzorców, musisz zmienić je na bezpieczniejsze alternatywy.
Jeśli w poprzednim kroku włączysz CSP, będziesz widzieć naruszenia zasad CSP w konsoli za każdym razem, gdy CSP zablokuje niezgodny wzór.
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>
Refaktoryzacja identyfikatorów 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ągu znaków JSON na obiekty JS, powinna przerobić takie instancje na funkcję JSON.parse()
, która jest też szybsza.
Jeśli nie da się usunąć wszystkich zastosowań eval()
, nadal możesz ustawić rygorystyczne CSP na podstawie liczby jednorazowej, ale musisz użyć słowa kluczowego CSP 'unsafe-eval'
, przez co zasada jest nieco mniej bezpieczna.
Te i inne przykłady refaktoryzacji znajdziesz w tym ćwiczeniu z programowania dotyczącym ścisłego CSP:
Krok 4 (opcjonalny). Dodaj alternatywne rozwiązania, aby obsługiwać starsze wersje przeglądarek
Jeśli musisz obsługiwać starsze wersje przeglądarek:
- Użycie elementu
strict-dynamic
wymaga dodania elementuhttps:
jako kreacji zastępczej we wcześniejszych wersjach Safari. Gdy to zrobisz:- Wszystkie przeglądarki, które obsługują
strict-dynamic
, ignorują wartość zastępcząhttps:
, więc nie osłabi to skuteczności zasad. - W starych przeglądarkach skrypty pozyskane z zewnątrz mogą być wczytywane tylko wtedy, gdy pochodzą ze źródła HTTPS. Jest to mniej bezpieczne niż ścisłe reguły CSP, ale nadal zapobiega niektórym typowym przyczynom XSS, takim jak wstrzykiwanie identyfikatorów URI
javascript:
.
- Wszystkie przeglądarki, które obsługują
- Aby zapewnić zgodność ze bardzo starszymi wersjami przeglądarek (ponad 4 lata), możesz dodać
unsafe-inline
jako opcję zastępczą. Wszystkie nowe przeglądarki ignorująunsafe-inline
, jeśli jest obecny nonce lub hasz CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Krok 5. Wdróż usługę CSP
Po potwierdzeniu, że CSP nie blokuje żadnych prawidłowych skryptów w lokalnym środowisku programistycznym, możesz wdrożyć CSP w środowisku testowym, a potem w środowisku produkcyjnym:
- (Opcjonalnie) Wdróż usługę 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 nieprawidłowej zmiany, takiej jak nowy CSP w środowisku produkcyjnym, zanim zaczniesz stosować ograniczenia CSP. W trybie tylko raportów nagłówek CSP nie wpływa na działanie aplikacji, ale przeglądarka nadal generuje błędy konsoli i raporty o naruszeniu, gdy wykryje wzorce niezgodne z tym nagłówkiem. Dzięki temu możesz sprawdzić, co nie działałoby u użytkowników. Więcej informacji znajdziesz w artykule Reporting API. - Gdy masz pewność, że zasada CSP nie spowoduje problemów w witrynie dla użytkowników końcowych, wdrożenie zasad CSP za pomocą nagłówka odpowiedzi
Content-Security-Policy
. Zalecamy skonfigurowanie CSP za pomocą nagłówka HTTP po stronie serwera, ponieważ jest on bezpieczniejszy niż tag<meta>
. Po wykonaniu tego kroku usługa CSP zacznie chronić Twoją aplikację przed atakami XSS.
Ograniczenia
Ścisłe przestrzeganie standardu CSP zapewnia dodatkową warstwę zabezpieczeń, która pomaga ograniczać ataki typu XSS. W większości przypadków CSP znacznie zmniejsza obszar ataku, odrzucając niebezpieczne wzorce, takie jak adresy URI javascript:
. Jednak w zależności od typu CSP, którego używasz (liczby jednorazowe, hasze, z 'strict-dynamic'
lub bez), mogą wystąpić przypadki, w których CSP nie chroni Twojej aplikacji:
- Jeśli skrypt jest zabezpieczony, ale wstrzyknięcie występuje bezpośrednio w treści lub parametrze
src
elementu<script>
. - Jeśli występują wstrzyknięcia w miejscach skryptów tworzonych dynamicznie (
document.createElement('script')
), w tym w dowolnych funkcjach biblioteki, które tworzą węzły modelu DOMscript
na 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 atakująca, która może wstrzyknąć kod do szablonu AngularJS, może go użyć do wykonania dowolnego kodu JavaScript.
- Jeśli polityka zawiera
'unsafe-eval'
, wstrzyknięcia doeval()
,setTimeout()
i kilka innych rzadko używanych interfejsów API.
Programiści i specjaliści ds. bezpieczeństwa powinni zwracać szczególną uwagę na takie wzorce podczas przeglądania kodu i audytów bezpieczeństwa. Więcej informacji na temat takich przypadków znajdziesz w artykule Content Security Policy: A Successful Mess Between Hardening and Łatigation (Polityka zabezpieczeń treści: udana korespondencja między wzmocnieniem a ograniczeniem).
Więcej informacji
- CSP Is Dead, Long Live CSP! O niebezpieczeństwie list białych i przyszłości Content Security Policy
- Sprawdzanie dostawcy usług w chmurze
- LocoMoco Conference: Content Security Policy - A successful mess between hardening and mitigation
- Prezentacja na konferencji Google I/O: zabezpieczanie aplikacji internetowych za pomocą funkcji nowoczesnej platformy