Content Security Policy

JavaScript, Bezpečnost, CSS

Content security policy je hlavička, jejíž aplikací je možné zakázat prohlížeči stahovat či spouštět soubory, skripty z jiných než dovolených adres a tím zvýšit celkovou bezpečnost webu.

Content Security Policy

Tento článek patří do seriálu Security headers. Ostatní články seriálu:

  1. Security headers - Co jsou a jak na ně
  2. HTTPS nestačí, jak na HSTS a HPKP
  3. Bezpečnost a expirace sessions v PHP
  4. Content Security Policy
  5. Report URI - Správce reportů z prohlížeče

Další bezpečnostní vrstvou pro lepší zabezpečení webu je Content Security Policy, zkráceně CSP. Jedná se o techniku, kdy se pomocí HTTP hlavičky omezí zdroje, které prohlížeč může stahovat. Při dobrém nastavení tak může velmi dobře posloužit jako ochrana před XSS

Jak na implementaci

Podobně jako pro hlavičky z předchozího článku, i tady by bylo ideální nastavit hlavičky přímo v .htaccesu či v nastavení serveru. Osobně ale používám knihovnu Secure Headers, která obsahuje integraci do Laravelu, i když ji lze použít i samostatně.

Reporty a Report-Only mode

Pravidla se zapisují do HTTP hlavičky Content-Security-Policy a po její aplikaci bude vše, co porušuje pravidla zakázáno. Proto existuje i Content-Security-Policy-Report-Only, která nic nezakáže, vše ale reportuje. To se může hodit především při nasazování na již zaběhnutý web. Prvně se pouze budou sbírat údaje o porušení pravidel a jejich náprava. Až poté se přepne z Report-Only módu na plný a striktní mód.

Hlavičky mohou obsahovat parametry report-to a report-uri. Report-uri obsahuje přímo URL adresu, kam reporty o porušení posílat, report-to obsahuje pouze název skupiny ze samostatné Report-To hlavičky. O tom více v dalším díle o službě Report-URI.

Parametr report-uri je označen jako deprecated a měl by se nahradit hlavičkou Report-To a report-to parametrem. Více o zpětně kompatibilním zápisu na developer.mozilla.org

Minimálně doporučovaný zpětně kompatibilní zápis hlavičky

Minimálně by hlavička měla vypadat jak je znázorněno níže. Je dosti striktní a stále zpětně kompatibilní se staršími prohlížeči. Zalomení řádků a odsazení je zde pouze pro přehlednost. 

Content-Security-Policy:
script-src 'strict-dynamic' 'nonce-[BASE-64-random-string]' 'unsafe-inline' https:;
object-src 'none';
base-uri 'none'

Prohlížeč s CSPv1 se bude řídit pravidly https: a 'unsafe-inline'. Tedy jakoby žádná pravidla nebyla. Prohlížeč s CSPv2 dovolí vše s https anebo s nonce. Prohlížeč s CSPv3 už dovolí jen a pouze skripty s nonce a ty, které tyto povolené skripty přidaly. Detailněji včetně ukázek lze najít na webu od Michala Špačka, a interaktivní validátor na CSP Evaluator od Google.

Co dělat se zápisy v HTML

Pokud v HTTP hlavičce je obsažen nonce, musí být přidán i k tagům pro vložení CSS nebo JS souborů nebo inline zápisu. Pro následující zkrácenou hlavičku je potřeba upravit HTML viz níže. Osobně tedy nepoužívám nonce pro styly, převážně kvůli unsafe-inline. Hodně knihoven pro lightboxy nebo výstup z WYSIWYG editoru obsahuje inline zápisy.

Content-Security-Policy:
  script-src 'strict-dynamic' 'nonce-[ForScripts]' 'unsafe-inline' https:;
style-src 'nonce-[ForStyles]' 'self' 'unsafe-inline'
<link href="/css/styles.css"> <!-- Funguje díky 'self' -->

<!-- Vše níže funguje díky nonce -->
<link href="https://fonts.googleapis.com/css?family=Muli:300" nonce="[ForStyles]">
<style nonce="[ForStyles]"> body{ color: red; } </style>
<script src="https://browser.sentry-cdn.com/5.15.5/bundle.min.js" nonce="[ForScripts]"></script>
<script nonce="[ForScripts]">console.log('Working!!!');</script>

<!-- Nic níže nefunguje - unsafe-inline je ignorován, protože nonce je přítomno v hlavičce-->
<style> body{ color: red; } </style>
<div style="margin: 10px" onclick="console.log('This will not work');"></div>
<script>console.log('Not working either!');</script>

Jak funguje strict-dynamic a proč jej používat

Problém CSP ve verzi 2 a starší nastával v případě JavaScriptů, které sloužily jako loader. Například Maps API, Google Tag Manager apod. Na stránku se vložil jeden JS, který přidával další a další. Poté se stalo, že se načítaly JS skripty z URL, které nebyly povoleny. Řešení bylo buď náročné, whitelistovat URL a při každé změně znovu opravovat. Nebo povolit vše z HTTPS a tím pádem zabezpečení pokulhávalo, protože opět bylo povoleno vše.

Proto vznikl strict-dynamic. Pokud je na stránku vložen kód s nonce, tak je označen jako bezpečný. Všechny další skripty, které jsou tímto povoleným skriptem dále vloženy jsou automaticky považovány za povolené, i když nonce přímo neobsahují.

Co už nepůjde s CSP použít?

Pokud bude CSP nastaveno, jak je znázorněno výše, nelze již používat inline handlery. Tedy kód jako <a href="javascript:">, <img onerror="..."> či <a onclick="..."> se musí přepsat. Myslel jsem, že document.write("<script src='/script.js'></script>");, což také nelze použít, již nikdo nepoužívá. Opak je pravdou, používají jej v API Mapy.cz. Takže jsem na webu nahradil mapku za obyčejný obrázek a založil ticket, ke kterému se zatím nikdo ze Seznamu nevyjádřil.

Co dalšího CSP ještě umí?

Blokovat lze takto mnohem více než jen CSS, JavaScripty a objekty. Výhodným atributem je default-src, který tuto hodnotu nastaví pro úplně všechny parametry, které nejsou specifikovány. Celý seznam se mění a stačí se podívat do dokumentace od Mozilly. Osobně hlavička na tomto blogu v době psaní vypadá přibližně takto:

Content-Security-Policy:
default-src 'self'; # Ve výpisu například chybí form-action, díky default je ale nastaven na 'self'
base-uri 'none';
connect-src 'self' https://*.google-analytics.com https://*.sentry.io; # AJAXové volání jen na tyto adresy
font-src 'self' data: https://fonts.gstatic.com; # Fonty lze načítat pouze ze stejné domény, z data uri a od Google
frame-src *; # Iframe může být jakýkoli, možná by šlo omezit jen na self a YouTube
img-src 'self' data: https:; # Podobně jako u fontů, ale lze načíst z jakékoli HTTPS adresy
object-src 'none';
script-src 'self' 'unsafe-inline' 'strict-dynamic' https: 'nonce-[ForScripts]';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
report-uri https://report-uri.com/r/d/csp/enforce # Reportování porušení pravidel

Nejedná se vůbec o jednoduchou techniku, proto doporučím ještě pár dalších zdrojů. Například přednášky od Michala Špačka (starší bez CSPv3 a novější jen o CSPv3), Adopting CSP with Google nebo dokumentaci od Mozilly k CSP všeobecně a CSP HTTP hlavičce.


Vlastní zkušenosti s CSP nebo bezpečnostními hlavičkami můžete sdílet v komentářích. Obrázek přejat od starline.

K tomuto článku již není možné přidávat další komentáře