Pokud navštívíte webovou stránku, váš prohlížeč většinou načítá mimo stránky samotné i spoustu dalšího obsahu – externí obrázky, skripty, stránky vložené přes iframe apod. Příkladem mohou být populární tlačítka pro sdílení obsahu na sociálních sítích (Google +1, Facebook Like…), služba Google Analytics, diskuze pod články od třetích stran apod. Zde je vše v pořádku, protože pokud jste si tyto prvky dali na svůj web dobrovolně, pravděpodobně souhlasíte s jejich nahráváním do vašich stránek.
Problém ale může nastat, pokud například útočník vloží speciálně upravený komentář, který obejde vaši ochranu proti XSS a načte škodlivý javascript z externího zdroje. Váš prohlížeč nemůže tušit, že skript, který se snaží ukrást vaše cookies a je hostovaný na adrese http://utocnikuv-web.cz, je na rozdíl od skriptu hostovaného z https://apis.google.com škodlivý, stáhne ho, spustí a problém je na světě. Jediný, kdo ví, odkud se mají externí zdroje načítat, jste vy – administrátor stránky, a s tímto vám pomůže právě zmiňované CSP.
Whitelisting zdrojů
CSP funguje na tom principu, že pomocí speciální HTTP hlavičky Content-Security-Policy
určíte politiku, odkud se mohou které zdroje nahrávat. Ukažme si to rovnou na příkladu. Pokud by webová stránka spolu s odpovědí vrátila i toto:
Content-Security-Policy: script-src 'self' https://apis.google.com;
tak by nás výše uvedený problém se škodlivým skriptem nemohl ohrozit, protože prohlížeč vykoná skripty pouze z adresy https://apis.google.com a ze sebe samotné (položka 'self'
). Dalším příkladem může být:
Content-Security-Policy: font-src 'none'; img-src 'self' https://*.moje-cdn.cz;
Zde jsme si určili, že se do stránky nebudou odnikud nahrávat fonty a obrázky získáme opět pouze ze sebe sama a z jakékoliv subdomény stránky moje-cdn.cz přes HTTPS.
Seznam zdrojů
Pokud chcete definovat, odkud se mohou jednotlivé prvky nahrávat, máte několik možností. První z nich je přímé definování:
- https://root.cz – pouze ze stránky root.cz pomocí HTTPS
- https://*.root.cz – pouze z jakékoliv subdomény stránky root.cz pomocí HTTPS
- root.cz – ze stránky root.cz pomocí jakéhokoliv protokolu
- *://root.cz:81 – jakýkoliv protokol z domény root.cz a portu 81
- https: – odkudkoliv ale pomocí HTTPS
Operátor hvězdička (wildcard) můžete využít místo protokolu, nejlevější části domény nebo portu. Dále jsou místo přesných umístění specifikována i různá klíčová slova:
- * – nahrávat zdroje lze odkudkoliv
- ‚none‘ – zdroje nelze nahrávat odnikud
- ‚self‘ – pouze ze sebe sama (stejný protokol, doména i port)
- ‚unsafe-inline‘ – povolí inline zdroje jako jsou skripty a styly (bude probráno později)
- ‚unsafe-eval‘ – to samé jako inline ale pro funkci eval()
Seznam direktiv
Prozatím jsme se setkali s direktivami script-src
, img-src
a font-src
. Jejich kompletní seznam naleznete např. na stránkách Mozilla Developer Network. Existuje však jedna výjimka, a tou je direktiva default-src
, která slouží jako výchozí hodnota pro všechny ostatní direktivy, pokud nejsou specifikovány. Například zde:
Content-Security-Policy: default-src 'self'; script-src 'none'; img-src *; media-src https://youtube.com;
jsme nastavili výchozí politiku na 'self'
, což znamená, že například fonty (které zde nejsou specifikované) se mohou načítat pouze ze sebe sama. Dále jsme zakázali veškeré skripty, obrázky se mohou načítat odkudkoliv a média (audio/video) mohou být vkládány pouze z YouTube přes HTTPS.
Syntaxe
Syntaxe je vcelku jednoduchá. Po uvedení hlavičky Content-Security-Policy:
následuje vždy direktiva a libovolně dlouhý seznam zdrojů oddělený mezerami a zakončený středníkem. Pokud jednu direktivu uvedete vícekrát:
Content-Security-Policy: script-src root.cz; script-src google.com;
Tak bude ta druhá ignorována a tudíž nebude možné spouštět skripty z Google. Správná varianta je:
Content-Security-Policy: script-src root.cz google.com;
Pokud se vám stane, že nemáte přístup k webovému serveru a nemůžete tedy nastavit HTTP hlavičky, lze to samozřejmě řešit pomocí meta tagů:
<meta http-equiv="Content-Security-Policy" content="default-src https://moje-cdn.cz; child-src 'none'; object-src 'none'">
Inline skripty
Celý mechanismus CSP je tedy založen na tom, že autor stránek prohlížeči sděluje, odkud je možné externí zdroje načítat a zbytek se zahodí. To však neřeší největší problém u XSS útoků – vložení inline skriptu. Pokud se útočníkovi podaří vložit např.:
<script>window.location = "http://phising-web.cz/login.php";</script>
tak bude uživatel přesměrován na phisingový web, který může vypadat naprosto stejně jako předloha a žádat o přihlašovací údaje. Méně zkušený uživatel si toho nemusí všimnout a tím pádem přijde o identitu.
CSP toto řeší jednoduše a elegantně zároveň – veškeré inline skripty jsou zakázané. Nezbývá vám tedy než upravit webovou stránku do té podoby, aby vše bylo načítáno z externích souborů. Pokud se nejedná o rozsáhlý projekt, tak to nebývá až takový problém a víceméně je to „best practice“ – pro prohlížeč je to jednodušší na cachování, web je čitelnější pro ostatní vývojáře, kód je možné optimalizovat apod.
Pokud se bez inline skriptů z nějakého důvodu nemůžete obejít, je zde u direktiv script-src
a style-src
možnost nastavit jako zdroj 'unsafe-inline'
. Tímto povolíte inline skripty a přijdete tak o jednu z největších výhod CSP. Přeci jenom, inline skripty jsou vcelku časté a občas se bez nich obejít nedá. Neexistuje nějaký kompromis mezi úplným zákazem a rizikovým povolením všeho? Ano, existuje.
Nonce a hash
První možností je tzv. nonce. V tomto případě by CSP hlavička mohla vypadat takto:
Content-Security-Policy: script-src 'self' 'nonce-UQg5njV6ZWpOQR2PPpyzgQZxBGxgvjg'
Jakýkoliv skript na stránce, aby byl vykonán, musí obsahovat stejnou nonce (uvedenou jako parametr skript tagu):
<script nonce="UQg5njV6ZWpOQR2PPpyzgQZxBGxgvjg">alert('Funguje to!');</script>
Zde je samozřejmě nutné, dodržet několik věcí. Nonce musí být alespoň 128 bitů dat z kryptograficky bezpečného náhodného generátoru zakódovaných do base64. Druhým požadavkem je, aby tato data byla znovu náhodně vygenerována při každém dalším HTTP požadavku. Pokud by byla stejná (nebo lehce predikovatelná), útočníkovi nic nebrání v tom, injektovat vlastní skript se správnou nonce.
Druhou možností je využít hashovacích funkcí. Místo nonce se do CSP hlavičky vloží hash skriptu (bez <script>
tagů), opět zakódovaný do base64. Pokud tedy budete chtít mít na své stránce tento skript:
<script>alert('root.cz');</script>
je nutné vytvořit CSP hlavičku:
Content-Security-Policy: script-src 'sha256-TTiSwfvRfpc8u02u9GS5utv4/np6iGFyU/Xmhg6fK0k'
CSP podporuje i sha384 a sha512, stačí pouze změnit prefix. Jaká z nabízených metod je lepší? To si musí rozhodnout každý sám. Obecně se dá říct, že nonce je lepší pro dynamicky generované weby a hash pro statické.
Testování a reporting
Pokud CSP čerstvě implementujete, určitě vás bude zajímat, jaké všechny prvky byly zablokovány. Uvažujme následující politiku:
Content-Security-Policy: default-src 'none'; script-src 'self'; img-src https://moje-cdn.cz;
na webové stránce, kde je pro demonstrativní příklad vložen inline javascript, externí javascript tlačítka Google +1 a logo stránky root.cz, taktéž z externího zdroje. Poté lze jednotlivé zablokované prvky vidět v konzoli prohlížeče:
Do konzole se však můžete dívat pouze v případě, že se jedná o váš prohlížeč. Určitě by bylo v některých případech vhodné vědět, že prohlížeč návštěvníka nějaký prvek zablokoval. Může se jednat například o zapomenutý skript někde v zákoutí produkčního serveru, případně o úspěšné obejití vaší XSS ochrany a vložení škodlivého inline skriptu útočníkem. V tomto případě můžete využít direktivu report-uri
:
Content-Security-Policy: default-src 'none'; script-src 'self'; img-src https://moje-cdn.cz; report-uri /csp-report-parser.cgi;
Ta se postará o to, že při jakémkoliv zablokování prvku na stránce odešle prohlížeč podrobnosti pomocí metody HTTP POST ve formátu JSON na uvedenou adresu. Tam můžete mít svůj vlastní parser, který bude přijaté zprávy zpracovávat, ukládat, přeposílat do SIEMu apod. Příklad zprávy:
{ "csp-report": { "document-uri": "http://moje-stranka.cz/", "referrer": "", "violated-directive": "script-src 'self'", "effective-directive": "script-src", "original-policy": "default-src 'none'; script-src 'self'; img-src https://moje-cdn.cz; report-uri /csp-report-parser.cgi", "blocked-uri": "https://apis.google.com/js/plusone.js", "status-code":200, } }
Rovnou bych zde ale rád upozornil na fakt, že podobný vlastnoručně napsaný CGI skript pro parsování CSP zpráv může být bezpečnostním rizikem. Útočník si z HTTP hlaviček dokáže přečíst jeho adresu a může se tedy pokusit o různé typy útoků – shození skriptu či získání nadvlády nad serverem speciálně upraveným a nevalidním vstupem, zaspamování a případné vyčerpání diskového prostoru, DoS útok apod. Pokud tuto funkcionalitu chcete implementovat, věnujte maximální pozornost bezpečnosti.
Co se týká reportingu, určitě je vhodné zmínit hlavičku Content-Security-Policy-Report-Only
. Ta má naprosto stejnou syntaxi jako Content-Security-Policy
, avšak jak již název napovídá, slouží pouze k reportování a nic neblokuje. Samozřejmostí je její kombinace s direktivou report-uri
, jinak pozbývá smyslu. Toto se dá využít při prvotní implementaci – nejdříve využívat pouze reportování a po důkladném několikadenním testování a finálním doladění lze z hlavičky odstranit Report-Only
a vynutit si tak blokování. Obě hlavičky je možné kombinovat současně.
Další zajímavé direktivy
Zatím jsme si (s výjimkou report-uri
) představili direktivy končící pouze na –src
. Ty mají tu společnou vlastnost, že vymezují, odkud lze do stránky nahrávat externí zdroje. CSP však definuje i další direktivy a na některé z nich se podíváme.
Direktiva block-all-mixed-content
zabrání prohlížeči v nahrání jakéhokoliv prvku přes nešifrované HTTP, pokud je stránka načtena přes HTTPS. Většina prohlížečů dnes ve výchozím stavu blokuje pouze tzv. active mixed-content ale passive mixed-content nahraje spolu s varováním do konzole. Touto direktivou tedy zablokujete vše. Rozdíl mezi pasivním a aktivním mixed-contentem je dobře popsán například na webu Mozilly.
Související direktivou je upgrade-insecure-requests
. Ta je velmi podobná předchozí, avšak místo blokování nahradí veškeré HTTP odkazy za HTTPS. Dojde tedy k nahrazení:
<img src="http://moje-cdn.cz/obrazek.png">
za
<img src="https://moje-cdn.cz/obrazek.png">
Tato direktiva je vyhodnocena dříve, než block-all-mixed-content
, použití obou zároveň tedy pozbývá smyslu. Pokud daný zdroj nelze přes HTTPS načíst, nedojde k pokusu o načtení přes HTTP, což by představovalo bezpečnostní riziko. Tato direktiva samozřejmě může být použita společně s reportováním, což je v některých případech vhodné.
Poslední zmíněnou direktivou bude form-action
. Ta určuje, kam je možné odeslat data pomocí HTML elementu <form>
. Tím, že ji nastavíme jako cíl např. sebe samu:
Content-Security-Policy: form-action 'self';
zabráníme útočníkovi v injektování škodlivého formuláře do webové stránky, který se bude snažit získat uživatelské údaje a odesílat je na podvodný web.
Pár slov závěrem
CSP je stále ve vývoji, aktuálně se pracuje na verzi CSP 3. Podpora prohlížečů je různá, každopádně specifikaci CSP2 podporuje majorita dnešních prohlížečů, vyjma IE/Edge. Direktivy se stále vyvíjejí, avšak ty uvedené v tomto článku by měly fungovat vždy. Pro podrobnější specifikaci doporučuji web W3C.
Jak jsme si v tomto článku ukázali, CSP může být velkým pomocníkem proti útokům XSS, data injection a dokáže zabránit i únikům dat o uživateli pomocí nešifrovaného spojení. Pokud je to tedy možné, doporučuji CSP implementovat. Na druhou stranu je dobré si uvědomit, že CSP je plně kontrolováno prohlížečem a odehrává se vždy na straně klienta. Ten, pokud bude používat prohlížeč, který není podporovaný, účinky CSP ho minou. Proto se vždy pokuste všem těmto bezpečnostním hrozbám zabránit na straně serveru a CSP berte pouze jako takovou zálohu, pokud by primární linie obrany nestačila.