Tento článek patří do seriálu Security headers. Ostatní články seriálu:
- Security headers - Co jsou a jak na ně
- HTTPS nestačí, jak na HSTS a HPKP
- Bezpečnost a expirace sessions v PHP
- Content Security Policy
- 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'
object-src 'none'
kompletně zakáže Flash objekty abase-uri 'none'
zakáže<base>
tag'strict-dynamic'
- Povolí spouštět jen a pouze skripty s nonce a také všechny další, které byly těmito skripty přidány pomocídocument.createElement('script')
apod. (CSPv3 a výše)'nonce-[BASE-64-random-string]'
- Pokud hlavička obsahuje nonce, skript se stejným nonce může být spuštěn. Verze CSP 2 a výšehttps:
- Dovoluje spouštět skripty načtené pomocí HTTPS protokolu. Pokud je použit strict-dynamic je tento záznam úplně ignorován. Lze případně přidat ještě navíchttp:
'unsafe-inline'
- Dovoluje spouštět nezabezpečené skripty, tedy úplně cokoli včetně onclick inline handlerů apod. Je ignorován, pokud je použito nonce.
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