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

Obsługa przeglądarek

  • Chrome: 52.
  • Krawędź: 79.
  • Firefox: 52.
  • Safari: 15.4

Źródło

XSS, możliwość wstrzykiwania złośliwych skryptów do aplikacji internetowej największą lukę w zabezpieczeniach sieci od ponad 10 lat.

Polityka bezpieczeństwa treści (CSP) to dodatkowa warstwa zabezpieczeń, która pomaga ograniczyć XSS. Aby skonfigurować CSP, dodaj do strony internetowej nagłówek HTTP Content-Security-Policy i ustaw wartości, które kontrolować, jakie zasoby klient użytkownika może wczytywać na tej stronie.

Na tej stronie dowiesz się, jak używać CSP na podstawie liczb jednorazowych lub haszy, aby zniwelować XSS. zamiast często używanych dostawców CSP opartych na liście dozwolonych hosta, którzy często opuszczają stronę narażone na XSS, ponieważ mogą zostać ominięte w większości konfiguracji.

Kluczowe hasło: liczba jednorazowa to liczba losowa używana tylko raz, której można użyć do oznaczenia <script> jako zaufany.

Kluczowy termin: funkcja skrótu to funkcja matematyczna, która przekształca dane wejściowe. w skompresowaną wartość liczbową tzw. hash. Możesz użyć skrótu (na przykład SHA-256), aby oznaczyć wbudowany <script> jako zaufany.

Polityka bezpieczeństwa treści opartą na liczbach jednorazowych lub haszach jest często nazywana rygorystycznego CSP. Jeśli aplikacja używa rygorystycznego CSP, hakerzy, którzy znajdują kod HTML które zwykle nie mogą użyć ich do wymuszenia uruchomienia przeglądarki złośliwe skrypty w dokumencie z luką w zabezpieczeniach. Dzieje się tak, ponieważ tylko rygorystyczne zasady CSP zezwala na zaszyfrowane skrypty lub skrypty z prawidłową wartością jednorazową wygenerowaną w tagu więc osoby przeprowadzające atak nie mogą wykonać skryptu bez znajomości poprawnej wartości jednorazowej dla danej odpowiedzi.

Dlaczego należy używać rygorystycznego CSP?

Jeśli masz już w witrynie wartości CSP podobne do script-src www.googleapis.com, ale prawdopodobnie nie będzie skuteczna w innych witrynach. Ten typ CSP jest nazywany dodanie CSP do listy dozwolonych. Wymagają dużego dostosowania i mogą być ominięte przez osoby przeprowadzające atak.

Rygorystyczne dostawcy usług CSP opierające się na kryptograficznych liczbach jednorazowych lub haszach, unikają tych pułapek.

Ścisła struktura CSP

Podstawowa rygorystyczna zasada Content Security Policy używa jednej z poniższych odpowiedzi HTTP nagłówki:

Rygorystyczne zasady CSP oparte na identyfikatorach całkowitych

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Jak działa rygorystyczne zasady CSP oparte na liczbie jednorazowej.

Rygorystyczne zasady CSP oparte na haszach

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” i bezpieczne:

  • Używa liczb jednorazowych 'nonce-{RANDOM}' lub haszy 'sha256-{HASHED_INLINE_SCRIPT}'. wskazuje tagi <script>, które deweloper witryny może wykorzystać do wykonania w przeglądarce użytkownika.
  • Ustawia wartość 'strict-dynamic' aby uprościć wdrażanie CSP opartego na liczbie jednorazowej lub opartej na haszach przez automatyczne co umożliwia wykonywanie skryptów tworzonych przez zaufany skrypt. To także umożliwia korzystanie z większości zewnętrznych bibliotek i widżetów JavaScript.
  • Adres URL nie jest oparty na listach dozwolonych adresów URL, typowych omijania CSP.
  • Blokuje niezaufane skrypty wbudowane, takie jak wbudowane moduły obsługi zdarzeń lub javascript: Identyfikatory URI.
  • Blokuje on object-src możliwość wyłączania niebezpiecznych wtyczek, takich jak Flash.
  • Ogranicza działanie usługi base-uri do blokowania wstrzykiwania <base> tagów. Zapobiega to przed zmianą lokalizacji skryptów wczytywanych ze względnych adresów URL.
.

Stosuj rygorystyczny standard CSP

Aby przyjąć rygorystyczne zasady CSP, musisz:

  1. Określ, czy aplikacja powinna ustawić CSP na podstawie liczby jednorazowej czy opartej na haszowaniu.
  2. Skopiuj wartość CSP z sekcji Ścisła struktura CSP i ustaw ją. jako nagłówek odpowiedzi w aplikacji.
  3. Refaktoryzacja szablonów HTML i kodu po stronie klienta w celu usunięcia wzorców, które niezgodne z CSP.
  4. Wdróż CSP.

Możesz używać Lighthouse, (wersja 7.3.0 i nowsza z flagą --preset=experimental) Audyt Sprawdzone metody w trakcie tego procesu, by sprawdzić, czy witryna ma CSP na tyle rygorystyczne, aby były skuteczne w odniesieniu do XSS.

Latarnia morska
  zgłosić ostrzeżenie, że w trybie egzekwowania nie znaleziono CSP.
Jeśli witryna nie ma CSP, Lighthouse wyświetli to ostrzeżenie.

Krok 1. Określ, czy potrzebujesz CSP opartego na liczbie jednorazowej czy opartej na haszach

Oto jak działają te 2 rodzaje rygorystycznego CSP:

CSP oparty na liczbie jednorazowej

W przypadku CSP opartego na liczbie jednorazowej generujesz losową liczbę w czasie działania i uwzględniasz ją w do CSP i powiąż go z każdym tagiem skryptu na stronie. Osoba przeprowadzająca atak nie może umieścić ani uruchomić złośliwego skryptu na stronie, ponieważ musiałby on odgadnij poprawną losową liczbę dla tego skryptu. Działa tylko wtedy, gdy liczba jest nie do odgadnięcia i generuje nowe w czasie działania dla każdej odpowiedzi.

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

CSP oparty na haszach

W przypadku CSP opartych na haszach do CSP jest dodawany hasz każdego wbudowanego tagu skryptu. Każdy skrypt ma inny hasz. Osoba przeprowadzająca atak nie może zawierać ani uruchamiać złośliwego oprogramowania skrypt na stronie, bo jego hasz musi znajdować się CSP.

Używaj CSP opartych na haszach w przypadku stron HTML wyświetlanych statycznie lub stron, które mają być w pamięci podręcznej. Na przykład w przypadku witryny jednostronicowej możesz użyć CSP opartego na haszowaniu aplikacji utworzonych na platformach takich jak Angular, React itp., statycznie wyświetlany bez renderowania po stronie serwera.

Krok 2. Ustaw rygorystyczny CSP i przygotuj skrypty

Podczas konfigurowania CSP masz kilka opcji:

  • Tryb „tylko raport” (Content-Security-Policy-Report-Only) lub tryb egzekwowania (Content-Security-Policy). W trybie „tylko do raportu” CSP nie blokuje zasobów, więc nic w witrynie nie działa, ale mogą pojawić się błędy i zgłoszenia dotyczące czegoś, co byłoby zablokowane. Lokalnie, gdy jesteś przy ustawianiu CSP, nie ma to znaczenia, ponieważ oba tryby pokazują w konsoli przeglądarki. Tryb wymuszania pomoże Ci znaleźć blokowanych przez CSP wersji roboczej, ponieważ zablokowanie zasobu może sprawić, że strona wygląda na uszkodzoną. Tryb „Tylko raport” staje się najbardziej przydatny na późniejszym etapie (patrz Krok 5).
  • Nagłówek lub tag HTML <meta>. W przypadku programowania lokalnego tag <meta> może być bardziej jest to wygodne do dostosowania 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 nagłówek HTTP.
    • Jeśli chcesz ustawić CSP w trybie „Tylko raport”, ustaw go jako nagłówek, ponieważ metatagi CSP nie obsługują trybu tylko do raportowania.

Opcja A: CSP na podstawie liczby jednorazowej

Ustaw następującą odpowiedź HTTP Content-Security-Policy nagłówek w aplikacji:

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

Generowanie liczby jednorazowej dla CSP

Liczba jednorazowa to liczba losowa używana tylko raz na wczytanie strony. Oparta na liczbie jednorazowej CSP może ograniczyć XSS tylko wtedy, gdy atakujący nie mogą odgadnąć wartości liczby jednorazowej. O Wartość jednorazowa CSP musi być:

  • Silna kryptograficznie wartość losowa (najlepiej o długości co najmniej 128 bitów).
  • Nowe generowane na potrzeby każdej odpowiedzi
  • Z kodowaniem Base64

Oto kilka przykładów dodawania liczby jednorazowej CSP w platformach 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("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 mają atrybut nonce pasujący do liczby jednorazowej losowej określona w nagłówku CSP. Wszystkie skrypty mogą mieć taką samą wartość liczba jednorazowa. Pierwszym krokiem jest dodanie tych atrybutów do wszystkich skryptów, aby CSP na to pozwala.

Opcja B: nagłówek odpowiedzi CSP oparty na haszach

Ustaw następującą odpowiedź HTTP Content-Security-Policy nagłówek w aplikacji:

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

W przypadku wielu skryptów w tekście składnia wygląda tak: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'

Dynamiczne ładowanie skryptów źródłowych

Skróty CSP są obsługiwane tylko w przeglądarkach w przypadku skryptów wbudowanych, wszystkie skrypty zewnętrzne muszą być ładowane dynamicznie za pomocą wbudowanego skryptu. Szyfrowanie skryptów źródłowych nie jest obsługiwane w różnych przeglądarkach.

Przykład wbudowanego skryptu
Dozwolone przez 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.head.appendChild(s);
  });
</script>
Aby uruchomić ten skrypt, musisz obliczyć hasz wbudowanego skryptu i dodaj go do nagłówka odpowiedzi CSP, zastępując {HASHED_INLINE_SCRIPT} . Aby zmniejszyć liczbę haszów, możesz scalić wszystkie wiadomości w tekście w jednym scenariuszu. Aby zobaczyć, jak to działa, zobacz przykład i jego kod.
Zablokowane przez CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP blokuje te skrypty, ponieważ tylko skrypty wbudowane mogą być zaszyfrowane.

Uwagi na temat wczytywania skryptu

Przykładowy skrypt wbudowany dodaje element s.async = false, aby zapewnić który jest wykonywany przez foo przed bar, nawet jeśli Najpierw wczytuje się bar. 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, jak tak jak w przypadku async skryptów. Jednak dzięki temu fragmentowi pamiętaj:

  • Jeden lub oba skrypty mogą być wykonywane, zanim dokument zostanie ukończony pobieranie. Jeśli chcesz, aby dokument był gotowy przed skryptów, poczekaj na zdarzenie DOMContentLoaded przed dołączysz skrypty. Jeśli powoduje to problem z wydajnością, ponieważ pobieranie skryptów nie rozpoczyna się wystarczająco wcześnie, użyj wstępnego wczytywania tagów na stronie.
  • defer = true nic nie robi. Jeśli tego potrzebujesz w celu uruchomienia działania skryptu, w razie potrzeby uruchom go ręcznie.

Krok 3. Refaktoryzacja szablonów HTML i kodu po stronie klienta

Wbudowane moduły obsługi zdarzeń (np. onclick="…", onerror="…") i identyfikatory URI JavaScript (<a href="javascript:…">) może służyć do uruchamiania skryptów. Oznacza to, że Jeśli atakujący znajdzie błąd XSS, może wstrzyknąć taki kod HTML i wykonać złośliwe oprogramowanie JavaScriptu. Protokół CSP oparty na liczbie jednorazowej lub opartej na haszach nie zezwala na używanie tego rodzaju znaczników. Jeśli Twoja witryna korzysta z któregoś z tych wzorców, musisz je przekształcić, aby były bezpieczniejsze i innych rozwiązań.

Jeśli w poprzednim kroku włączono CSP, naruszenia zasad dotyczących CSP pojawią się tutaj: za każdym razem, gdy CSP zablokuje niezgodny wzorzec.

Raporty o naruszeniach zasad CSP w konsoli programisty Chrome
Błędy konsoli związane z blokowanym kodem.

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

Refaktoryzacja wbudowanych modułów obsługi zdarzeń

Dozwolone przez CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP zezwala na moduły obsługi zdarzeń zarejestrowane przy użyciu JavaScriptu.
Zablokowane przez CSP
<span onclick="doThings();">A thing.</span>
CSP blokuje wbudowane moduły obsługi zdarzeń.

Refaktoryzacja identyfikatorów URI javascript:

Dozwolone przez CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP zezwala na moduły obsługi zdarzeń zarejestrowane przy użyciu JavaScriptu.
Zablokowane przez CSP
<a href="javascript:linkClicked()">foo</a>
CSP blokuje identyfikatory URI w języku JavaScript.

Usuń eval() z JavaScriptu

Jeśli Twoja aplikacja używa eval() do konwertowania serializacji ciągów JSON na JS musisz dokonać refaktoryzacji takich instancji na JSON.parse(), co również szybciej.

Jeśli nie możesz usunąć wszystkich zastosowań eval(), nadal możesz ustawić rygorystyczną liczbę jednorazową CSP, ale musisz używać słowa kluczowego CSP 'unsafe-eval', dzięki któremu są nieco mniej bezpieczne.

Te i inne przykłady takich refaktoryzacji znajdziesz w tym rygorystycznym CSP codelab:

Krok 4 (opcjonalny). Dodaj wartości zastępcze na potrzeby obsługi starych wersji przeglądarek

Obsługa przeglądarek

  • Chrome: 52.
  • Krawędź: 79.
  • Firefox: 52.
  • Safari: 15.4

Źródło

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

  • Użycie elementu strict-dynamic wymaga dodania elementu https: jako kreacji zastępczej dla wcześniejszego stanu Safari. Gdy to zrobisz:
    • Wszystkie przeglądarki, które obsługują strict-dynamic, ignorują kreację zastępczą https:. nie obniży to skuteczności zasady.
    • W starszych przeglądarkach skrypty pozyskane z zewnątrz mogą być wczytywane tylko wtedy, gdy pochodzą z punkt początkowy HTTPS. Jest to mniej bezpieczne niż rygorystyczne CSP, ale zapobiega niektórym częstym przyczynom XSS, takim jak wstrzykiwanie identyfikatorów URI typu javascript:.
  • Aby zapewnić zgodność ze starszymi wersjami przeglądarek (ponad 4 lata), możesz dodać unsafe-inline jako kreacji zastępczej. Wszystkie ostatnie przeglądarki ignorują unsafe-inline jeśli jest dostępna wartość 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

Jeśli upewnisz się, że CSP nie blokuje prawidłowych skryptów w w lokalnym środowisku programistycznym, możesz wdrożyć CSP na etapie przejściowym, a następnie środowisko produkcyjne:

  1. (Opcjonalnie) Wdróż CSP w trybie tylko do raportu za pomocą Nagłówek Content-Security-Policy-Report-Only. Tryb „Tylko raportowanie” jest przydatny przetestuj potencjalnie zmieniającą się zmianę, tak jak w przypadku nowego CSP w środowisku produkcyjnym, zacząć egzekwować ograniczenia CSP. W trybie „tylko do raportu” CSP nie wpływa na działanie aplikacji, ale przeglądarka nadal generuje błędy konsoli i zgłoszeniami o naruszeniach w przypadku wzorców niezgodnych z CSP. aby zobaczyć, co niekorzystnie wpłynie na użytkowników. Więcej więcej informacji zawiera Reporting API.
  2. Jeśli masz pewność, że CSP nie zepsuje witryny użytkownikom, wdrożyć CSP za pomocą nagłówka odpowiedzi Content-Security-Policy. Śr zalecamy skonfigurowanie CSP za pomocą nagłówka HTTP po stronie serwera, jest bezpieczny niż tag <meta>. Gdy wykonasz ten krok, CSP zacznie się aby chronić aplikację przed atakami XSS.
.

Ograniczenia

Rygorystyczne zasady CSP zapewniają zwykle silny dodatkowy poziom zabezpieczeń, który pomaga i ograniczanie XSS. W większości przypadków CSP znacznie zmniejsza powierzchnię ataku, odrzucanie niebezpiecznych wzorców, takich jak identyfikatory URI javascript:. Jednakże, używanego CSP (liczba jednorazowa, hasze, z elementem 'strict-dynamic' lub bez niego), to przypadki, w których CSP nie chroni Twojej aplikacji również:

  • Jeśli generujesz jednorazowy skrypt, ale wstrzykujesz go bezpośrednio do treści lub Parametr src tego elementu <script>.
  • Jeśli występują wstrzykiwanie w lokalizacji skryptów tworzonych dynamicznie (document.createElement('script')), w tym w wszystkich funkcjach biblioteki tworzący węzły DOM (script) na podstawie wartości ich argumentów. Ten zawiera kilka popularnych interfejsów API, takich jak .html() jQuery, a także .get() i .post() w jQuery < 3.0
  • Czy w starych aplikacjach AngularJS istnieją wstrzykiwanie szablonów. Osoba przeprowadzająca atak który można wstrzykiwać do szablonu AngularJS, może go używać do wykonywanie dowolnego JavaScriptu.
  • Jeśli zasada zawiera 'unsafe-eval', wstrzykiwanie tekstu w eval() setTimeout() i kilka innych rzadko używanych interfejsów API.

Programiści i inżynierowie ds. bezpieczeństwa powinni zwracać szczególną uwagę na podczas sprawdzania kodu i kontroli bezpieczeństwa. Więcej informacji znajdziesz na znajdziesz je w artykule Content Security Policy: A Successful Mess Between Hardening and Łatigation.

.

Więcej informacji