Egy CSP-fejlécet könnyű úgy kipipálni, hogy script-src 'self', aztán kész. Ettől még az XSS elleni védelem gyakran csak látszólagos: a saját originről betölthető régi JavaScript, egy engedélyezett külső szolgáltató vagy egy rosszul karbantartott hostlista könnyen túl nagy bizalmi kört ad. A Content Security Policy akkor ad érdemi második védelmi vonalat, ha nem URL-eket, hanem konkrétan megbízható kódrészleteket jelölünk ki.

Erre való a nonce-os, 'strict-dynamic'-os CSP. Nem XSS-helyettesítő: a bemenetek helyes kódolását és a biztonságos DOM-kezelést továbbra is az OWASP XSS-ajánlásai szerint kell megoldani. Viszont egy hibán át becsúszó <script> futását a böngésző akkor is megállíthatja.
A nonce nem egy titkos konfigurációs érték
A szerver minden HTML-válaszhoz készít egy kriptográfiailag véletlen, egyszer használatos értéket. Ugyanez bekerül a Content-Security-Policy fejlécbe és azoknak a script elemeknek a nonce attribútumába, amelyeket a szerver szándékosan kiküldött. A böngésző csak az egyezést fogadja el. A script-src dokumentációja külön kiemeli, hogy a nonce-nak válaszonként egyedinek kell lennie.
import { randomBytes } from "node:crypto";
function createCsp() {
const nonce = randomBytes(16).toString("base64");
const policy = [
"default-src 'self'",
`script-src 'nonce-${nonce}' 'strict-dynamic' 'self'`,
"object-src 'none'",
"base-uri 'none'",
].join("; ");
return { nonce, policy };
}
Az alkalmazás a policy értékét HTTP-fejlécként küldi, majd a sablonban ugyanazt a nonce-ot használja: <script nonce="…" src="/assets/boot.js" defer></script>. A nonce ne legyen buildid, sessionazonosító vagy állandó környezeti változó. Ha egy támadó egy előző válaszból ismeri, a policy már nem választja el a saját scriptjét a szerverétől.
Mit ad hozzá a strict-dynamic?
Nonce önmagában azt mondja meg, melyik kezdeti script indulhat el. A CSP3 'strict-dynamic' szabálya ehhez bizalmi láncot tesz: egy nonce-os vagy hash-elt, ezért megbízhatónak kijelölt script által programból létrehozott script elem is betölthető. Tipikus eset a bootstrap fájl, amely a route alapján tölti a kóddarabokat vagy egy harmadik féltől származó SDK-t.
Ez nem azt jelenti, hogy „minden dinamikus script mehet”. A bizalom a jóváhagyott kezdő scriptből ered. Egy HTML-injekcióval beszúrt, nonce nélküli, parser által feldolgozott <script src="…"> továbbra is blokkolódik. Ezzel a hostlista állandó gondozása helyett arra a jóval rövidebb listára koncentrálhatunk, melyik belépő kódot adjuk ki a kezünkből.
A nonce nem engedélyezi az inline eseménykezelőket sem: az onclick="…" és társai számára nem helyettesíti az 'unsafe-inline'-t. Ez jó kényszer. A handler kerüljön a nonce-os külső vagy inline scriptbe addEventListener-rel. Az 'unsafe-inline' felvétele csak azért, hogy a régi sablon működjön, a régebbi CSP-értelmezőkben épp azt a védelmet oldhatja fel, amelyet bevezetnénk. Ugyanezért az 'unsafe-eval' engedélyezése is legyen külön, megindokolt kivétel.
A példában szereplő 'self' nem véletlen. A strict CSP telepítéséről szóló web.dev útmutató szerint CSP3-as böngészőben a nonce és a 'strict-dynamic' mellett a hostforrások nem adják a tényleges engedélyt; a régebbi CSP-értelmezők viszont nem ismerik a 'strict-dynamic'-ot, ezért náluk a 'self' marad visszaesési szabály. Ha a bootstrap külső originről tölt további scriptet, a régebbi kliensekhez annak célzott hostját is fel kell venni – nem egy általános https: engedélyt.
A bevezetés buktatója: a cache
A nonce-os oldal dinamikus HTML. Egy közös CDN- vagy reverse-proxy-cache nem szolgálhatja ki változatlanul ugyanazt a HTML-t és CSP-fejlécet sok kérésre, mert ezzel a nonce újrahasználódik. Ilyenkor vagy az edge-en kell válaszonként beilleszteni az értéket, vagy a stabil inline kódhoz hash-alapú CSP-t érdemes választani. A kettőt keverni is lehet, de a nonce-nak akkor is frissnek kell maradnia.
WordPress-, tag manager- és widgetdús oldalon a migráció munka: minden engedélyezett inline scriptnek meg kell kapnia a nonce-ot, a beillesztett külső kódot pedig fel kell mérni. Első körben célszerű ugyanazzal a policyval Content-Security-Policy-Report-Only fejlécet küldeni, figyelni a sértéseket, majd a szükséges eseteket megszüntetni vagy tudatosan engedélyezni. A cél nem a nullára csökkentett konzolhiba, hanem az, hogy ne maradjon indokolatlan scriptfuttatási út.
Mikor éri meg?
Ott különösen hasznos, ahol felhasználói vagy CMS-ből származó tartalom megjelenik, sok kliensoldali kód fut, és egy XSS ára magas. Nem varázspajzs: egy nonce-os, már futó saját kódban lévő DOM-XSS-t nem javít ki. Viszont szűkíti a hibák kihasználható felületét, és egy jó boot.js-t valódi bizalmi gyökérré tesz. A fontos váltás fejben ennyi: ne azt kérdezd, „melyik domain tölthet scriptet?”, hanem azt, „melyik konkrét kód indulhat el először?”