Mitiga il cross-site scripting (XSS) applicando un criterio di sicurezza del contenuto (CSP) rigoroso.

Lukas Weichselbaum
Lukas Weichselbaum

Supporto dei browser

  • 52
  • 79
  • 52
  • 15,4

Fonte

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';
Come funziona un CSP rigoroso basato su nonce.

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:

  1. Decidi se la tua applicazione deve impostare un CSP basato su nonce o hash.
  2. Copia il CSP dalla sezione Struttura di CSP restrittiva e impostalo come intestazione di risposta in tutta l'applicazione.
  3. Esegui il refactoring dei modelli HTML e del codice lato client per rimuovere i pattern incompatibili con CSP.
  4. 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.
Se il tuo sito non ha un CSP, Lighthouse mostra questo avviso.

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.

Opzione A: CSP non basato su norme

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:

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.

Opzione B: intestazione della risposta CSP basata su hash

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.

Un esempio di come incorporare gli script.
Consentito da CSP
<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.
Bloccata da CSP
<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.
Errori della console per codice bloccato.

Nella maggior parte dei casi, la soluzione è semplice:

Refactoring dei gestori di eventi incorporati

Consentito da CSP
<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.
Bloccata da CSP
<span onclick="doThings();">A thing.</span>
CSP blocca i gestori di eventi incorporati.

Esegui il refactoring degli URI di javascript:

Consentito da CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP consente i gestori di eventi registrati utilizzando JavaScript.
Bloccata da CSP
<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

Supporto dei browser

  • 52
  • 79
  • 52
  • 15,4

Fonte

Se hai bisogno di supportare versioni precedenti del browser:

  • L'utilizzo di strict-dynamic richiede l'aggiunta di https: come riserva per le versioni precedenti di Safari. In questo caso:
    • Tutti i browser che supportano strict-dynamic ignorano il metodo di riserva https:, 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:.
  • 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 ignorano unsafe-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:

  1. (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.
  2. 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 DOM script 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 in eval(), 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