La Content Security Policy (CSP) è un header HTTP con cui dichiari al browser da dove la tua pagina può caricare risorse ed eseguire codice. Se un attaccante riesce a iniettare uno script nella pagina, una CSP ben configurata impedisce al browser di eseguirlo. Non sostituisce la corretta gestione dell'input, ma è una seconda linea di difesa molto efficace contro il cross-site scripting.
Il problema è che una CSP scritta in fretta rompe il sito: script bloccati, stili mancanti, widget che spariscono. Questo articolo mostra come costruirla un passo alla volta.
La struttura di una policy
Una CSP è un elenco di direttive, ognuna con le sue sorgenti consentite, separate da punto e virgola:
Content-Security-Policy: default-src 'self'; img-src 'self' data:; script-src 'self'
Le direttive più usate:
| Direttiva | Controlla |
|---|---|
default-src |
valore di riserva per tutte le direttive non specificate |
script-src |
file JavaScript e script inline |
style-src |
fogli di stile e stili inline |
img-src |
immagini |
font-src |
web font |
connect-src |
richieste con fetch, XHR, WebSocket |
frame-src |
contenuti incorporati in iframe |
frame-ancestors |
chi può incorniciare la tua pagina |
form-action |
indirizzi a cui i form possono inviare dati |
base-uri |
valori consentiti per l'elemento base |
object-src |
plugin come object ed embed |
Le sorgenti più comuni sono 'self' (la stessa origine della pagina), 'none' (nulla), un dominio specifico come https://cdn.esempio.it e schemi come data: o https:. Le parole chiave vanno tra apici singoli; i domini no.
Il problema degli script inline
Per impostazione predefinita, una CSP con script-src blocca tutti gli script inline: i blocchi <script> nell'HTML e gli attributi come onclick. È proprio il punto di forza della policy, perché gli attacchi XSS iniettano quasi sempre codice inline.
La scorciatoia 'unsafe-inline' riabilita tutto, ma annulla gran parte della protezione. Le alternative corrette sono due.
Nonce
Il server genera un valore casuale diverso a ogni risposta e lo inserisce sia nell'header sia negli script legittimi:
Content-Security-Policy: script-src 'nonce-R4nd0mV4lu3'
<script nonce="R4nd0mV4lu3">
inizializzaMenu();
</script>
Un attaccante che inietta uno script non conosce il nonce, quindi il suo codice viene bloccato. Il nonce funziona solo se è davvero imprevedibile e rigenerato a ogni richiesta: un nonce fisso scritto nel modello non protegge nulla.
Hash
Per siti statici, dove non c'è un server che genera valori a ogni richiesta, puoi autorizzare uno script inline tramite l'hash SHA-256 del suo contenuto:
Content-Security-Policy: script-src 'self' 'sha256-<hash in base64>'
Se il contenuto dello script cambia anche di un solo carattere, l'hash non corrisponde più e va aggiornato. Molti strumenti di build possono calcolarlo automaticamente.
La soluzione più semplice
Spesso la via migliore è spostare il codice inline in file esterni e sostituire gli attributi onclick con addEventListener. Così basta script-src 'self'.
Introdurla per gradi con Report-Only
La CSP ha una modalità di prova: l'header Content-Security-Policy-Report-Only applica le stesse regole ma non blocca nulla, si limita a segnalare le violazioni nella console del browser ed, eventualmente, a un indirizzo di raccolta.
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Un percorso pratico:
- Pubblica una policy restrittiva in modalità Report-Only.
- Naviga il sito in tutte le sue parti e raccogli le violazioni per qualche giorno.
- Per ogni violazione decidi: è una risorsa legittima da autorizzare, o codice da riscrivere?
- Quando le segnalazioni legittime sono finite, passa all'header vero e proprio.
- Mantieni la raccolta delle segnalazioni anche dopo, per accorgerti di regressioni.
La direttiva report-uri è ancora molto diffusa; quella più recente è report-to, che si appoggia a un header separato per definire gli indirizzi di raccolta.
Una policy di partenza
Per un sito che serve tutto dalla propria origine, senza servizi di terze parti:
Content-Security-Policy:
default-src 'self';
img-src 'self' data:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'self';
upgrade-insecure-requests
(Nell'header reale va tutto su una riga.) object-src 'none' e base-uri 'self' chiudono due vie di attacco spesso dimenticate; upgrade-insecure-requests chiede al browser di caricare in HTTPS le risorse indicate in HTTP.
Servizi di terze parti
Analytics, mappe, video incorporati e font esterni richiedono di aggiungere i loro domini alle direttive appropriate. Ogni dominio autorizzato è una fiducia che concedi: se quel dominio ospita anche contenuti caricati da altri utenti, un attaccante potrebbe sfruttarlo per far passare il proprio script. Autorizza solo ciò che serve e preferisci, dove possibile, ospitare le risorse sul tuo dominio.
Errori frequenti
- Usare
'unsafe-inline'e'unsafe-eval'"per far funzionare tutto", vanificando la policy. - Dimenticare
connect-srce rompere le chiamate API. - Autorizzare interi schemi come
https:inscript-src: equivale a fidarsi di tutto il web. - Mettere la policy in un tag
metae aspettarsi che funzioninoframe-ancestorso le segnalazioni: in quella forma non sono supportate.
In sintesi
- La CSP indica al browser da dove la pagina può caricare risorse ed eseguire codice.
- Evita
'unsafe-inline': usa nonce casuali, hash o sposta il codice in file esterni. - Introducila con
Content-Security-Policy-Report-Onlye analizza le violazioni. - Includi sempre
object-src 'none',base-urieframe-ancestors. - Ogni dominio di terze parti autorizzato è un rischio: limita l'elenco al necessario.