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

Lukas Weichselbaum
Lukas Weichselbaum

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Il cross-site scripting (XSS), ovvero la possibilità di inserire script dannosi in un'app web, è una delle maggiori vulnerabilità della sicurezza web da oltre un decennio.

Content Security Policy (CSP) è un livello di sicurezza aggiuntivo che contribuisce a mitigare gli attacchi XSS. Per configurare un CSP, aggiungi l'intestazione HTTP Content-Security-Policy a una pagina web e imposta i valori che controllano le risorse che lo user agent può caricare per quella pagina.

Questa pagina spiega come utilizzare una CSP basata su nonce o hash per mitigare gli attacchi XSS, anziché le CSP basate su elenchi consentiti di host di uso comune, che spesso lasciano la pagina esposta agli attacchi XSS perché possono essere aggirate nella maggior parte delle configurazioni.

Termine chiave: un nonce è un numero casuale utilizzato una sola 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> in linea come attendibile.

Una Content Security Policy basata su nonce o hash viene spesso chiamata CSP rigida. Quando un'applicazione utilizza una CSP rigorosa, gli autori di attacchi che trovano difetti di iniezione HTML in genere non possono utilizzarli per forzare il browser a eseguire script dannosi in un documento vulnerabile. Questo perché la CSP rigida consente solo script con hash o script con il valore nonce corretto generato sul server, quindi gli autori di attacchi non possono eseguire lo script senza conoscere il nonce corretto per una determinata risposta.

Perché dovresti utilizzare una CSP rigorosa?

Se il tuo sito ha già una CSP simile a script-src www.googleapis.com, probabilmente non è efficace contro gli attacchi cross-site. Questo tipo di CSP è chiamato CSP con lista consentita. Richiedono molta personalizzazione e possono essere aggirati dagli autori degli attacchi.

Le CSP rigide basate su nonce o hash crittografici evitano questi problemi.

Struttura CSP rigida

Una Content Security Policy di base rigorosa utilizza una delle seguenti intestazioni della risposta HTTP:

CSP rigida basata su nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';
Come funziona una CSP rigida basata su nonce.

CSP rigoroso basato su hash

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none&#39;;
  base-uri 'none';

Le seguenti proprietà rendono una CSP come questa "rigida" e quindi sicura:

  • Utilizza nonce 'nonce-{RANDOM}' o hash 'sha256-{HASHED_INLINE_SCRIPT}' per indicare di quali tag <script> lo sviluppatore del sito si fida per l'esecuzione nel browser dell'utente.
  • Imposta 'strict-dynamic' per ridurre lo sforzo di deployment di una CSP basata su nonce o hash consentendo automaticamente l'esecuzione di script creati da uno script attendibile. Inoltre, sblocca l'utilizzo della maggior parte dei widget e delle librerie JavaScript di terze parti.
  • Non si basa su elenchi consentiti di URL, quindi non soffre di bypass CSP comuni.
  • Blocca gli script incorporati non attendibili, come i gestori di eventi incorporati o gli URI javascript:.
  • Limita object-src per disattivare plug-in pericolosi come Flash.
  • Limita base-uri per bloccare l'inserimento dei tag <base>. Ciò impedisce agli autori di attacchi di modificare le posizioni degli script caricati da URL relativi.

Adotta un CSP rigoroso

Per adottare una CSP rigorosa, devi:

  1. Decidi se la tua applicazione deve impostare una CSP basata su nonce o hash.
  2. Copia il CSP dalla sezione Struttura CSP rigorosa e impostalo come intestazione della risposta nell'applicazione.
  3. Esegui il refactoring dei modelli HTML e del codice lato client per rimuovere i pattern incompatibili con CSP.
  4. Esegui il deployment del CSP.

Puoi utilizzare il controllo Best Practices di Lighthouse (versione 7.3.0 e successive con il flag --preset=experimental) durante questa procedura per verificare se il tuo sito ha una CSP e se è abbastanza rigorosa da essere efficace contro XSS.

Avviso del report Lighthouse
  che indica che non è stata trovata alcuna CSP in modalità di applicazione forzata.
Se il tuo sito non ha una CSP, Lighthouse mostra questo avviso.

Passaggio 1: decidi se hai bisogno di una CSP basata su nonce o hash

Ecco come funzionano i due tipi di CSP rigorosa:

CSP basata su 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 di script nella pagina. Un 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 è intuibile e viene generato di nuovo in fase di runtime per ogni risposta.

Utilizza una CSP basata su nonce per le pagine HTML di cui è stato eseguito il rendering sul server. Per queste pagine, puoi creare un nuovo numero casuale per ogni risposta.

CSP basato su hash

Per una CSP basata su hash, l'hash di ogni tag di script incorporato viene aggiunto alla CSP. Ogni script ha un hash diverso. Un malintenzionato non può includere o eseguire uno script dannoso nella tua pagina, perché l'hash di questo script dovrebbe essere presente nella CSP per poter essere eseguito.

Utilizza una CSP basata su hash per le pagine HTML pubblicate in modo statico o per le pagine che devono essere memorizzate nella cache. Ad esempio, puoi utilizzare una CSP basata su hash per applicazioni web a pagina singola create con framework come Angular, React o altri, che vengono pubblicate staticamente senza rendering lato server.

Passaggio 2: imposta una CSP rigorosa e prepara gli script

Quando imposti un CSP, hai alcune opzioni:

  • Modalità solo report (Content-Security-Policy-Report-Only) o modalità di applicazione (Content-Security-Policy). In modalità solo report, la CSP non bloccherà ancora le risorse, quindi nulla sul tuo sito si interrompe, ma puoi visualizzare gli errori e ricevere report per qualsiasi elemento che sarebbe stato bloccato. A livello locale, quando imposti la CSP, questo non ha molta importanza, perché entrambe le modalità mostrano gli errori nella console del browser. Se non altro, la modalità di applicazione può aiutarti a trovare le risorse bloccate dalla bozza della CSP, perché il blocco di una risorsa può rendere la pagina danneggiata. La modalità solo report diventa più utile in un secondo momento della procedura (vedi Passaggio 5).
  • Tag <meta> di intestazione o HTML. Per lo sviluppo locale, un tag <meta> può essere più comodo per modificare la CSP e vedere rapidamente come influisce sul tuo sito. Tuttavia:
    • In un secondo momento, quando esegui il deployment di CSP in produzione, ti consigliamo di impostarlo come intestazione HTTP.
    • Se vuoi impostare la CSP in modalità solo report, devi impostarla come intestazione, perché i metatag CSP non supportano la modalità solo report.

Opzione A: CSP basata su nonce

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';

Generare un nonce per CSP

Un nonce è un numero casuale utilizzato una sola volta per caricamento pagina. Una CSP basata su nonce può mitigare gli attacchi XSS solo se gli autori degli attacchi non riescono a indovinare il valore nonce. Un nonce CSP deve:

  • Un valore casuale crittograficamente sicuro (idealmente di lunghezza pari o superiore a 128 bit)
  • Generato di recente per ogni risposta
  • Codificato in 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(&>quot;Content-Security-Policy", csp);

  // Every script tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

Aggiungere un attributo nonce agli elementi <script>

Con una CSP basata su nonce, ogni elemento <script> deve avere un attributo nonce che corrisponda 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 la 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}'.

Caricare gli script di origine in modo dinamico

Puoi caricare dinamicamente script di terze parti utilizzando uno script incorporato.

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.hea<d.appen>dChild(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 il numero di hash, puoi unire tutti gli script incorporati in un unico script. Per vedere un esempio pratico, consulta questo esempio e il relativo codice.
Bloccato da CSP
<script src="https://example.org/fo><o.js&qu>o<t;/script
script src="https://exam><ple.org>/bar.js"/script
CSP blocca questi script perché non sono stati aggiunti dinamicamente e non hanno l'attributo integrity che corrisponde a un'origine consentita.

Considerazioni sul caricamento degli script

L'esempio di script incorporato aggiunge s.async = falseper garantire che foo venga eseguito prima di bar, anche se bar viene caricato per primo. In questo snippet, s.async = false non blocca il parser durante il caricamento degli script, perché gli script 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 quanto segue:

  • Uno o entrambi gli script potrebbero essere eseguiti prima che il download del documento sia completato. Se vuoi che il documento sia pronto per l'esecuzione degli script, attendi l'evento DOMContentLoaded prima di aggiungere gli script. Se ciò causa un problema di prestazioni perché gli script non iniziano il download abbastanza presto, utilizza i tag preload all'inizio della pagina.
  • defer = true non fa nulla. Se hai bisogno di questo comportamento, esegui lo script manualmente quando è necessario.

Passaggio 3: rifattorizza i modelli HTML e il codice lato client

I gestori di eventi incorporati (come onclick="…", onerror="…") e gli URI JavaScript (<a href="javascript:…">) possono essere utilizzati per eseguire script. Ciò significa che un malintenzionato che trova un bug XSS può inserire questo tipo di HTML ed eseguire codice JavaScript dannoso. Una CSP basata su nonce o hash vieta l'utilizzo di questo tipo di markup. Se il tuo sito utilizza uno di questi pattern, dovrai eseguirne il refactoring in alternative più sicure.

Se hai abilitato CSP nel passaggio precedente, potrai visualizzare le violazioni di CSP nella console ogni volta che CSP blocca un pattern incompatibile.

Report sulle violazioni del CSP nella console per sviluppatori di Chrome.
Errori della console per il codice bloccato.

Nella maggior parte dei casi, la correzione è semplice:

Rifattorizzare i gestori di eventi incorporati

Consentito da CSP
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
  document.getElementById('things').addEventL<istener>('click', doThings);
/script
La CSP consente i gestori di eventi registrati tramite JavaScript.
Bloccato da CSP
<span onclick="doThing>s();&quo<t;A t>hing./span
CSP blocca i gestori di eventi incorporati.

Refactoring degli URI javascript:

Consentito da CSP
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
  document.getElementById('foo').addEventList<ener(&#>39;click', linkClicked);
/script
La CSP consente i gestori di eventi registrati tramite JavaScript.
Bloccato da CSP
<a href="javascript:linkClick>ed(<)&>quot;foo/a
CSP blocca gli URI JavaScript.

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 rigoroso basato su nonce, ma devi utilizzare la parola chiave CSP 'unsafe-eval', che rende il criterio leggermente meno sicuro.

Puoi trovare questi e altri esempi di refactoring in questo codelab CSP rigido:

Passaggio 4 (facoltativo): aggiungi fallback per supportare le versioni precedenti del browser

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Se devi supportare versioni precedenti del browser:

  • L'utilizzo di strict-dynamic richiede l'aggiunta di https: come fallback per le versioni precedenti di Safari. Quando esegui questa operazione:
    • Tutti i browser che supportano strict-dynamic ignorano il fallback https:, quindi questo non ridurrà la validità delle norme.
    • Nei browser meno recenti, gli script di origine esterna possono essere caricati solo se provengono da un'origine HTTPS. Questa opzione è meno sicura di una CSP rigorosa, ma impedisce comunque alcune cause comuni di XSS, come le iniezioni di URI javascript:.
  • Per garantire la compatibilità con versioni del browser molto precedenti (più di 4 anni), puoi aggiungere unsafe-inline come fallback. Tutti i browser recenti ignorano unsafe-inline se è presente un nonce o un hash CSP.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 9;none';
  base-uri 'none';

Passaggio 5: esegui il deployment del CSP

Dopo aver verificato che la CSP non blocca script legittimi nell'ambiente di sviluppo locale, puoi implementarla in staging e poi nell'ambiente di produzione:

  1. (Facoltativo) Implementa il CSP in modalità solo report utilizzando l'intestazione Content-Security-Policy-Report-Only. La modalità solo report è utile per testare una modifica potenzialmente distruttiva, ad esempio una nuova CSP in produzione, prima di iniziare a applicare le restrizioni CSP. In modalità solo report, la CSP non influisce sul comportamento dell'app, ma il browser genera comunque errori della console e report sulle violazioni quando rileva pattern incompatibili con la CSP, in modo da poter vedere cosa sarebbe andato in errore per gli utenti finali. Per saperne di più, consulta l'API Reporting.
  2. Quando hai la certezza che la tua CSP non danneggerà il tuo sito per gli utenti finali, implementa la CSP utilizzando l'intestazione della risposta Content-Security-Policy. Ti consigliamo di impostare la 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

Una CSP rigorosa in genere fornisce un livello aggiuntivo di sicurezza elevato che contribuisce a mitigare gli attacchi XSS. Nella maggior parte dei casi, CSP riduce notevolmente la superficie di attacco rifiutando pattern pericolosi come gli URI javascript:. Tuttavia, in base al tipo di CSP che utilizzi (nonce, hash, con o senza 'strict-dynamic'), esistono casi in cui la CSP non protegge altrettanto bene la tua app:

  • Se aggiungi un nonce a uno script, ma viene eseguito un inserimento direttamente nel corpo o nel parametro src dell'elemento <script>.
  • Se sono presenti iniezioni nelle posizioni degli script creati dinamicamente (document.createElement('script')), incluse le funzioni di libreria che creano nodi DOM script in base ai valori dei relativi argomenti. Ciò include alcune API comuni come .html() di jQuery, nonché .get() e .post() in jQuery < 3.0.
  • Se sono presenti injection di modelli in vecchie applicazioni AngularJS. Un malintenzionato che può eseguire l'injection in un modello AngularJS può utilizzarlo per eseguire codice JavaScript arbitrario.
  • Se la policy contiene 'unsafe-eval', iniezioni in eval(), setTimeout() e alcune altre API utilizzate raramente.

Gli sviluppatori e gli ingegneri della sicurezza devono prestare particolare attenzione a questi pattern durante le revisioni del codice e gli audit di sicurezza. Puoi trovare maggiori dettagli su questi casi in Content Security Policy: A Successful Mess Between Hardening and Mitigation.

Per approfondire