Polityka bezpieczeństwa treści

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

Joe Medley
Joe Medley

Obsługa przeglądarek

  • 25
  • 14
  • 23
  • 7

Źródło

Model zabezpieczeń internetu bazuje na zasadach dotyczących tego samego pochodzenia. Na przykład kod z https://mybank.com musi mieć dostęp tylko do danych usługi https://mybank.com, a https://evil.example.com nie może mieć dostępu do danych. Każde źródło jest teoretycznie odizolowane od reszty internetu, dając deweloperom bezpieczną piaskownicę. W praktyce hakerzy znaleźli jednak kilka sposobów na obadzenie systemu.

Na przykład ataki typu cross-site scripting (XSS) polegają na obejściu zasad dotyczących tego samego pochodzenia, nakłaniając witrynę do dostarczenia złośliwego kodu wraz z zamierzonymi treściami. To duży problem, ponieważ przeglądarki traktują cały kod wyświetlany na stronie jako integralny element zabezpieczeń strony. Ściągawka XSS zawiera stare, ale reprezentatywne zestawienie metod, których atakujący może używać do naruszenia tego zaufania przez wstrzyknięcie złośliwego kodu. Jeśli haker wstrzykuje dowolny kod, oznacza to, że haker przejął sesję użytkownika i uzyskał dostęp do informacji prywatnych.

Na tej stronie przedstawiamy standard Content Security Policy (CSP) jako strategię ograniczającą ryzyko i wpływ ataków XSS w nowoczesnych przeglądarkach.

Komponenty CSP

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

  • Dzięki listom dozwolonych możesz poinformować klienta, co jest dozwolone, a co nie.
  • Dowiedz się, jakie dyrektywy są dostępne.
  • Dowiedz się, jakie słowa kluczowe pobierają.
  • Ogranicz używanie kodu w tekście i elementu eval().
  • Zanim wyegzekwujesz naruszenia zasad, zgłaszaj je na swój serwer.

Listy dozwolonych źródeł

Ataki typu XSS wykorzystują niemożność przeglądarki do odróżnienia skryptu stanowiącego część aplikacji od skryptu, który został złośliwie wstrzykiwany przez inną firmę. Na przykład przycisk Google +1 u dołu tej strony wczytuje się i wykonuje kod z adresu https://apis.google.com/js/plusone.js w kontekście pochodzenia tej strony. Ufamy temu kodowi, ale nie możemy oczekiwać, że przeglądarka samodzielnie dowie się, że kod z apis.google.com można bezpiecznie uruchomić, podczas gdy kod z apis.evil.example.com prawdopodobnie już nie. Przeglądarka z przyjemnością pobiera i uruchamia każdy kod, którego żąda strona, niezależnie od źródła.

Nagłówek HTTP CSP Content-Security-Policy 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 osoba przeprowadzająca atak znajdzie lukę, przez którą można wstawić skrypt, nie będzie on pasować do listy dozwolonych i w związku z tym nie zostanie wykonany.

Wierzymy, że apis.google.com dostarczy prawidłowy kod i wierzymy, że zrobimy to samo. Oto przykładowa zasada, która pozwala 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 kontrolująca zestaw uprawnień związanych ze skryptem na stronie. Nagłówek 'self' jako jedno prawidłowe źródło skryptu, a https://apis.google.com jako drugie. Przeglądarka może teraz pobierać i wykonywać JavaScript ze strony apis.google.com przez HTTPS oraz z pierwotnego źródła strony, ale nie z innego źródła. Jeśli osoba przeprowadzająca atak wstrzykuje kod w witrynie, przeglądarka zgłosi błąd i nie uruchomi wstrzykniętego skryptu.

Błąd konsoli: odmówiono wczytania skryptu „http://evil.example.com/evil.js”, ponieważ narusza on następującą dyrektywę Content Security Policy: script-src 'self' https://apis.google.com
Jeśli skrypt próbuje uruchomić skrypt ze źródła spoza listy dozwolonych, konsola wyświetli błąd.

Zasada dotyczy wielu różnych zasobów

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

Na liście poniżej znajdziesz pozostałe dyrektywy dotyczące zasobów dostępne na poziomie 2. Przygotowano specyfikację poziomu 3, ale w głównych przeglądarkach w dużej mierze nie została zaimplementowana.

base-uri
Ogranicza adresy URL, które mogą się pojawiać w elemencie <base> strony.
child-src
Zawiera listę adresów URL instancji roboczych i zawartości umieszczonych 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żna się łączyć za pomocą XHR, WebSockets i EventSource.
font-src
Określa źródła, z których mogą być wyświetlane czcionki internetowe. Możesz na przykład zezwolić na używanie czcionek internetowych Google za pomocą elementu font-src https://themes.googleusercontent.com.
form-action
Zawiera listę prawidłowych punktów końcowych do przesłania z tagów <form>.
frame-ancestors
Określa źródła, w których można umieścić bieżącą stronę. Ta dyrektywa dotyczy 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 jest przywrócona na poziomie 3. Jeśli go nie ma, przeglądarka przełącza się z powrotem na child-src.
img-src
Określa źródło, z którego można ładować obrazy.
media-src
Ogranicza źródła, z których można przesyłać treści wideo i audio.
object-src
Daje kontrolę nad Flashem i innymi wtyczkami.
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 zasady 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 korzystać z arkuszy stylów.
upgrade-insecure-requests
Instruuje klienty użytkownika, aby przepisywanie schematów adresów URL odbywało się przez zmianę protokołu HTTP na HTTPS. Ta dyrektywa jest przeznaczona dla witryn z dużą liczbą starych adresów URL, które trzeba przepisać.
worker-src
Dyrektywa CSP poziomu 3 ograniczająca adresy URL, które mogą być ładowane jako instancja robocza, instancja robocza lub skrypt service worker. Od lipca 2017 r. implementacja tej dyrektywy jest ograniczona.

Domyślnie przeglądarka wczytuje powiązany zasób z dowolnego źródła bez żadnych ograniczeń, chyba że ustawisz zasadę z konkretną dyrektywą. Aby zastąpić wartość domyślną, użyj dyrektywy default-src. Ta dyrektywa określa wartości domyślne każdej nieokreślonej dyrektywy kończącej się na -src. Jeśli np. ustawisz default-src na https://example.com i nie określisz dyrektywy font-src, będzie można ładować tylko czcionki z https://example.com.

Te dyrektywy nie używają default-src jako kreacji zastępczej. Pamiętaj, że ich nieskonfigurowanie jest równoznaczne z zezwoleniem na cokolwiek:

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

Podstawowa składnia CSP

Aby używać dyrektyw CSP, wymień je w nagłówku HTTP z dyrektywami rozdzielonymi dwukropkiem. Pamiętaj, aby w jednej dyrektywie wymienić wszystkie wymagane zasoby określonego typu w ten sposób:

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

Poniżej znajdziesz przykład wielu dyrektyw aplikacji internetowej, która wczytuje wszystkie zasoby z sieci dostarczania treści w https://cdn.example.net i nie korzysta z treści i wtyczek w ramkach:

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. To jest zalecany nagłówek. Nagłówki X-WebKit-CSP i X-Content-Security-Policy, które możesz widzieć w samouczkach online, zostały wycofane.

Standard CSP jest definiowany dla każdej strony osobno. Musisz wysyłać nagłówek HTTP z każdą odpowiedzią, którą chcesz chronić. Dzięki temu możesz dostosować zasady dotyczące konkretnych stron do ich potrzeb. Jeśli na przykład jeden zestaw stron w Twojej witrynie ma przycisk +1, a inne nie, możesz zezwolić na wczytywanie kodu przycisku tylko wtedy, gdy jest to konieczne.

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

Lista źródłowa może zawierać także 4 słowa kluczowe:

  • Wyrażenie 'none' nie odpowiada niczemu.
  • 'self' pasuje do bieżącego źródła, ale nie do jego subdomen.
  • 'unsafe-inline' umożliwia wbudowany kod JavaScript i CSS. Więcej informacji znajdziesz w artykule o unikaniu kodu wbudowanego.
  • 'unsafe-eval' umożliwia korzystanie z mechanizmów zamiany tekstu na JavaScript, takich jak eval. Więcej informacji znajdziesz w artykule o unikaniu eval().

Te słowa kluczowe wymagają cudzysłowów. Na przykład script-src 'self' (z cudzysłowem) zezwala na wykonywanie kodu JavaScript z bieżącego hosta; script-src self (bez cudzysłowu) zezwala na wykonywanie kodu JavaScript z serwera o nazwie „self” (i nie od obecnego hosta), co prawdopodobnie nie o to Ci chodzi.

Umieść strony w piaskownicy

Warto wspomnieć o jeszcze jednej dyrektywie: sandbox. Różni się to nieco od innych, które już omówiliśmy, ponieważ ogranicza działania, które może wykonać strona, a nie zasoby, które może ona wczytać. Jeśli występuje dyrektywa sandbox, strona jest traktowana tak, jakby została wczytana wewnątrz elementu <iframe> z atrybutem sandbox. Może to mieć szeroki wpływ na stronę: wymuszać na stronie umieszczenie unikalnego źródła strony lub uniemożliwiać przesłanie formularza. Ta strona wykracza poza zakres tej strony, ale szczegółowe informacje na temat prawidłowych atrybutów piaskownicy znajdziesz w sekcji „Piaskownica” w specyfikacji HTML5.

Metatag

Mechanizm przesyłania preferowanego przez CSP to nagłówek HTTP. Czasami warto jednak ustawić zasadę na stronie bezpośrednio w znacznikach. Użyj do tego 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'">

Tej opcji nie można użyć w przypadku zasad frame-ancestors, report-uri ani sandbox.

Unikaj kodu wbudowanego

Choć listy dozwolonych oparte na źródle używane w dyrektywach CSP są bardzo skuteczne, nie są w stanie wyeliminować największego zagrożenia, jakie stwarzają ataki XSS, czyli wstrzykiwanie skryptów. Jeśli osoba przeprowadzająca atak może wstrzyknąć tag skryptu, który zawiera bezpośrednio złośliwe ładunki (np. <script>sendMyDataToEvilDotCom()</script>), przeglądarka nie będzie w stanie odróżnić go od prawidłowego wbudowanego tagu skryptu. CSP rozwiązuje ten problem, całkowicie blokując wbudowany skrypt.

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

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

na przykład:

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

Przepisany kod jest nie tylko zgodny z CSP, ale także zgodny ze sprawdzonymi metodami projektowania stron internetowych. Wbudowany JavaScript łączy strukturę i działanie w sposób, który wprowadza w błąd. Jest to również bardziej skomplikowane. Przeniesienie kodu do zewnętrznych zasobów poprawia wydajność stron.

Zdecydowanie zalecamy też przeniesienie wbudowanych tagów i atrybutów style do zewnętrznych arkuszy stylów, aby chronić witrynę przed atakami związanymi z wydobyciem danych opartymi na CSS.

Jak tymczasowo zezwolić na wbudowane skrypty i style

Aby włączyć wbudowane skrypty i style, dodaj 'unsafe-inline' jako dopuszczalne źródło w dyrektywie script-src lub style-src. CSP na poziomie 2 pozwala też dodawać określone skrypty wbudowane do listy dozwolonych, używając kryptograficznej liczby jednorazowej (liczby użytej raz) lub haszowania zgodnie z następującymi ciągami.

Aby użyć liczby jednorazowej, nadaj tagowi skryptu atrybut liczba jednorazowa. Wartość musi być taka sama 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 liczbę jednorazową do dyrektywy script-src po słowie kluczowym nonce-:

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

W przypadku każdego żądania strony trzeba je ponownie wygenerować. Muszą one być też trudne do odgadnięcia.

Hasze działają w podobny sposób. 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 strona zawiera te treści:

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

Zasady muszą 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 parametru sha256-, ale CSP obsługuje też elementy sha384- i sha512-. Generując hasz, pomiń tagi <script>. Wielkość liter i odstępy, w tym odstępy na początku i na końcu,

Rozwiązania do generowania haszów SHA są dostępne w dowolnej liczbie języków. W Chrome 40 lub nowszej możesz otworzyć Narzędzia deweloperskie i załadować ponownie stronę. Karta Console (Konsola) wyświetla komunikaty o błędach z prawidłowym haszem SHA-256 dla każdego z wbudowanych skryptów.

Unikaj: eval()

Nawet jeśli osoba przeprowadzająca atak nie może bezpośrednio wprowadzić skryptu, może podstępem nakłonić aplikację do konwersji tekstu wejściowego na wykonywalny kod JavaScript i wykonać go w jego imieniu. eval(), new Function(), setTimeout([string], …) i setInterval([string], ...) to wektory, których mogą używać hakerzy do wykonywania złośliwego kodu za pomocą wstrzykiwanego 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ć kod JSON za pomocą wbudowanego komponentu JSON.parse, zamiast polegać na eval. Bezpieczne operacje JSON są dostępne w każdej przeglądarce od IE8.
  • Musisz przepisać wszystkie wywołania setTimeout i setInterval wykonywane za pomocą funkcji wbudowanych zamiast ciągów tekstowych. Jeśli na przykład strona zawiera:

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

    Przepisz go jako:

    setTimeout(function () {
        document.querySelector('a').style.display = 'none';
    }, 10);
      ```
    
  • Unikaj tworzenia szablonów w treści w czasie działania. Wiele bibliotek szablonów często używa new Function(), aby przyspieszyć generowanie szablonów w czasie działania, co umożliwia ocenę złośliwego tekstu. Niektóre platformy od razu obsługują CSP. W przypadku braku eval następuje powrót do solidnego parsera. Dobrym przykładem jest dyrektywa ng-csp w AngularJS. Zamiast tego zalecamy jednak używanie języka szablonów, który oferuje wstępną kompilację, np. kierowniki. Wstępne skompilowanie szablonów może sprawić, że korzystanie z aplikacji będzie jeszcze szybsze niż w przypadku najkrótszej implementacji w środowisku wykonawczym, a także zwiększyć bezpieczeństwo witryny.

Jeśli eval() lub inne funkcje oparte na tekście na JavaScript są niezbędne w Twojej aplikacji, możesz je włączyć, dodając 'unsafe-eval' jako dopuszczalne źródło w dyrektywie script-src. Zdecydowanie odradzamy takie rozwiązanie ze względu na ryzyko wstrzyknięcia kodu.

Zgłaszanie naruszeń zasad

Aby powiadomić serwer o błędach, które mogą umożliwić wstrzykiwanie złośliwego oprogramowania, możesz poprosić przeglądarkę o POST zgłoszeń o naruszeniach w formacie JSON w lokalizacji określonej w dyrektywie report-uri:

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

Raporty te 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 ułatwiające ustalenie przyczyny naruszenia zasad, w tym informacje o stronie, na której doszło do naruszenia (document-uri), stronie referrer, zasobie, który narusza zasady strony (blocked-uri), konkretnej dyrektywie, którą naruszyła (violated-directive), i pełnej zasadzie strony (original-policy).

Tylko raportowanie

Jeśli dopiero zaczynasz korzystać z CSP, zalecamy użycie trybu „tylko raporty” do oceny stanu aplikacji przed zmianą zasady. 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;

Zasada określona w trybie „tylko zgłaszanie” nie blokuje zasobów z ograniczeniami, ale wysyła zgłoszenia naruszeń do wskazanej przez Ciebie lokalizacji. Możesz nawet wysłać oba nagłówki, aby wymusić stosowanie jednej zasady, a jednocześnie monitorować inną. To świetny sposób na przetestowanie zmian w CSP przy jednoczesnym egzekwowaniu bieżącej zasady: włącz zgłaszanie nowej zasady, monitoruj zgłoszenia o naruszeniach i naprawiaj błędy, a gdy nowa zasada spełnia Twoje oczekiwania, zacznij ją egzekwować.

Rzeczywiste wykorzystanie

Pierwszym krokiem do opracowania zasady dla aplikacji jest ocena wczytywanych zasobów. Gdy poznasz już strukturę aplikacji, utwórz zasadę na podstawie jej wymagań. W sekcjach poniżej omawiamy kilka typowych przypadków użycia oraz proces podejmowania decyzji w zakresie tych przypadków zgodnie ze wskazówkami dotyczącymi CSP.

Widżety mediów społecznościowych

  • Przycisk „Podoba mi się” na Facebooku ma kilka opcji implementacji. Zalecamy użycie wersji <iframe>, aby odizolować ją od reszty witryny. Do prawidłowego działania wymaga dyrektywy child-src https://facebook.com.
  • Przycisk tweetowania użytkownika X wymaga dostępu do skryptu. Przenieś udostępniony przez niego 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 się nimi zająć w podobny sposób. Aby przetestować te zasoby, zalecamy ustawienie wartości default-src na 'none' i sprawdzenie konsoli, aby określić, które zasoby należy włączyć.

Aby używać wielu widżetów, połącz instrukcje w następujący 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

Lockdown

W przypadku niektórych witryn chcesz ładować tylko zasoby lokalne. Z poniższego przykładu dowiesz się, jak utworzyć CSP dla witryny banku. Zaczynamy od domyślnej zasady, która blokuje wszystko (default-src 'none').

Witryna wczytuje wszystkie obrazy, style i skrypty z sieci CDN na stronie https://cdn.mybank.net oraz łączy się z usługą https://api.mybank.com/ za pomocą XHR. Zawiera ramki, ale tylko w przypadku stron lokalnych w witrynie (bez źródeł zewnętrznych). W witrynie nie ma Flasha, czcionek ani dodatków. Najbardziej restrykcyjny nagłówek CSP, który można 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 jego forum są ładowane tylko przez bezpieczne kanały, ale nie ma doświadczenia w kodowaniu i nie ma zasobów do redagowania oprogramowania forów innych firm pełnych wbudowanych skryptów i stylów:

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

Chociaż w polu default-src jest określony https:, dyrektywy skryptu i dyrektywy stylu nie dziedziczą tego źródła automatycznie. Każda dyrektywa zastępuje wartość domyślną dla określonego typu zasobu.

Programowanie standardu CSP

Content Security Policy Level 2 to zalecany standard W3C. Grupa robocza ds. zabezpieczeń aplikacji internetowych firmy W3C opracowuje kolejną iterację specyfikacji – Content Security Policy Level 3.

Dalszą dyskusję na temat nadchodzących funkcji znajdziesz w archiwach listy adresowej public-webappsec@.