Jak správně nastavit CSP a script-src
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
Dobrý den,
jak lze nastavit config.neon tak aby jsem mohl nastavit jiná pravidla pro FrontModule a jiná pro AdminModule?
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.
#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
Chcete-li odeslat komentář, přihlaste se