Content Security Policy
en liste over hvor en side får laste ting fra
Hva det er
Content Security Policy er et sett med regler et nettsted gir nettleseren om hva sidene får kjøre,
hente og sende inn, og som nettleseren deretter håndhever på nettstedets vegne. Navnet forkortes
nesten alltid til CSP. Nettstedet sender reglene i svarheaderen Content-Security-Policy, eller
skriver dem inn i siden i et meta-element, og fra det øyeblikket blir alt reglene ikke tillater,
avvist før det lastes, enten det er et skript, et stilark, et bilde, en skrift, en ramme eller en
tilkobling.
En policy er en kort streng med direktiver skilt med semikolon. Hvert direktiv navngir én type ressurs eller atferd og kildene det godtar. Her er en streng policy, vist med ett direktiv per linje for lesbarhetens skyld, selv om den over nettet er én enkelt headerverdi:
default-src 'self';
script-src 'nonce-q8Zt3VwR1mKx7NpL2cYfJA==' 'strict-dynamic';
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none'
Vokabularet er lite. 'self' betyr sidens eget opphav (origin). En vert som
https://static.example.com eller et URL-skjema som https: utvider det. 'none' tillater
ingenting. En nonce er en tilfeldig verdi serveren lager for ett enkelt svar og skriver både inn i
policyen og på hvert skriptelement den går god for. En hash er sammendraget (digest) av den
nøyaktige teksten i et innebygd skript, slik at akkurat det skriptet og ikke noe annet får kjøre.
To nøkkelord, 'unsafe-inline' og 'unsafe-eval', slår beskyttelsen av igjen, og navnene deres er
en rimelig advarsel.
default-src er reserven for de hentedirektivene (fetch directives) en policy utelater, som
script-src, style-src, img-src og connect-src. Den er en reserve for dem og bare dem.
Direktiver som styrer atferd og ikke henting, blant dem base-uri, form-action og
frame-ancestors, forblir ubegrenset helt til de skrives ut.
En policy kan også kjøre i en observerende modus. Sendt som
Content-Security-Policy-Report-Only blokkerer den ingenting og rapporterer i stedet hvert brudd
til et endepunkt navngitt med report-to, som bruker nettleserens Reporting API. Det eldre
direktivet report-uri er foreldet til fordel for det. Den gjeldende teksten i standarden er
Content Security Policy Level 3, et W3C Working Draft fra arbeidsgruppen Web Application Security
Working Group.
Hvorfor det er viktig
Angrepet CSP ble bygd mot, er krysskripting (cross-site scripting, XSS). Noen får tekst de selv har valgt, inn i en side, gjennom et kommentarfelt, et søkeord som gjentas i HTML-en, eller en kompromittert avhengighet, og nettleseren kjører den som skript med all den myndigheten nettstedets egen kode har: den kan lese siden, opptre som den innloggede personen og sende det den finner, hvor som helst. Det første forsvaret er riktig koding av utdata (escaping), og den må være riktig i hver mal, på hver linje, hver gang. CSP er det andre forsvaret. Når en glipp i kodingen likevel slipper gjennom, må det innsmuglede skriptet fortsatt forbi policyen, og en policy som bare kjører skript med nettopp dette svarets nonce, avviser det.
Den begrenser også hva et skript som faktisk kjører, kan nå. connect-src avgjør hvilke verter
skript får snakke med, noe som snevrer inn hvor en skimmer kan poste det den samler inn.
frame-ancestors avgjør hvilke nettsteder som får vise siden inne i en ramme. Det er
standardforsvaret mot clickjacking, der en side legges over en annen slik at et klikk havner et
sted den som klikket, aldri mente. form-action avgjør hvor et skjema får sende inn, og base-uri
hindrer et innsmuglet base-element i å omdirigere hver relative adresse på siden i det stille.
Det er verdt å være presis om hvem CSP beskytter. Den kjører i nettleseren, så den vokter personen som leser siden, mot innhold en angriper har plantet der. Den gjør ingenting for serveren mot den samme personen, som kan endre eller fjerne headeren i sin egen nettleser på sekunder. En policy er en grunn til å stole mindre på siden, aldri en grunn til å stole mer på forespørselen.
Kostnaden er for det meste kartlegging. En policy tvinger et team til å kjenne hvert opphav sidene laster fra, hvert innebygd skript og hver hendelseshåndterer som er skrevet som et attributt. Et nytt nettsted kan bygges etter en streng policy fra første commit for svært lite. Et eldre nettsted med innebygde håndterere, en tag manager og flere widgeter betaler for oppryddingen først, og den oppryddingen er som regel det egentlige prosjektet.
Slik ser det ut i praksis
Dette nettstedet holder seg selv til en slik policy. Sikkerhetssiden lister en
streng Content Security Policy, deklarert i selve siden, med form-action 'none', fordi
nettstedet ikke sender et eneste skjema noe sted. Den samme siden lister ett førstepartsskript
under én kilobyte og ingen forespørsler til tredjeparter i det hele tatt, og det er det som holder
en slik policy kort: alt en side trenger, kommer fra én vert, nettstedets egen, så det er svært lite
å tillate.
En applikasjon med innloggede personer trenger mer enn en kort tillatelsesliste. På en plattform
med faglige og organisatoriske data
setter proxy-mellomvaren i Next.js en Content-Security-Policy med en nonce per forespørsel og strict-dynamic,
i tillegg til HSTS og SameSite-cookies. En ny nonce på hvert svar betyr at bare skriptelementene
serveren stemplet på det svaret, får kjøre. Med strict-dynamic i policyen får et skript som
noncen stoler på, laste flere skript selv, så policyen trenger ikke å navngi hver vert en pakke
henter bitene sine fra.
Den samme siden sier tydelig hvor nettleserens del slutter: «Arbeidsantakelsen må være at klienten er fiendtlig». Derfor bestemmer policyen bare hvilken kode nettleseren kjører. Hvem som får gjøre hva, avgjør serveren selv for hver forespørsel, og sperrene i frontenden er UX og ikke mer, eller med sidens egne ord: «Sperrene avgjør hva man ser; serveren avgjør hva man kan gjøre.» CSP er ett lag av flere der, og jobben er å beskytte de innloggede mot innsmuglet kode.
Hvor det går galt
Policyen går i produksjon, og halve nettstedet slutter å virke. En streng policy som sendes
rett til håndheving, blokkerer det innebygde skriptet ingen husket, og sporingstaggen noen la til
for to år siden. Send den som Content-Security-Policy-Report-Only først, les rapportene til de
stilner, tillat eller fjern hver kilde de navngir, og håndhev først da. Headeren for report-only
må være en ekte header: spesifikasjonen støtter den ikke i et meta-element.
Tillatelseslisten stoler på en hel vert. script-src https://cdn.example.com tillater hver
fil på den verten, også gamle bibliotekversjoner og endepunkter som sender inndata fra den som
kaller, tilbake som skript. Spesifikasjonen sier selv at det er en utfordring å rulle ut en policy
som virker mot krysskripting, og anbefaler det den kaller en Strict CSP: script-src bygd av
nonces eller hasher med 'strict-dynamic', pluss base-uri satt til 'self' eller 'none'.
'unsafe-inline' antas å tillate alle innebygde skript. Så snart en nonce eller en hash står i
den samme kildelisten, overser nettlesere som forstår dem, 'unsafe-inline'. Regelen lar en
policy beholde 'unsafe-inline' som reserve for nettlesere som er for gamle til å kjenne nonces,
mens nyere nettlesere bruker den strenge listen. Den overrasker et team som legger en nonce på ett
skript og finner de andre innebygde skriptene sine blokkert.
Noncen slutter å være en hemmelighet. Spesifikasjonen krever en ny verdi hver gang en policy sendes, og ber om minst 128 bit fra en kryptografisk sikker generator. En nonce som er hardkodet i en mal, eller en hel side som lagres med noncen sin i en delt cache og serveres til alle, gir en angriper akkurat den verdien som trengs for å stemple sitt eget skript.
En policy i et meta-element gjør mindre enn den samme policyen i en header. Nettleseren
overser frame-ancestors, report-uri og sandbox når de kommer den veien, og policyen styrer
bare det som kommer etter elementet i dokumentet, så alt over det i head lastes ukontrollert. Et
nettsted som ikke rår over headerne i sine egne svar, får likevel ekte beskyttelse av en
meta-policy, plassert så tidlig i head som den kan stå, men beskyttelse mot innramming krever
headeren.
En policy nummer to strammer inn i stedet for å løsne. Et svar kan bære flere policyer, fra et rammeverk, en proxy og et CDN, og en ressurs må bestå hver eneste av dem. Å legge til en policy kan bare begrense, så en romslig header som legges til lenger ned i kjeden for å rette en blokkert forespørsel, gjør ingenting. Rettelsen må inn i policyen som blokkerte den.
Les mer
- Content Security Policy Level 3 — selve standarden, med anbefalingen om Strict CSP og et avsnitt om hvordan nonces blir stjålet.
- HTML Standard: the meta element — pragmaet som tar i bruk en policy deklarert i siden, direktivene det dropper, og hvorfor ressurser hentet før det kan slippe forbi.
- Reporting API — endepunktmekanismen bak
report-to, for å lese brudd før policyen håndheves. - Trusted Types — følgestandarden som direktivet
require-trusted-types-forslår på, rettet mot skript som smugles inn gjennom DOM-en.