Polityka bezpieczeństwa treści

Content Security Policy może znacznie zmniejszyć ryzyko i wpływ ataków typu cross-site scripting na witryny we współczesnych przeglądarkach.

Joe Medley
Joe Medley

Obsługa przeglądarek

  • Chrome: 25.
  • Krawędź: 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 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 uzyskanie dostępu do informacji prywatnych.

Ta strona przedstawia standard Content Security Policy (CSP) jako strategię zmniejszania ryzyka i wpływu ataków typu XSS w nowoczesnych przeglądarkach.

Komponenty CSP

Aby wdrożyć skuteczny 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.
  • dowiedzieć się, jakie słowa kluczowe są przez nie używane;
  • Ogranicz używanie kodu wbudowanego i 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 uruchomienia, 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 dziurę umożliwiającą wstrzykiwanie skryptu, skrypt nie pasuje do listy dozwolonych i dlatego 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 uruchamiać kod JavaScript z witryny apis.google.com przez HTTPS, a także z początku bieżącej strony, ale nie z innego źródła. Jeśli osoba przeprowadzająca atak wstrzykuje kod w Twojej witrynie, przeglądarka zgłosi 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 zawiera zestaw dyrektyw zasad zapewniających szczegółową kontrolę nad zasobami, które może wczytywać strona. Obejmuje to m.in. zasadę 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ą występować 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ę połą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 odnosi się do tagów <frame>, <iframe>, <embed> i <applet>. Nie można go używać w tagach <meta> ani w zasobach 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 mogą być dostarczane treści wideo i audio.
object-src
Pozwala kontrolować Flasha i inne wtyczki.
plugin-types
Ogranicza rodzaje wtyczek, jakie 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 HTTP na HTTPS. Ta dyrektywa jest przeznaczona do witryn z dużą liczbą starych adresów URL, które trzeba przerobić.
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 r. ta dyrektywa ma ograniczoną liczbę implementacji.

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ć domyślną, określ dyrektywę default-src. Ta dyrektywa określa domyślne wartości dla wszystkich nieokreślonych dyrektyw, które kończą się na -src. Jeśli np. ustawisz default-src na https://example.com i nie określisz dyrektywy font-src, możesz wczytywać czcionki tylko z tego źródła: 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 przedrostka. 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 określany dla każdej strony z osobna. Musisz wysył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. jedna grupa 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ślać według schematu (data:, https:) lub wybierając poziom szczegółowości – tylko nazwa hosta (example.com, który pasuje do dowolnego źródła na tym hoście: dowolny schemat, dowolny port) do w pełni kwalifikowanego identyfikatora URI (https://example.com:443, który pasuje tylko do HTTPS, tylko example.com i tylko port 443). Symbole wieloznaczne są akceptowane, ale tylko jako schemat, port lub w najbardziej lewej pozycji nazwy hosta: *://*.example.com:* będzie pasować do wszystkich subdomen example.com (ale nie do samej example.com), używając dowolnego schematu i dowolnego portu.

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

  • 'none' nic nie odpowiada.
  • '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 o unikaniu kodu wbudowanego.
  • 'unsafe-eval' zezwala na mechanizmy zamiany tekstu na JavaScript, takie jak eval. Więcej informacji znajdziesz w artykule Unikaj eval().

Te słowa kluczowe wymagają cudzysłowu. 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 dyrektywa sandbox jest obecna, strona jest traktowana tak, jakby została wczytana wewnątrz tagu <iframe> z atrybutem sandbox. Może to mieć na nią różny wpływ: może na przykład wymusić unikalny element źródła lub uniemożliwić przesyłanie formularzy. Jest to nieco wykraczające poza zakres tej strony, ale szczegółowe 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 kod źródłowy. 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 kod HTML. CSP rozwiązuje ten problem, całkowicie zakazując skryptów wbudowanych.

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

<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);
});

Zastąpiony 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ę nieczytelny. Jest też trudniejsza do zcacheowania i skompilowania. Przeniesienie kodu do zasobów zewnętrznych poprawia działanie stron.

Zdecydowanie zalecamy też przeniesienie wbudowanych tagów i atrybutów style do zewnętrznych arkuszy stylów, by chronić witrynę przed atakami polegającymi na wydobyciu danych opartymi na 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. CSP poziomu 2 umożliwia też dodawanie określonych skryptów w tekście do listy dozwolonych przy użyciu kryptograficznej liczby jednorazowej (liczby używanej raz) lub haszowania w następujący 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 losowy ciąg znaków 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 samego 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, który generuje 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 kod 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ący wpływ na sposób tworzenia aplikacji:

  • Musisz przeanalizować dane JSON za pomocą wbudowanej funkcji JSON.parse zamiast polegać na funkcji eval. Bezpieczne operacje JSON są dostępne w każdej przeglądarce od IE8.
  • Musisz zastąpić wszystkie wywołania funkcji setTimeout lub setInterval za pomocą 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. Dobrym przykładem jest dyrektywa ng-csp narzędzia AngularJS. Zamiast tego zalecamy użycie języka szablonów, który oferuje wstępną kompilację, np. Handlebars. Wstępne kompilowanie szablonów może zwiększyć wygodę użytkowników nawet szybciej niż w przypadku najkrótszej implementacji środowiska wykonawczego, a także 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 sprawie stworzenia zasad dla aplikacji jest ocena zasobów, które wczytuje. Gdy poznasz strukturę aplikacji, utwórz zasady na podstawie jej wymagań. W sekcjach poniżej omawiamy kilka typowych przypadków użycia i procesy decyzyjne, które mają zapewnić zgodność z wytycznymi CSP.

Widgety mediów społecznościowych

  • Przycisk Podoba mi się na Facebooku ma kilka opcji implementacji. Zalecamy korzystanie z wersji <iframe>, aby oddzielić ją od reszty witryny. Do prawidłowego działania wymaga ona 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 o wartości 'none' i obserwowanie zasobów w konsoli, aby określić, które zasoby musisz 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 pod adresem https://cdn.mybank.net, a do danych pobieranych za pomocą XHR łączy się z adresem https://api.mybank.com/. Używa ramek, ale tylko na stronach docelowych w witrynie (nie ma źródeł 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 polu default-src określono https:, skrypt i dyrektywy dotyczące stylu nie dziedziczą automatycznie tego źródła. Każda dyrektywa zastępuje domyślne ustawienie danego typu zasobu.

Rozwój standardu CSP

Content Security Policy Level 2 to zalecany standard W3C. Grupa robocza W3C ds. bezpieczeństwa aplikacji internetowych 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.