Jak správně nastavit CSP a script-src

před 5 lety od David Grudl  

Content Security Policy (CSP) je dodatečný bezpečnostní prvek, který prohlížeči říká, jaké další zdroje může stránka načítat a jak může být zobrazena. Chrání tak před vkládáním škodlivého kódu a útokům jako je XSS. Odesílá se v podobě hlavičky sestavené z řady direktiv.

Tento článek se věnuje direktivě script-src, která říká, jaké skripty může stránka spouštět. Její nasazení totiž není zcela triviální.

Pokud bychom požadovali jen to, aby prohlížeč zabránil spouštění JavaScriptu přímo zapsaného v HTML kódu stránky (tj. inline skriptů) a dále skriptů uložených mimo naši doménu, je to jednoduché. Stačí použít script-src 'self'. Nebo povolit další domény script-src 'self' static.mojedomena.cz.

Jenže obvykle tak jednoduché to není. Často chceme používat i knihovny a nástroje umístěné mimo náš server, například měřící kód Google Analytics, reklamní systémy, captchy atd. A tady bohužel první verze CSP selhává. Vyžaduje přesnou analýzu načítaného obsahu a nastavení správných pravidel. Tedy vytvořit whitelist, výčet všech domén, což není snadné, jelikož některé skripty dynamicky dotahují další skripty z jiných domén, nebo jsou na jiné domény přesměrované atd. Provozovatelé služeb na CSP často kašlou a jen málokdy zdokumentují pravidla potřebná pro funkčnost jejich kódu. A i když si dáte práci a seznam vytvoříte ručně, nikdy nevíte, co se může v budoucnu změnit, takže musíte neustále sledovat, jestli je seznam stále aktuální a opravovat ho. Analýza Google ukázala, že i to pečlivé ladění ve finále vede k tomu, že povolíte tak široký přístup, že celý smysl CSP padá, jen posíláte s každým požadavek mnohem větší hlavičky.

Další verze CSP level 2 (částečně) nahrazuje whitelisty za tzv. nonce, což je náhodně vygenerovaný jednorázový token lišící se pro každý HTTP požadavek, který odešleme v hlavičce script-src 'nonce-c7f26c' a následně uvedeme v každém elementu:

<script nonce="c7f26c" src="..."></script>

<script nonce="c7f26c"> ... kód ... </script>

Skripty, které nemají správný nonce, prohlížeč zablokuje. S nonce konečně chytlo CSP věc za správný konec, bohužel ale nefunguje pro další skripty, které JavaScript dynamicky vkládá. Myšleno elementem vytvořeným pomocí document.createElement('script'). Jejich lokace je potřeba opět povolit whitelistem nebo do nich vložit JavaScriptem atribut nonce.

Tohle řeší až CSP level 3. Konečně! Stačí doplnit do hlavičky strict-dynamic. Bohužel podpora CSP 3 v roce 2019 není zdaleka ideální, chybí například v iOS.

Jak správně nastavit script-src?

Začneme s nonce & strict-dynamic. Na začátku každého requestu nonce vygenerujeme, odešleme v hlavičce a vypíšeme v každém elementu script (příklady níže). Tím máme zajištěno, že jen podepsané skripty se spustí a ostatní prohlížeč zablokuje. V prohlížečích podporujících CSP 3 tak máme vystaráno, všechny knihovny budou fungovat a netřeba nic víc dělat.

Ale prohlížeče podporující jen CSP 2? Tam by nefungovalo dynamické načítaných dalších skriptů. A samozřejmě nechceme pracně zkoumat, odkud která knihovna co donačítá, proto povolíme načítání odkudkoliv a to doplněním * (případně striktnější 'https:' apod.) do hlavičky, čímž částečně simulujeme strict-dynamic. Bohužel jen částečně, protože prohlížeč bude blokovat dynamické inline skripty, tedy elementy vytvořené pomocí document.createElement('script') které místo atributu src obsahují přímo kód. S tím si CSP 2 neporadí. Jestli se něco takového týká vašeho webu můžete zjistit monitoringem, viz níže.

A co v případě prohlížečů, které podporují jen CSP 1 a nonce tedy neznají? Jak jsem v úvodu psal, vyjmenování všech domén je stěží řešitelný úkol, což určitě nechceme podstupovat kvůli pradávným málo používaným prohlížečům, takže v nich ochranu vypneme, tj. povolíme načítání odkudkoliv a to již zmíněným uvedením hvězdičky, a dále povolením inline skriptů (tj. <script>...kód...</script>) pomocí unsafe-inline.

Výsledná podoba univerzální direktivy script-src vypadá následovně:

script-src 'nonce-XXXXX' 'strict-dynamic' * 'unsafe-inline'

Příklad použití v Nette

Protože Nette má vestavěnou podporu pro CSP a nonce od verze 2.4, stačí v konfiguračním souboru uvést:

http:
	csp:
		script-src: [nonce, strict-dynamic, *, unsafe-inline]

A v šablonách pak používat:

<script n:nonce src="...">

eval()

Content Security Policy také zakazuje používání funkce eval(). Je velmi nepravděpodobné, že některá z knihoven by tuhle funkci používala, ale kdyby ano, můžete ji povolit doplněním 'unsafe-eval' do hlavičky.

Kaskádové styly

Téměř totéž, co platí pro <script>, lze použít i pro kaskádové styly (<link> a <style>) s direktivou style-src. Chybí pouze strict-dynamic, který se týká jen JavaScriptu.

http:
	csp:
		style-src: [nonce, *, unsafe-inline]

A opět doplníme atributy n:nonce u elementů <style> nebo <link>.

Atributy onevent a style

Používání atributů jako onclick nebo style nelze nijak při nasazení nonce povolit, takže je potřeba je přepsat do klasických skriptů nebo stylů.

Obrázky, iframe a další

Také v případě obrázků, videa, audia, iframe a podobně můžete pomocí direktiv jako img-src, media-src, frame-src, font-src nebo object-src kontrolovat, odkud se načítají. Ale pozor, u těchto direktiv se kombinace nonce a hvězdičky chová úplně jinak (tj. nonce nemusí být podporované) a je potřeba uvádět whitelist zdrojů.

Monitoring

Než nastavíte nová pravidla pro CSP, vyzkoušejte si je nejprve nanečisto pomocí hlavičky Content-Security-Policy-Report-Only. Ta funguje ve všech prohlížečích podporujících CSP. Při porušení pravidel prohlížeč nezablokuje skript, ale jen pošle notifikaci na URL uvedené v direktivě report-uri. K příjmu notifikací a jejich analýze můžete použít třeba službu Report URI.

http:
	cspReportOnly:
		script-src: [nonce, strict-dynamic, *, unsafe-inline]
		report-uri: https://xxx.report-uri.com/r/d/csp/reportOnly

Tak se dá odhalit, jestli třeba neselhává nějaká knihovna na dynamických skriptech v CSP 2, volání funkce eval nebo na nějaké chybě v prohlížeči.

Můžete zároveň používat obě hlavičky a v Content-Security-Policy mít ověřené a aktivní pravidla a zároveň v Content-Security-Policy-Report-Only si testovat jejich úpravu. Samozřejmě i selhání ostrých pravidlech si můžete nechat monitorovat.

Komentáře

  1. Dobrý den,
    jak lze nastavit config.neon tak aby jsem mohl nastavit jiná pravidla pro FrontModule a jiná pro AdminModule?

    před 5 lety · replied [2] spaze
  2. Díky za článek, Davide!

    Udělal jsem testovací/vysvětlovací „živou“ stránku pro ‚strict-dynamic‘, je tu https://exploited.cz/…p/strict.php a třeba se někomu bude hodit. A pro vyzkoušení a ukazování všemožného reportování jsem vyrobil jednoduchou aplikaci https://canhas.report, kterou používám na různých přednáškách, kde o reportování mluvím.

    #1 JIROUT REKLAMNÍ AGENTURA Myslím, že to nejde. Pro možnost posílat různé hlavičky v různých modulech i presenterech a akcích jsem si před několika lety napsal rozšíření https://github.com/…e/csp-config, které přesně takovou věc dělá a od té doby ho používám na svém webu. Můžete ho v klidu použít nebo se inspirovat. Bohužel to neumí hlavičku i samo poslat, tu je potřeba poslat ručně, ale klidně by to mohlo dělat i automaticky – za případný pull request budu rád.

    před 5 lety · replied [3] JIROUT REKLAMNÍ AGENTURA
  3. #2 spaze děkuji za komentář, rozšíření omrknu později důkladně. Škoda, že to rozdělení na moduly nejde už rovnou v nette bez dalšího rozšíření. Jinak moc se mi líbí ten nápad rozdělení až na jednotlivé akce protože jsem byl při použití wysiwygu nucen to ohnout když jsem to před půl rokem implementoval a nastavit jinou politiku přímo v kódu – což se mi vůbec nelíbí.

    „…za případný pull request budu rád“ – když se k tomu dostanu, popřemýšlím nad tím, už nějakou dobu uvažuju, že se zapojím aktivněji do světa PHP a nette

    Josef Zelenka

    před 5 lety

Chcete-li odeslat komentář, přihlaste se