⌨️ Sviluppo web

Content Security Policy: configurarla senza rompere il sito

Come funziona la Content Security Policy: direttive principali, perché evitare unsafe-inline, nonce, hash e modalità Report-Only per introdurla per gradi.

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:

  1. Pubblica una policy restrittiva in modalità Report-Only.
  2. Naviga il sito in tutte le sue parti e raccogli le violazioni per qualche giorno.
  3. Per ogni violazione decidi: è una risorsa legittima da autorizzare, o codice da riscrivere?
  4. Quando le segnalazioni legittime sono finite, passa all'header vero e proprio.
  5. 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-src e rompere le chiamate API.
  • Autorizzare interi schemi come https: in script-src: equivale a fidarsi di tutto il web.
  • Mettere la policy in un tag meta e aspettarsi che funzionino frame-ancestors o 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-Only e analizza le violazioni.
  • Includi sempre object-src 'none', base-uri e frame-ancestors.
  • Ogni dominio di terze parti autorizzato è un rischio: limita l'elenco al necessario.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche