Content Security Policy może znacznie zmniejszyć ryzyko i wpływ ataków typu cross-site scripting na potrzeby współczesnych przeglądarek.
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](https://web.dev/static/articles/csp/image/console-error-refused-l-fc9682b801661.png?authuser=3&hl=pl)
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 jakeval
. Więcej informacji znajdziesz w artykule o unikaniueval()
.
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ć naeval
. Bezpieczne operacje JSON są dostępne w każdej przeglądarce od IE8. Musisz przepisać wszystkie wywołania
setTimeout
isetInterval
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 brakueval
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 dyrektywychild-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@.