Polityka bezpieczeństwa treści

Standard Content Security Policy może znacznie zmniejszyć ryzyko i skutki ataków typu cross-site scripting w nowoczesnych przeglądarkach.

Joe Medley
Joe Medley

Obsługa przeglądarek

  • Chrome: 25.
  • Edge: 14.
  • Firefox: 23.
  • Safari: 7.

Źródło

Model bezpieczeństwa w internecie opiera się na zasadzie takiego samego pochodzenia. Na przykład kod z https://mybank.com musi mieć dostęp tylko do danych https://mybank.com, a dostęp do danych https://evil.example.com nigdy nie może być dozwolony. Teoretycznie każda domena jest odizolowana od reszty internetu, co daje deweloperom bezpieczne środowisko piaskownicy do tworzenia. W praktyce jednak atakujący znaleźli kilka sposobów na obejście systemu.

Ataki cross-site scripting (XSS) omijają zasadę tego samego pochodzenia, oszukując witrynę, aby dostarczała ona złośliwy kod wraz z zamierzoną treścią. To ogromny problem, ponieważ przeglądarki ufają wszystkiemu kodowi, który pojawia się na stronie, jako że jest on legalnym elementem pochodzenia zabezpieczeń tej strony. Poradnik dotyczący XSS to stary, ale reprezentatywny przegląd metod, których może użyć atakujący, aby naruszyć to zaufanie, wstrzykując złośliwy kod. Jeśli atakujący wstrzyknie jakikolwiek kod, spowoduje to naruszenie sesji użytkownika i uzyska dostęp do informacji prywatnych.

Ta strona przedstawia zasady ochrony treści (CSP) jako strategię zmniejszania ryzyka i wpływu ataków typu XSS w nowoczesnych przeglądarkach.

Komponenty CSP

Aby wdrożyć skuteczny mechanizm CSP, wykonaj te czynności:

  • Używaj list dozwolonych, aby informować klienta, co jest dozwolone, a co nie.
  • Dowiedz się, jakie dyrektywy są dostępne.
  • Dowiedz się, jakie słowa kluczowe są przez nie używane.
  • Ogranicz użycie kodu wbudowanego i elementu eval().
  • Zgłaszaj naruszenia zasad na serwerze przed ich egzekwowaniem.

Listy dozwolonych źródeł

Ataki XSS wykorzystują fakt, że przeglądarka nie potrafi odróżnić skryptu będącego częścią aplikacji od skryptu wstrzykniętego przez osobę trzecią. Na przykład przycisk Google +1 na dole tej strony wczytuje i wykonuje kod z https://apis.google.com/js/plusone.js w kontekście pochodzenia tej strony. Ufamy temu kodowi, ale nie możemy oczekiwać, że przeglądarka sama z siebie zorientuje się, że kod z apis.google.com jest bezpieczny do wykonania, a kod z apis.evil.example.com prawdopodobnie nie. Przeglądarka pobiera i wykonuje każdy kod, którego strona zażąda, niezależnie od źródła.

Nagłówek HTTP Content-Security-Policy w standardzie CSP umożliwia utworzenie listy dozwolonych źródeł zaufanych treści i informuje przeglądarkę, aby uruchamiała lub renderowała tylko zasoby z tych źródeł. Nawet jeśli atakujący znajdzie lukę, przez którą mógłby wstrzyknąć skrypt, skrypt nie będzie pasował do listy dozwolonych, a więc nie zostanie wykonany.

Ufamy, że apis.google.com dostarczy prawidłowy kod, i wierzymy, że my też to zrobimy. Oto przykład zasady, która zezwala na wykonywanie skryptów tylko wtedy, gdy pochodzą z jednego z tych 2 źródeł:

Content-Security-Policy: script-src 'self' https://apis.google.com

script-src to dyrektywa, która kontroluje zestaw uprawnień związanych ze skryptem na stronie. Ten nagłówek 'self' jako jeden prawidłowy źródło skryptu i https://apis.google.com jako drugie. Przeglądarka może teraz pobierać i wykonywać kod JavaScript z apis.google.com przez HTTPS, a także z źródła bieżącej strony, ale nie z żadnego innego źródła. Jeśli atakujący wstrzyknie kod do Twojej witryny, przeglądarka wyświetli błąd i nie uruchomi wstrzykniętego skryptu.

Błąd w konsoli: wczytywanie skryptu „http://evil.example.com/evil.js” zostało odrzucone, ponieważ narusza on tę dyrektywę Content Security Policy: script-src 'self' https://apis.google.com
Konsola wyświetla błąd, gdy skrypt próbuje się uruchomić z źródła, które nie znajduje się na liście dozwolonych.

Zasady dotyczą wielu różnych zasobów

CSP udostępnia zestaw dyrektyw zasad, które umożliwiają szczegółową kontrolę zasobów, które strona może wczytać, w tym script-src z poprzedniego przykładu.

Poniższa lista zawiera pozostałe dyrektywy dotyczące zasobów na poziomie 2. Opracowano specyfikację na poziomie 3, ale jest ona w dużej mierze niezrealizowana w głównych przeglądarkach.

base-uri
Ogranicza adresy URL, które mogą się pojawiać w elemencie <base> strony.
child-src
Wyświetla listę adresów URL pracowników i zawartości wbudowanych ramek. Na przykład child-src https://youtube.com umożliwia umieszczanie filmów z YouTube, ale nie z innych źródeł.
connect-src
Ogranicza źródła, z którymi możesz się łączyć za pomocą XHR, WebSockets i EventSource.
font-src
Określa źródła, które mogą dostarczać czcionki internetowe. Możesz na przykład zezwolić na czcionki internetowe Google za pomocą font-src https://themes.googleusercontent.com.
form-action
Wyświetla listę prawidłowych punktów końcowych do przesłania z tagów <form>.
frame-ancestors
Określa źródła, które mogą osadzić bieżącą stronę. Ta dyrektywa dotyczy tagów <frame>, <iframe>, <embed><applet>. Nie można go używać w tagach <meta> ani w przypadku zasobów HTML.
frame-src
Ta dyrektywa została wycofana na poziomie 2, ale przywrócona na poziomie 3. Jeśli go nie ma, przeglądarka przyjmuje wartość domyślną child-src.
img-src
Określa źródła, z których można wczytywać obrazy.
media-src
Ogranicza źródła, z których można przesyłać dźwięk i obraz.
object-src
Pozwala kontrolować Flasha i inne wtyczki.
plugin-types
Ogranicza typy wtyczek, które może wywoływać strona.
report-uri
Określa adres URL, na który przeglądarka wysyła raporty w przypadku naruszenia zasad bezpieczeństwa treści. Tej dyrektywy nie można używać w tagach <meta>.
style-src
Ogranicza źródła, z których strona może używać arkuszy stylów.
upgrade-insecure-requests
Instrukcje dla klientów użytkowników, aby ponownie zapisywać schematy adresów URL, zmieniając protokół HTTP na HTTPS. Ta dyrektywa jest przeznaczona do witryn z dużą liczbą starych adresów URL, które trzeba przekierować.
worker-src
Instrukcja CSP poziomu 3, która ogranicza adresy URL, które mogą być wczytywane jako worker, worker współdzielony lub worker usługi. Od lipca 2017 roku ta dyrektywa ma ograniczone wdrożenia.

Domyślnie przeglądarka wczytuje powiązany zasób z dowolnego punktu początkowego bez ograniczeń, chyba że skonfigurujesz zasadę z konkretną dyrektywą. Aby zastąpić ustawienie domyślne, użyj dyrektywy default-src. Ta dyrektywa określa domyślne wartości dla wszystkich nieokreślonych dyrektyw, które kończą się na -src. Jeśli na przykład ustawisz default-src na https://example.com i nie określisz dyrektywy font-src, możesz wczytywać czcionki tylko z https://example.com.

W tych dyrektywach nie jest używany znak default-src jako wartość domyślna. Pamiętaj, że nieustawienie tych wartości jest równoznaczne ze zezwoleniem na wszystko:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

Podstawowa składnia CSP

Aby używać dyrektyw CSP, umieść je w nagłówku HTTP, rozdzielając je dwukropkami. Pamiętaj, aby w jednym poleceniu podać wszystkie wymagane zasoby danego typu:

script-src https://host1.com https://host2.com

Poniżej znajduje się przykład wielu dyrektyw, w tym przypadku dla aplikacji internetowej, która wczytuje wszystkie zasoby z sieci dostarczania treści na adresie https://cdn.example.net i nie używa treści ani wtyczek w ramce:

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

Szczegóły implementacji

Nowoczesne przeglądarki obsługują nagłówek Content-Security-Policy bez prefiksu. Oto zalecany nagłówek. Nagłówki X-WebKit-CSPX-Content-Security-Policy, które możesz zobaczyć w samouczkach online, zostały wycofane.

CSP jest definiowany na podstawie poszczególnych stron. Musisz wysłać nagłówek HTTP z każdą odpowiedzią, którą chcesz chronić. Pozwala to dostosować zasady do konkretnych stron zgodnie z ich konkretnymi potrzebami. Jeśli np. jeden zestaw stron w Twojej witrynie zawiera przycisk +1, a inne nie, możesz zezwolić na wczytywanie kodu przycisku tylko wtedy, gdy jest to konieczne.

Lista źródeł w przypadku każdej dyrektywy jest elastyczna. Źródła możesz określić według schematu (data:, https:) lub z różnym zakresem szczegółowości: od nazwy hosta (example.com, która pasuje do dowolnego źródła na tym hoście: dowolny schemat, dowolny port) do pełnego identyfikatora URI (https://example.com:443, który pasuje tylko do HTTPS, tylko do example.com i tylko do portu 443). Symbole wieloznaczne są akceptowane, ale tylko jako schemat, port lub w najbardziej lewej pozycji nazwy hosta: *://*.example.com:* pasuje do wszystkich subdomen example.com (ale nie do samej example.com), używając dowolnego schematu na dowolnym porcie.

Lista źródeł obsługuje też 4 słowa kluczowe:

  • 'none' nie pasuje do niczego.
  • 'self' pasuje do bieżącego źródła, ale nie do jego subdomen.
  • 'unsafe-inline' umożliwia wstawianie kodu JavaScript i CSS. Więcej informacji znajdziesz w artykule Unikaj kodu w tekście.
  • 'unsafe-eval' umożliwia stosowanie mechanizmów konwersji tekstu na kod JavaScript, takich jak eval. Więcej informacji znajdziesz w artykule Unikaj eval().

Te słowa kluczowe wymagają cudzysłowów. Na przykład script-src 'self' (w cudzysłowie) autoryzuje wykonywanie kodu JavaScript z bieżącego hosta, a script-src self (bez cudzysłowu) zezwala na wykonywanie kodu JavaScript z serwera o nazwie „self” (a nie z bieżącego hosta), co prawdopodobnie nie jest tym, co masz na myśli.

Piaskownicy stron

Jest jeszcze jedna dyrektywa, o której warto wspomnieć: sandbox. Jest on nieco inny niż inne, które analizowaliśmy, ponieważ nakłada ograniczenia na działania, które może wykonać strona, a nie na zasoby, które może wczytać. Jeśli występuje dyrektywa sandbox, strona jest traktowana tak, jakby została załadowana w ramach elementu <iframe> z atrybutem sandbox. Może to mieć na nią różny wpływ: może na przykład wymusić unikalny element docelowy lub uniemożliwić przesyłanie formularzy. Jest to nieco wykraczające poza zakres tej strony, ale pełne informacje o obowiązujących atrybutach piaskownicy znajdziesz w sekcji „Piaskownia” specyfikacji HTML5.

Metatag

Preferowanym mechanizmem dostarczania przez dostawców usług internetowych jest nagłówek HTTP. Może jednak być przydatne ustawienie zasad na stronie bezpośrednio w sygnalizacji. Aby to zrobić, użyj tagu <meta> z atrybutem http-equiv:

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

Nie można go używać w przypadku kart frame-ancestors, report-urisandbox.

Unikaj kodu wbudowanego

Pomimo że listy dozwolonych domen używane w regułach CSP są bardzo skuteczne, nie mogą one rozwiązać największego zagrożenia związanego z atakami XSS, czyli wstrzykiwania skryptów w instrukcjach. Jeśli atakujący wstrzyknie tag skryptu, który zawiera bezpośrednio złośliwy ładunek (np. <script>sendMyDataToEvilDotCom()</script>), przeglądarka nie będzie mogła odróżnić go od prawidłowego tagu skryptu wstawionego w tekście. CSP rozwiązuje ten problem, całkowicie zakazując skryptów wbudowanych.

Zakaz obejmuje nie tylko skrypty osadzone bezpośrednio w tagach script, ale też wbudowane moduły obsługi zdarzeń i adresy URL javascript:. Musisz przenieść zawartość tagów script do pliku zewnętrznego i zastąpić adresy URL javascript: oraz <a ... onclick="[JAVASCRIPT]"> odpowiednimi wywołaniami addEventListener(). Możesz na przykład przepisać te informacje z:

<script>
   
function doAmazingThings() {
    alert
('YOU ARE AMAZING!');
   
}
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

na coś takiego:

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert
('YOU ARE AMAZING!');
}
document
.addEventListener('DOMContentLoaded', function () {
    document
.getElementById('amazing')
   
.addEventListener('click', doAmazingThings);
});

Napisany od nowa kod jest zgodny nie tylko z CSP, ale też ze wskazówkami dotyczącymi najlepszych praktyk w zakresie projektowania stron internetowych. Wbudowany JavaScript miesza strukturę i zachowanie w sposób, który powoduje, że kod staje się niejasny. Jest też trudniejsza do zcacheowania i skompilowania. Przeniesienie kodu do zasobów zewnętrznych poprawia działanie stron.

Zalecamy też przeniesienie tagów i atrybutów style do zewnętrznych arkuszy stylów, aby chronić witrynę przed atakami polegającymi na wydobyciu danych za pomocą języka CSS.

Jak tymczasowo zezwalać na skrypty i style w kodzie źródłowym

Aby włączyć skrypty i style w dokumencie, dodaj 'unsafe-inline' jako dozwolone źródło w dyrektywie script-src lub style-src. Poziom 2 CSP umożliwia też dodawanie określonych skryptów wbudowanych do listy dozwolonych za pomocą liczby jednorazowej (numeru użytego raz) lub hasha w ten sposób:

Aby użyć nonce, nadaj tagowi skryptu atrybut nonce. Jego wartość musi być zgodna z wartością na liście zaufanych źródeł. Na przykład:

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
   
// Some inline code I can't remove yet, but need to as soon as possible.
</script>

Dodaj wartość losową do dyrektywy script-src po słowie kluczowym nonce-:

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

W przypadku każdego żądania strony należy wygenerować nowe losowe ciągi znaków, które nie powinny być możliwe do odgadnięcia.

Hashe działają podobnie. Zamiast dodawać kod do tagu skryptu, utwórz skrót SHA skryptu i dodaj go do dyrektywy script-src. Jeśli na przykład Twoja strona zawierała ten tekst:

<script>alert('Hello, world.');</script>

Polityka musi zawierać te informacje:

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

Prefiks sha*- określa algorytm generujący hasz. W poprzednim przykładzie użyto atrybutu sha256-, ale CSP obsługuje też atrybuty sha384-sha512-. Podczas generowania hasha pomiń tagi <script>. Wielkość liter i odstępy mają znaczenie, w tym odstępy na początku i na końcu.

Rozwiązania do generowania haszy SHA są dostępne w dowolnej liczbie języków. W Chrome 40 lub nowszym możesz otworzyć Narzędzia deweloperskie, a następnie ponownie załadować stronę. Karta Konsola zawiera komunikaty o błędach z prawidłowym ciągiem znaków identyfikatora SHA-256 dla każdego skryptu wbudowanego.

Unikaj eval()

Nawet jeśli atakujący nie może wstrzyknąć skryptu bezpośrednio, może zmusić Twoją aplikację do przekształcenia tekstu wejściowego w wykonalny skrypt JavaScript i wykonanie go w swoim imieniu. eval(), new Function(), setTimeout([string], …) i setInterval([string], ...) to wektory, których atakujący mogą używać do uruchamiania złośliwego kodu za pomocą wstrzykniętego tekstu. Domyślną reakcją CSP na to ryzyko jest całkowite zablokowanie wszystkich tych wektorów.

Ma to następujące konsekwencje dla sposobu tworzenia aplikacji:

  • Musisz przeanalizować dane JSON za pomocą wbudowanej funkcji JSON.parse zamiast polegać na funkcji eval. Bezpieczne operacje na danych JSON są dostępne w każdej przeglądarce od IE8.
  • Musisz zastąpić wywołania funkcji setTimeout lub setInterval wywołaniami funkcji wbudowanych zamiast ciągów tekstowych. Jeśli na przykład Twoja strona zawiera te elementy:

    setTimeout("document.querySelector('a').style.display = 'none';", 10);

    Zmień to na:

    setTimeout(function () {
        document
    .querySelector('a').style.display = 'none';
    }, 10);
     
    ```
  • Unikaj szablonów wbudowanych w czasie wykonywania. Wiele bibliotek szablonów często używa new Function(), aby przyspieszyć generowanie szablonów w czasie wykonywania, co umożliwia ocenę złośliwego tekstu. Niektóre frameworki obsługują CSP, używając domyślnie niezawodnego parsowania, a w przeciwnym razie używając eval. Dyrektywa ng-csp w AngularJS jest tego dobrym przykładem. Zamiast tego zalecamy użycie języka szablonów, który oferuje wstępną kompilację, np. Handlebars. Wstępna kompilacja szablonów może zwiększyć szybkość działania witryny i ułatwić użytkownikom korzystanie z niej. Pozwoli też zwiększyć bezpieczeństwo witryny.

Jeśli funkcja eval() lub inne funkcje konwersji tekstu na JavaScript są niezbędne w Twojej aplikacji, możesz je włączyć, dodając 'unsafe-eval' jako dozwolone źródło w instrukcji script-src. Zdecydowanie odradzamy to ze względu na ryzyko związane z wstrzyknięciem kodu.

Zgłaszanie naruszeń zasad

Aby powiadomić serwer o błędach, które mogą umożliwiać wstrzyknięcie złośliwego kodu, możesz zlecić przeglądarce wysyłanie POST raportów o naruszeniu w formacie JSON do lokalizacji określonej w instrukcji report-uri:

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

Te raporty wyglądają tak:

{
   
"csp-report": {
   
"document-uri": "http://example.org/page.html",
   
"referrer": "http://evil.example.com/",
   
"blocked-uri": "http://evil.example.com/evil.js",
   
"violated-directive": "script-src 'self' https://apis.google.com",
   
"original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
   
}
}

Raport zawiera przydatne informacje, które ułatwiają znalezienie przyczyny naruszenia zasad, w tym stronę, na której wystąpiło naruszenie (document-uri), identyfikator tej strony (referrer), zasób, który naruszył zasady (blocked-uri), konkretną dyrektywę, którą naruszono (violated-directive), oraz pełne zasady (original-policy).

Tylko raporty

Jeśli dopiero zaczynasz korzystać z CSP, zalecamy użycie trybu tylko z raportami, aby ocenić stan aplikacji przed zmianą zasad. Aby to zrobić, zamiast wysyłać nagłówek Content-Security-Policy, wyślij nagłówek Content-Security-Policy-Report-Only:

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

Zasady określone w trybie tylko do raportowania nie blokują zasobów objętych ograniczeniami, ale wysyłają raporty o naruszeniu do określonej lokalizacji. Możesz nawet wysłać oba nagłówki, aby egzekwować jedną zasadę, a jednocześnie monitorować drugą. To świetny sposób na przetestowanie zmian w Twoim CSP podczas egzekwowania bieżących zasad: włącz raportowanie nowych zasad, monitoruj raporty o naruszeniu zasad i naprawiaj błędy, a gdy będziesz zadowolony/zadowolona z nowych zasad, zacznij je egzekwować.

Praktyczne zastosowanie

Pierwszym krokiem w kierunku stworzenia zasad dla aplikacji jest ocena zasobów, które wczytuje. Gdy poznasz strukturę aplikacji, utwórz zasady na podstawie jej wymagań. W następnych sekcjach omawiamy kilka typowych przypadków użycia oraz proces podejmowania decyzji o ich obsłudze zgodnie z wytycznymi CSP.

Widgety mediów społecznościowych

  • Przycisk polubienia na Facebooku można wdrożyć na kilka sposobów. Zalecamy korzystanie z wersji <iframe>, aby oddzielić ją od reszty witryny. Do prawidłowego działania potrzebuje dyrektywy child-src https://facebook.com.
  • Przycisk Tweeta aplikacji X wymaga dostępu do skryptu. Przenieś skrypt do zewnętrznego pliku JavaScript i użyj dyrektywy script-src https://platform.twitter.com; child-src https://platform.twitter.com.
  • Inne platformy mają podobne wymagania i można je rozwiązać w podobny sposób. Aby przetestować te zasoby, zalecamy ustawienie default-src na 'none' i obserwowanie konsoli, aby określić, które zasoby należy włączyć.

Aby użyć wielu widżetów, połącz dyrektywy w ten sposób:

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

Blokada

W przypadku niektórych witryn musisz mieć pewność, że można wczytywać tylko zasoby lokalne. W tym przykładzie tworzymy zasadę CSP dla witryny bankowej, zaczynając od domyślnej zasady, która blokuje wszystko (default-src 'none').

Witryna wczytuje wszystkie obrazy, styl i skrypt z CDN na stronie https://cdn.mybank.net, a do połączenia z witryną https://api.mybank.com/ używa XHR do pobierania danych. Używa ramek, ale tylko na stronach dostępnych lokalnie (bez stron zewnętrznych). W witrynie nie ma Flasha, czcionek ani innych dodatków. Najbardziej restrykcyjny nagłówek CSP, który może wysłać, to:

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

Tylko SSL

Poniżej znajduje się przykładowy CSP dla administratora forum, który chce mieć pewność, że wszystkie zasoby na forum są wczytywane tylko za pomocą bezpiecznych kanałów, ale nie ma doświadczenia w kodowaniu i nie ma zasobów, aby przepisać oprogramowanie forum firmy zewnętrznej pełne skryptów i stylów w ciele dokumentu:

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

Chociaż w sekcji default-src występuje zmienna https:, skrypt i style nie dziedziczą automatycznie tego źródła. Każda dyrektywa zastępuje domyślne ustawienie danego typu zasobu.

Rozwój standardu CSP

Polityka bezpieczeństwa treści na poziomie 2 to zalecany standard W3C. Grupa robocza ds. bezpieczeństwa aplikacji internetowych W3C opracowuje kolejną wersję specyfikacji, czyli zasady bezpieczeństwa treści na poziomie 3.

Aby śledzić dyskusję na temat tych nadchodzących funkcji, zapoznaj się z archiwami public-webappsec@ mailing list.