Cross-site scripting (XSS), czyli możliwość wstrzyknięcia szkodliwych skryptów do aplikacji internetowej, jest od ponad dekady 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 używać CSP opartego na wartościach nonce lub haszy, aby ograniczyć ryzyko ataków XSS, zamiast powszechnie stosowanych CSP opartych na liście dozwolonych hostów, które często pozostawiają stronę bez ochrony przed atakami XSS, ponieważ można je ominąć w większości konfiguracji.
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 wstrzykiwaniu kodu HTML, nie mogą ich wykorzystać do wymuszenia na przeglądarce wykonania złośliwych skryptów w dokumentach z luką. 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ż tag CSP podobny do script-src www.googleapis.com
, prawdopodobnie nie jest on skuteczny w przeciwdziałaniu atakom między witrynami. Ten typ CSP nazywamy listą dozwolonych CSP. Wymagają one wielu dostosowań i mogą być omijane przez atakujących.
W przypadku rygorystycznych zasad CSP opartych na kryptograficznych identyfikatorach losowych lub wartościach skrótowych nie ma takich problemów.
Struktura ścisła 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';
Właściwości, które sprawiają, że usługa CSP jest „surowa”, a co za tym idzie bezpieczna:
- 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 używanie większości zewnętrznych bibliotek JavaScriptu i widżetów. - Nie opiera się na listach dozwolonych adresów URL, więc nie jest podatny na częste obejścia CSP.
- Blokuje niesprawdzone skrypty wstawiane inline, takie jak moduły obsługi zdarzeń wstawiane inline czy identyfikatory URI danych.
javascript:
- Ogranicza ono
object-src
do wyłączania niebezpiecznych wtyczek, takich jak Flash. - Ogranicza ona dostęp do
base-uri
, aby zablokować wstrzykiwanie tagów<base>
. Zapobiega to zmianie przez atakujących lokalizacji skryptów wczytywanych z względnych adresów URL.
Stosowanie ścisłych zasad CSP
Aby wdrożyć ścisłe zasady CSP, musisz:
- Zdecyduj, czy aplikacja ma ustawić CSP na podstawie liczby jednorazowej czy hasza.
- Skopiuj CSP z sekcji Szczegółowe informacje o strukturze CSP i ustaw go jako nagłówek odpowiedzi w aplikacji.
- Przerzuć szablony HTML i kod po stronie klienta, aby usunąć wzorce niezgodne z CSP.
- Wdróż CSP.
W trakcie tego procesu możesz użyć Lighthouse (w wersji 7.3.0 lub nowszej z flagą --preset=experimental
) do sprawdzenia, czy Twoja witryna ma zasadę CSP i czy jest ona wystarczająco rygorystyczna, aby skutecznie chronić przed atakami XSS.
Krok 1. Zdecyduj, czy potrzebujesz CSP opartego na liczbie losowym lub na haszu
Oto jak działają 2 rodzaje rygorystycznych zasad CSP:
CSP oparty na niepowtarzalnych identyfikatorach
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ą losową liczbę 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 opartego na łańcuchu haszowym do CSP jest dodawany łańcuch haszowy każdego tagu skryptu wbudowanego. 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ż aby mógł on działać, jego hasz musi znajdować się 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ą dostarczane 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. Tryb egzekwowania może pomóc w znalezieniu zasobów blokowanych przez wersję roboczą CSP, ponieważ zablokowanie zasobu może spowodować, że strona będzie wyglądać na uszkodzoną. Tryb tylko do raportowania jest najbardziej przydatny w późniejszej fazie procesu (patrz Krok 5). - nagłówku lub tagu HTML
<meta>
. W przypadku rozwoju lokalnego tag<meta>
może być wygodniejszy do dostosowania CSP i szybkiego sprawdzenia, jaki ma on wpływ na witrynę. Jednak:- Podczas późniejszego wdrażania CSP w produkcji 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
Niepowtarzalny identyfikator to losowa liczba używana tylko raz podczas wczytywania strony. CSP oparty na wartościach nonce może ograniczyć XSS tylko wtedy, gdy atakujący nie może odgadnąć wartości nonce. CSP nonce musi:
- wartość losowa o wysokiej odporności na szyfrowanie (najlepiej o długości co najmniej 128 bitów);
- nowo generowane dla każdej odpowiedzi;
- zakodowany w formacie Base64,
Oto kilka przykładów dodawania nonce’a CSP w ramkach 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 }); });
dodać 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>
Ważne informacje o wczytywaniu skryptów
Przykład skryptu w ciele wiadomości dodaje tag 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 funkcja s.async = false
nie blokuje parsowania podczas wczytywania skryptów, ponieważ skrypty są dodawane dynamicznie. Parser przestaje działać 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 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
nie robi nic. Jeśli potrzebujesz takiego zachowania, uruchom skrypt ręcznie, gdy zajdzie taka potrzeba.
Krok 3. Przerzuć szablony HTML i kod 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 używa któregoś z tych wzorów, musisz go przekształcić w bezpieczniejsze rozwiązanie.
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>
Przerzuć javascript:
identyfikatory URI
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Usuń eval()
z kodu JavaScript
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 możesz usunąć wszystkich wystąpień eval()
, nadal możesz skonfigurować ścisłą politykę CSP o zasadzie nonce, ale musisz użyć słowa kluczowego CSP 'unsafe-eval'
, co spowoduje, że polityka będzie 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:
- Korzystanie z
strict-dynamic
wymaga dodaniahttps:
jako opcji zapasowej w przypadku starszych wersji 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 starszych przeglądarkach skrypty pochodzące z zewnątrz mogą się wczytywać tylko wtedy, gdy pochodzą z ź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 starszymi wersjami przeglądarek (starszymi niż 4 lata), możesz dodać opcję
unsafe-inline
jako alternatywę. 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 Interfejs Reporting API. - Gdy będziesz mieć 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 o tych przypadkach znajdziesz w artykule Zasady bezpieczeństwa treści: skuteczne zabezpieczenie i ograniczenie zagrożeń.
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 z Google I/O: zabezpieczanie aplikacji internetowych za pomocą funkcji nowoczesnej platformy