Cross-site scripting (XSS), la capacità di inserire script dannosi in un'app web, è da oltre un decennio una delle principali vulnerabilità per la sicurezza web.
Criterio di sicurezza del contenuto (CSP)
è un ulteriore livello di sicurezza che contribuisce a mitigare l'XSS. Per configurare un CSP, aggiungi l'intestazione HTTP Content-Security-Policy
a una pagina web e imposta i valori che controllano quali risorse lo user agent può caricare per quella pagina.
Questa pagina spiega come utilizzare un CSP basato su nonce o hash per mitigare l'XSS, al posto dei CSP di uso comune basati su lista consentita di host che spesso lasciano la pagina esposta a XSS perché possono essere ignorati nella maggior parte delle configurazioni.
Termine chiave: un nonce è un numero casuale utilizzato solo una volta che puoi usare per contrassegnare un tag <script>
come attendibile.
Termine chiave: una funzione hash è una funzione matematica che converte un valore di input in un valore numerico compresso chiamato hash. Puoi utilizzare un hash
(ad esempio, SHA-256) per contrassegnare un tag <script>
incorporato come attendibile.
Un criterio di sicurezza del contenuto basato su nonce o hash viene spesso chiamato CSP rigoroso. Quando un'applicazione utilizza un CSP rigoroso, gli utenti malintenzionati che riscontrano difetti di inserimento HTML generalmente non possono utilizzarli per forzare il browser a eseguire script dannosi in un documento vulnerabile. Questo perché il criterio CSP rigoroso consente solo script o script sottoposti ad hashing con il valore nonce corretto generato sul server. Di conseguenza, gli utenti malintenzionati non possono eseguire lo script senza conoscere il nonce corretto per una determinata risposta.
Perché dovresti usare un CSP rigido?
Se il tuo sito ha già un criterio CSP simile a script-src www.googleapis.com
, è probabile che non sia efficace contro i cross-site. Questo tipo di CSP è chiamato
CSP della lista consentita. Richiedono molta personalizzazione e possono essere aggirati dai malintenzionati.
CSP rigorosi basati su hash o nonce crittografici evitano queste insidie.
Struttura CSP rigorosa
Un criterio di sicurezza del contenuto di base rigida utilizza una delle seguenti intestazioni di risposta HTTP:
CSP nonce basato su rigorose
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
![](https://web.developers.google.cn/static/articles/strict-csp/image/er4BaGCJzBwDaESFKfZd.jpg?authuser=9&hl=it)
CSP rigoroso basato su hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Le seguenti proprietà rendono un CSP come questo "rigoroso" e quindi sicuro:
- Utilizza nonce
'nonce-{RANDOM}'
o hash'sha256-{HASHED_INLINE_SCRIPT}'
per indicare quali tag<script>
lo sviluppatore del sito ritiene affidabili per l'esecuzione nel browser dell'utente. - Imposta
'strict-dynamic'
in modo da ridurre l'impegno necessario per eseguire il deployment di un CSP basato su hash o nonce, consentendo automaticamente l'esecuzione di script creati da uno script attendibile. Ciò sblocca anche l'utilizzo della maggior parte delle librerie e dei widget JavaScript di terze parti. - Non si basa sulle liste consentite di URL, quindi non risente dei comuni bypass dei CSP.
- Blocca gli script incorporati non attendibili, come i gestori di eventi incorporati o gli URI
javascript:
. - Limita
object-src
a disattivare plug-in pericolosi come Flash. - Limita
base-uri
a bloccare l'inserimento di tag<base>
. In questo modo gli utenti malintenzionati non possono modificare le posizioni degli script caricati dagli URL relativi.
Adotta un CSP rigoroso
Per adottare un CSP rigoroso, devi:
- Decidi se la tua applicazione deve impostare un CSP basato su nonce o hash.
- Copia il CSP dalla sezione Struttura di CSP restrittiva e impostalo come intestazione di risposta in tutta l'applicazione.
- Esegui il refactoring dei modelli HTML e del codice lato client per rimuovere i pattern incompatibili con CSP.
- Esegui il deployment di CSP.
Puoi utilizzare il controllo delle best practice di Lighthouse
(v7.3.0 e successive con flag --preset=experimental
) durante questa procedura per verificare se il tuo sito ha un criterio CSP e se è abbastanza rigoroso per essere efficace contro le soluzioni XSS.
![Avviso del report Lighthouse che indica che non è stato trovato alcun CSP in modalità di applicazione forzata.](https://web.developers.google.cn/static/articles/strict-csp/image/lighthouse-report-warning-74f66e2ef76dc.png?authuser=9&hl=it)
Passaggio 1: decidi se ti serve un CSP basato su hash o nonce
Ecco come funzionano i due tipi di CSP rigidi:
CSP nonce
Con un CSP basato su nonce, generi un numero casuale in fase di runtime, lo includi nel CSP e lo associ a ogni tag script nella pagina. Un utente malintenzionato non può includere o eseguire uno script dannoso nella tua pagina perché dovrebbe indovinare il numero casuale corretto per quello script. Funziona solo se il numero non è indovinabile e viene generato di recente in fase di runtime per ogni risposta.
Utilizza un criterio CSP nonce per le pagine HTML visualizzate sul server. Per queste pagine, puoi creare un nuovo numero casuale per ogni risposta.
CSP basato su hash
Per un CSP basato su hash, l'hash di ogni tag script incorporato viene aggiunto a quest'ultimo. Ogni script ha un hash diverso. Un utente malintenzionato non può includere o eseguire uno script dannoso nella tua pagina, perché per essere eseguito l'hash di quello script deve essere nel tuo CSP.
Utilizza un criterio CSP basato su hash per le pagine HTML pubblicate in modo statico o per le pagine che devono essere memorizzate nella cache. Ad esempio, puoi utilizzare un criterio CSP basato su hash per le applicazioni web a pagina singola create con framework come Angular, React o altri che vengono pubblicati in modo statico senza rendering lato server.
Passaggio 2: imposta un criterio CSP preciso e prepara gli script
Quando imposti un criterio CSP, hai a disposizione alcune opzioni:
- modalità di solo report (
Content-Security-Policy-Report-Only
) o modalità di applicazione forzata (Content-Security-Policy
). In modalità di solo report, il CSP non blocca ancora le risorse, quindi non c'è nulla sul tuo sito che causa errori, ma puoi visualizzare gli errori e ricevere report per qualsiasi elemento che sarebbe stato bloccato. A livello locale, quando imposti il CSP, questo non ha importanza, perché entrambe le modalità mostrano gli errori nella console del browser. La modalità di applicazione forzata può aiutarti a trovare le risorse che la bozza di CSP blocca, perché il blocco di una risorsa può far sembrare la pagina non funzionante. La modalità solo report diventa più utile in una fase successiva del processo (vedi il passaggio 5). - Tag intestazione o HTML
<meta>
. Per lo sviluppo locale, un tag<meta>
può essere più pratico per modificare il CSP e vedere rapidamente come influisce sul tuo sito. Tuttavia:- In seguito, durante il deployment di CSP in produzione, ti consigliamo di impostarlo come intestazione HTTP.
- Se vuoi impostare il CSP in modalità di solo report, devi impostarlo come intestazione, perché i meta tag CSP non supportano la modalità di solo report.
Imposta la seguente intestazione della risposta HTTP Content-Security-Policy
nella tua applicazione:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Genera un nonce per CSP
Un nonce è un numero casuale utilizzato solo una volta per caricamento pagina. Un CSP basato sul nonce può mitigare l'XSS solo se gli utenti malintenzionati non sono in grado di indovinare il valore nonce. Un nonce CSP deve essere:
- Un valore casuale con una crittografia forte (preferibilmente con una lunghezza superiore a 128 bit)
- Nuova generazione per ogni risposta
- Codifica Base64
Ecco alcuni esempi di come aggiungere un nonce CSP nei framework lato server:
- Django (python)
- Express (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Aggiungi un attributo nonce
agli elementi <script>
Con un CSP nonce basato su CSP, ogni elemento <script>
deve avere un attributo nonce
che corrisponde al valore nonce casuale specificato nell'intestazione CSP. Tutti gli script possono avere lo stesso nonce. Il primo passaggio consiste nell'aggiungere questi attributi a tutti gli script in modo che il CSP li consenta.
Imposta la seguente intestazione della risposta HTTP Content-Security-Policy
nella tua applicazione:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Per più script incorporati, la sintassi è la seguente:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Carica dinamicamente gli script provenienti
Poiché gli hash CSP sono supportati nei vari browser solo per gli script incorporati, devi caricare tutti gli script di terze parti in modo dinamico utilizzando uno script incorporato. Gli hash per gli script provenienti da fonti non sono ben supportati su tutti i browser.
![](https://web.developers.google.cn/static/articles/strict-csp/image/B2YsfJDYw8PRI6kJI7Bs.jpg?authuser=9&hl=it)
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script>Per consentire l'esecuzione di questo script, devi calcolare l'hash dello script incorporato e aggiungerlo all'intestazione della risposta CSP, sostituendo il segnaposto
{HASHED_INLINE_SCRIPT}
. Per ridurre la quantità di hash, puoi unire tutti gli script incorporati in un unico script. Per vedere come funziona, fai riferimento a questo
esempio
e al relativo codice.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>CSP blocca questi script perché solo gli script incorporati possono essere sottoposti ad hashing.
Considerazioni sul caricamento degli script
Nell'esempio di script incorporato, viene aggiunto s.async = false
per garantire che foo
venga eseguito prima del giorno bar
, anche se viene caricato prima bar
. In questo snippet, s.async = false
non blocca l'analizzatore sintattico durante il caricamento degli script, poiché questi vengono aggiunti
in modo dinamico. Il parser si arresta solo durante l'esecuzione degli script, come farebbe per gli script async
. Tuttavia, con questo snippet, tieni presente che:
-
Uno o entrambi gli script potrebbero essere eseguiti prima del termine del download del documento. Se vuoi che il documento sia pronto quando vengono eseguiti gli script, attendi l'evento
DOMContentLoaded
prima di aggiungere gli script. Se ciò causa un problema di prestazioni perché il download degli script non inizia abbastanza presto, utilizza il precaricamento dei tag nella parte precedente della pagina. -
defer = true
non esegue alcuna azione. Se devi avere questo comportamento, esegui lo script manualmente quando è necessario.
Passaggio 3: esegui il refactoring dei modelli HTML e del codice lato client
Per eseguire gli script è possibile utilizzare gestori di eventi incorporati (come onclick="…"
, onerror="…"
) e URI JavaScript (<a href="javascript:…">
). Ciò significa che un utente malintenzionato che trova un bug XSS può inserire questo tipo di codice HTML ed eseguire JavaScript dannoso. Un CSP basato su nonce o hash vieta l'utilizzo di questo tipo di markup.
Se il tuo sito utilizza uno di questi pattern, dovrai trasformarli in alternative più sicure.
Se hai abilitato CSP nel passaggio precedente, potrai visualizzare le violazioni CSP nella console ogni volta che CSP blocca un pattern incompatibile.
![Report sulle violazioni CSP nella Developer Console di Chrome.](https://web.developers.google.cn/static/articles/strict-csp/image/csp-violation-reports-th-ff599b24b17be.jpg?authuser=9&hl=it)
Nella maggior parte dei casi, la soluzione è semplice:
Refactoring dei gestori di eventi incorporati
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>CSP consente i gestori di eventi registrati utilizzando JavaScript.
<span onclick="doThings();">A thing.</span>CSP blocca i gestori di eventi incorporati.
Esegui il refactoring degli URI di javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>CSP consente i gestori di eventi registrati utilizzando JavaScript.
<a href="javascript:linkClicked()">foo</a>CSP blocca JavaScript: URI.
Rimuovi eval()
da JavaScript
Se la tua applicazione utilizza eval()
per convertire le serializzazioni di stringhe JSON in oggetti JS, devi eseguire il refactoring di queste istanze in JSON.parse()
, che è anche più veloce.
Se non riesci a rimuovere tutti gli utilizzi di eval()
, puoi comunque impostare un CSP rigido basato su nonce, ma devi utilizzare la parola chiave CSP 'unsafe-eval'
, il che rende il tuo criterio leggermente meno sicuro.
Puoi trovare questi e altri esempi di refactoring in questo rigoroso codelab CSP:
Passaggio 4 (facoltativo): aggiungi elementi di riserva per supportare le versioni precedenti del browser
Se hai bisogno di supportare versioni precedenti del browser:
- L'utilizzo di
strict-dynamic
richiede l'aggiunta dihttps:
come riserva per le versioni precedenti di Safari. In questo caso:- Tutti i browser che supportano
strict-dynamic
ignorano il metodo di riservahttps:
, pertanto ciò non ridurrà l'efficacia del criterio. - Nei browser precedenti, gli script provenienti da origini esterne possono essere caricati solo se provengono da
un'origine HTTPS. Questa soluzione è meno sicura di un CSP rigido, ma impedisce comunque alcune cause XSS comuni, come l'iniezione di URI
javascript:
.
- Tutti i browser che supportano
- Per garantire la compatibilità con versioni del browser molto vecchie (più di 4 anni), puoi aggiungere
unsafe-inline
come metodo di riserva. Tutti i browser recenti ignoranounsafe-inline
se è presente un hash o un nonce CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Passaggio 5: esegui il deployment di CSP
Dopo aver confermato che il CSP non blocca alcun script legittimo nell'ambiente di sviluppo locale, puoi eseguirne il deployment nella gestione temporanea e poi nell'ambiente di produzione:
- (Facoltativo) Esegui il deployment di CSP in modalità di solo report utilizzando l'intestazione
Content-Security-Policy-Report-Only
. La modalità di solo report è utile per testare una modifica potenzialmente efficace come un nuovo CSP in produzione prima di iniziare ad applicare limitazioni CSP. Nella modalità di solo report, il CSP non influisce sul comportamento dell'app, ma il browser genera comunque report su errori e violazioni della console quando rileva pattern incompatibili con il CSP, consentendoti di capire cosa non funziona per gli utenti finali. Per ulteriori informazioni, consulta API di reporting. - Quando hai la certezza che il CSP non interromperà il tuo sito per gli utenti finali, esegui il deployment del CSP utilizzando l'intestazione della risposta
Content-Security-Policy
. Ti consigliamo di impostare il CSP utilizzando un'intestazione HTTP lato server perché è più sicura di un tag<meta>
. Dopo aver completato questo passaggio, il CSP inizia a proteggere la tua app da XSS.
Limitazioni
Un CSP rigoroso fornisce in genere un forte livello aggiuntivo di sicurezza che aiuta a mitigare l'XSS. Nella maggior parte dei casi, CSP riduce in modo significativo la superficie di attacco, rifiutando pattern pericolosi come gli URI javascript:
. Tuttavia, a seconda del tipo di CSP che stai utilizzando (nonce, hash, con o senza 'strict-dynamic'
), in alcuni casi anche CSP non protegge la tua app:
- Se non esegui il nonce di uno script, ma c'è un'iniezione direttamente nel corpo o nel
parametro
src
di quell'elemento<script>
. - In caso di iniezioni nelle posizioni degli script creati dinamicamente (
document.createElement('script')
), anche nelle funzioni di libreria che creano nodi DOMscript
in base ai valori dei loro argomenti. Ciò include alcune API comuni, come.html()
di jQuery, nonché.get()
e.post()
in jQuery < 3.0. - Se esistono iniezioni di modelli nelle vecchie applicazioni AngularJS. Un utente malintenzionato che può inserire un modello AngularJS può utilizzarlo per eseguire codice JavaScript arbitrario.
- Se il criterio contiene
'unsafe-eval'
, iniezioni ineval()
,setTimeout()
e alcune altre API utilizzate raramente.
Sviluppatori e tecnici della sicurezza dovrebbero prestare particolare attenzione a questi pattern durante le revisioni del codice e i controlli di sicurezza. Puoi trovare maggiori dettagli su questi casi in Content Security Policy: A Successful Mess Between Hardening and Mitigation.
Per approfondire
- Il CSP è morto, Lunga vita a CSP sull'insicurezza delle whitelist e il futuro delle norme sulla sicurezza dei contenuti
- Valutatore CSP
- Conferenza LocoMoco: Content Security Policy - Un disastro efficace tra rafforzamento e mitigazione
- Google I/O talk: protezione delle app web con le funzionalità moderne della piattaforma