Ogranicz ataki typu cross-site scripting (XSS) przy użyciu rygorystycznej zasady Content Security Policy (CSP)

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Cross-site scripting (XSS), czyli możliwość wstrzykiwania złośliwych skryptów do aplikacji internetowej, od ponad dekady jest jedną z największych luk w zabezpieczeniach internetowych.

Content Security Policy (CSP) to dodatkowa warstwa zabezpieczeń, która pomaga ograniczyć ataki typu XSS. Aby skonfigurować CSP, dodaj do strony internetowej nagłówek HTTP Content-Security-Policy i ustaw wartości, które określają, jakie zasoby agent użytkownika może wczytać 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 tego użyjemy powszechnie stosowanych standardów CSP opartych na liście dozwolonych hostów, które często pozostawiają stronę podatną na ataki XSS, ponieważ w większości konfiguracji można je obejść.

Kluczowe pojęcie: wartość nonce 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ą zwaną skrótem. Aby oznaczyć tag wstawiony <script> jako zaufany, możesz użyć haszu (np. SHA-256).

Standard Content Security Policy oparty na jednorazowych kodach lub skrótach jest często nazywany ścisłym CSP. Jeśli aplikacja używa ścisłych zasad CSP, hakerzy, którzy znajdą luki wstrzykiwania kodu HTML, zwykle nie mogą ich wykorzystać do zmuszenia przeglądarki do wykonania złośliwych skryptów w podatnym na ataki dokumencie. Dzieje się tak, ponieważ ścisła zasada CSP zezwala tylko na skrypty z wartością skrótu lub skrypty z prawidłową wartością nonce wygenerowaną na serwerze, więc atakujący nie mogą wykonać skryptu bez znajomości prawidłowej wartości nonce dla danej odpowiedzi.

Dlaczego warto używać ścisłej strategii CSP?

Jeśli Twoja witryna ma już CSP podobną do script-src www.googleapis.com, prawdopodobnie nie jest ona skuteczna w przypadku ataków typu cross-site. Ten typ CSP nazywa się CSP z białą listą. Wymagają one wielu dostosowań i mogą zostać ominięte przez atakujących.

Ścisłe zasady CSP oparte na kryptograficznych wartościach nonce lub skrótach pozwalają uniknąć tych pułapek.

Ścisła struktura CSP

Podstawowa ścisła zasada Content Security Policy używa jednego z tych nagłówków odpowiedzi HTTP:

Rygorystyczne CSP oparte na wartości nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';
Jak działają rygorystyczne CSP oparte na jednorazowych ciągach znaków.

Ścisła zasada CSP oparta na haszowaniu

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';

Te właściwości sprawiają, że zasady CSP, takie jak te, są „ścisłe”, a tym samym bezpieczne:

  • Używa ona wartości nonce 'nonce-{RANDOM}' lub funkcji skrótu 'sha256-{HASHED_INLINE_SCRIPT}', aby wskazać, którym tagom <script> deweloper witryny ufa na tyle, że mogą być wykonywane w przeglądarce użytkownika.
  • Ustawia wartość 'strict-dynamic', aby zmniejszyć nakład pracy związany z wdrażaniem CSP opartego na liczbach jednorazowych lub haszach przez automatyczne zezwalanie na wykonywanie skryptów utworzonych przez zaufany skrypt. Odblokowuje to też używanie większości bibliotek i widżetów JavaScriptu innych firm.
  • Nie opiera się na listach dozwolonych adresów URL, więc nie jest podatny na częste obejścia CSP.
  • Blokuje niezaufane skrypty wbudowane, takie jak wbudowane moduły obsługi zdarzeń lub identyfikatory URI javascript:.
  • Ogranicza object-src, aby wyłączyć niebezpieczne wtyczki, takie jak Flash.
  • Ogranicza base-uri, aby blokować wstawianie tagów <base>. Zapobiega to zmianie przez atakujących lokalizacji skryptów wczytywanych z względnych adresów URL.

Wdrożenie ścisłej polityki CSP

Aby wdrożyć rygorystyczne CSP, musisz:

  1. Zdecyduj, czy aplikacja ma ustawiać CSP opartą na liczbie jednorazowej czy na haszu.
  2. Skopiuj CSP z sekcji Struktura rygorystycznych CSP i ustaw go jako nagłówek odpowiedzi w całej aplikacji.
  3. Przebuduj szablony HTML i kod po stronie klienta, aby usunąć wzorce niezgodne z CSP.
  4. Wdróż CSP.

W trakcie tego procesu możesz używać audytu Najlepsze praktykiLighthouse (wersja 7.3.0 lub nowsza z flagą --preset=experimental), aby sprawdzić, czy Twoja witryna ma CSP i czy jest ona wystarczająco rygorystyczna, aby skutecznie chronić przed atakami XSS.

Lighthouse
  ostrzega, że w trybie egzekwowania nie znaleziono CSP.
Jeśli Twoja witryna nie ma CSP, Lighthouse wyświetli to ostrzeżenie.

Krok 1. Zdecyduj, czy potrzebujesz CSP opartego na liczbie jednorazowej czy na haszu

Oto jak działają 2 rodzaje rygorystycznych CSP:

CSP oparty na wartości nonce

W przypadku CSP opartego na liczbie jednorazowej generujesz losową liczbę w czasie działania, umieszczasz ją w CSP i łączysz z każdym tagiem skryptu na stronie. Atakujący nie może umieścić ani uruchomić na Twojej stronie złośliwego skryptu, ponieważ musiałby odgadnąć prawidłową liczbę losową dla tego skryptu. Działa to tylko wtedy, gdy liczba nie jest zgadywalna i jest generowana na nowo w czasie działania programu dla każdej odpowiedzi.

Używaj CSP opartego na jednorazowym kodzie w przypadku stron HTML renderowanych na serwerze. W przypadku tych stron możesz utworzyć nową liczbę losową dla każdej odpowiedzi.

CSP oparty na haszowaniu

W przypadku CSP opartego na haszowaniu do CSP dodawany jest hasz każdego tagu skryptu wbudowanego. Każdy skrypt ma inny hash. Atakujący nie może umieścić ani uruchomić na Twojej stronie złośliwego skryptu, ponieważ jego hash musi znajdować się w zasadach CSP, aby można było go uruchomić.

Używaj CSP opartego na haszowaniu w przypadku statycznie wyświetlanych stron HTML lub stron, które muszą być buforowane. Możesz na przykład użyć CSP opartego na haszowaniu w przypadku jednostronicowych aplikacji internetowych utworzonych za pomocą platform takich jak Angular, React itp., które są statycznie obsługiwane bez renderowania po stronie serwera.

Krok 2. Skonfiguruj ścisłą zasadę CSP i przygotuj skrypty

Podczas konfigurowania CSP masz kilka opcji:

  • Tryb tylko raportowania (Content-Security-Policy-Report-Only) lub tryb egzekwowania (Content-Security-Policy). W trybie tylko raportowania CSP nie blokuje jeszcze zasobów, więc nic w Twojej witrynie nie ulegnie uszkodzeniu, ale możesz zobaczyć błędy i otrzymywać raporty o wszystkim, co zostałoby zablokowane. Lokalnie, podczas ustawiania CSP, nie ma to większego znaczenia, ponieważ w obu trybach błędy są wyświetlane 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 raportowania staje się najbardziej przydatny w późniejszej części procesu (patrz krok 5).
  • nagłówka lub tagu HTML <meta>. W przypadku lokalnego tworzenia stron tag <meta> może być wygodniejszy do dostosowywania CSP i szybkiego sprawdzania, jak wpływa on na witrynę. Pamiętaj jednak, że:
    • Później, podczas wdrażania CSP w środowisku produkcyjnym, zalecamy ustawienie go jako nagłówka HTTP.
    • Jeśli chcesz ustawić CSP w trybie tylko raportowania, musisz ustawić go jako nagłówek, ponieważ tagi meta CSP nie obsługują trybu tylko raportowania.

Opcja A. Standard CSP oparty na jednorazowym kodzie

W aplikacji ustaw ten nagłówek Content-Security-Policy odpowiedzi HTTP:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Generowanie wartości nonce dla CSP

Wartość nonce to losowa liczba używana tylko raz podczas wczytywania strony. CSP oparty na liczbie jednorazowej może ograniczyć ryzyko XSS tylko wtedy, gdy osoby przeprowadzające atak nie mogą odgadnąć wartości liczby jednorazowej. Wartość nonce CSP musi:

  • Kryptograficznie silna wartość losowa (najlepiej o długości co najmniej 128 bitów).
  • Generowane na nowo przy każdej odpowiedzi
  • zakodowany w formacie Base64,

Oto kilka przykładów dodawania nonce CSP w frameworkach po stronie serwera:

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(&>quot;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 liczbie jednorazowej każdy element <script> musi mieć atrybut nonce, który jest zgodny z wartością losowej liczby jednorazowej określoną w nagłówku CSP. Wszystkie skrypty mogą mieć ten sam identyfikator nonce. Pierwszym krokiem jest dodanie tych atrybutów do wszystkich skryptów, aby CSP je zezwalał.

Opcja B. Nagłówek odpowiedzi CSP oparty na haszowaniu

W aplikacji ustaw ten nagłówek Content-Security-Policy odpowiedzi HTTP:

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 jest następująca:'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'

Dynamiczne ładowanie skryptów ze źródeł

Skrypty zewnętrzne możesz wczytywać dynamicznie za pomocą skryptu wbudowanego.

Przykład sposobu wstawiania skryptów w treści.
Zgodne z zasadami CSP
<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.hea<d.appen>dChild(s);
  });
/script
Aby umożliwić uruchomienie tego skryptu, musisz obliczyć jego hash i dodać go do nagłówka odpowiedzi CSP, zastępując symbol zastępczy {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.
Zablokowane przez CSP
<script src="https://example.org/fo><o.js&qu>o<t;/script
script src="https://exam><ple.org>/bar.js"/script
CSP blokuje te skrypty, ponieważ nie zostały dodane dynamicznie i nie mają atrybutu integrity, który pasuje do dozwolonego źródła.

Uwagi dotyczące wczytywania skryptów

W przykładzie skryptu wbudowanego dodano s.async = false, aby zapewnić, że foo zostanie wykonany przed bar, nawet jeśli bar zostanie wczytany jako pierwszy. W tym fragmencie kodu s.async = false nie blokuje parsera podczas wczytywania skryptów, ponieważ są one dodawane dynamicznie. Analizator 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 wykonania skryptów, poczekaj na DOMContentLoaded zdarzenie przed dołączeniem skryptów. Jeśli powoduje to problemy 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 = true nie robi nic. 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 funkcji obsługi zdarzeń (np. onclick="…", onerror="…") i adresów URI JavaScriptu (<a href="javascript:…">). Oznacza to, że atakujący, który znajdzie błąd XSS, może wstrzyknąć tego rodzaju kod HTML i wykonać złośliwy kod JavaScript. Zasady CSP oparte na wartości nonce lub funkcji skrótu zabraniają używania tego rodzaju znaczników. Jeśli Twoja witryna używa któregoś z tych wzorców, musisz zastąpić go bezpieczniejszą alternatywą.

Jeśli w poprzednim kroku włączysz CSP, za każdym razem, gdy CSP zablokuje niezgodny wzorzec, w konsoli zobaczysz naruszenia CSP.

Raporty o naruszeniach CSP w konsoli programisty Chrome.
Błędy konsoli dotyczące zablokowanego kodu.

W większości przypadków rozwiązanie jest proste:

Refaktoryzacja wbudowanych modułów obsługi zdarzeń

Zgodne z zasadami CSP
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
  document.getElementById('things').addEventL<istener>('click', doThings);
/script
CSP zezwala na moduły obsługi zdarzeń zarejestrowane za pomocą JavaScriptu.
Zablokowane przez CSP
<span onclick="doThing>s();&quo<t;A t>hing./span
CSP blokuje wbudowane moduły obsługi zdarzeń.

Refaktoryzacja identyfikatorów URIjavascript:

Zgodne z zasadami CSP
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
  document.getElementById('foo').addEventList<ener(&#>39;click', linkClicked);
/script
CSP zezwala na moduły obsługi zdarzeń zarejestrowane za pomocą JavaScriptu.
Zablokowane przez CSP
<a href="javascript:linkClick>ed(<)&>quot;foo/a
CSP blokuje JavaScript: identyfikatory URI.

Usuwanie eval() z kodu JavaScript

Jeśli Twoja aplikacja używa funkcji eval() do konwertowania serializacji ciągów znaków JSON na obiekty JS, zmień takie instancje na JSON.parse(), która jest też szybsza.

Jeśli nie możesz usunąć wszystkich wystąpień eval(), nadal możesz ustawić ścisłą politykę CSP opartą na jednorazowym kodzie, ale musisz użyć słowa kluczowego CSP 'unsafe-eval', co sprawi, że Twoja polityka będzie nieco mniej bezpieczna.

Więcej przykładów takich refaktoryzacji znajdziesz w tym ćwiczeniu z programowania dotyczącym ścisłej strategii CSP:

Krok 4. (Opcjonalnie) Dodaj alternatywne rozwiązania, aby obsługiwać starsze wersje przeglądarek

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Jeśli musisz obsługiwać starsze wersje przeglądarek:

  • Korzystanie z strict-dynamic wymaga dodania https: jako opcji rezerwowej w starszych wersjach Safari. Gdy to zrobisz:
    • Wszystkie przeglądarki obsługujące strict-dynamic ignorują https:, więc nie zmniejszy to skuteczności zasady.
    • W starszych przeglądarkach skrypty pochodzące z zewnątrz mogą się wczytywać tylko wtedy, gdy pochodzą ze źródła HTTPS. Jest to mniej bezpieczne niż ścisłe przestrzeganie standardu CSP, ale nadal zapobiega niektórym typowym przyczynom ataków typu XSS, takim jak wstrzykiwanie adresów URI javascript:.
  • Aby zapewnić zgodność ze starszymi wersjami przeglądarek (starszymi niż 4 lata), możesz dodać unsafe-inline jako opcję rezerwową. Wszystkie najnowsze przeglądarki ignorują unsafe-inline, jeśli występuje nonce lub hash CSP.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 9;none';
  base-uri 'none';

Krok 5. Wdróż CSP

Po potwierdzeniu, że zasady CSP nie blokują żadnych prawidłowych skryptów w lokalnym środowisku programistycznym, możesz wdrożyć je w środowisku testowym, a następnie w środowisku produkcyjnym:

  1. (Opcjonalnie) Wdróż CSP w trybie tylko do raportowania za pomocą nagłówka Content-Security-Policy-Report-Only. Tryb tylko raportowania jest przydatny do testowania w środowisku produkcyjnym potencjalnie powodujących problemy zmian, takich jak nowe zasady CSP, zanim zaczniesz egzekwować ograniczenia CSP. W trybie tylko raportowania zasady CSP nie wpływają na działanie aplikacji, ale przeglądarka nadal generuje błędy konsoli i raporty o naruszeniach, gdy napotyka wzorce niezgodne z zasadami CSP. Dzięki temu możesz zobaczyć, co mogłoby przestać działać u użytkowników. Więcej informacji znajdziesz w artykule Reporting API.
  2. Gdy masz pewność, że zasady CSP nie spowodują, że witryna przestanie działać dla użytkowników, wdróż je za pomocą nagłówka odpowiedzi Content-Security-Policy. Zalecamy ustawienie CSP po stronie serwera za pomocą nagłówka HTTP, ponieważ jest to bezpieczniejsze niż tag <meta>. Po wykonaniu tego kroku dostawca usług w chmurze zacznie chronić Twoją aplikację przed atakami XSS.

Ograniczenia

Ścisłe przestrzeganie standardu CSP zapewnia dodatkową warstwę zabezpieczeń, która pomaga ograniczyć ataki XSS. W większości przypadków CSP znacznie zmniejsza obszar narażony na atak, odrzucając niebezpieczne wzorce, takie jak javascript: URI. Jednak w zależności od typu używanego CSP (liczby jednorazowe, hasze, z dyrektywą 'strict-dynamic' lub bez niej) istnieją przypadki, w których CSP nie chroni aplikacji tak dobrze:

  • Jeśli zastosujesz nonce w przypadku skryptu, ale wstrzyknięcie nastąpi bezpośrednio do treści lub parametru src elementu <script>.
  • Jeśli wstrzykiwanie następuje w lokalizacjach skryptów tworzonych dynamicznie (document.createElement('script')), w tym w funkcjach biblioteki, które tworzą węzły DOM script na podstawie wartości argumentów. Dotyczy to niektórych popularnych interfejsów API, takich jak .html() w bibliotece jQuery, a także .get().post() w bibliotece jQuery w wersji starszej niż 3.0.
  • czy w starszych 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 zasada zawiera 'unsafe-eval', wstrzykiwanie do eval(), 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 sprawdzania 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 (w języku angielskim).

Więcej informacji