Il cross-site scripting (XSS), ovvero la possibilità di iniettare script dannosi in un'app web, è una delle più grandi vulnerabilità della sicurezza web da oltre un decennio.
I criteri di sicurezza del contenuto (CSP)
rappresentano un ulteriore livello di sicurezza 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 l'user agent può caricare per quella pagina.
Questa pagina spiega come utilizzare un CSP basato su nonce o hash per mitigare gli attacchi XSS, anziché i CSP basati su liste consentite per gli host di uso comune che spesso lasciano la pagina esposta agli attacchi XSS perché possono essere aggiranti nella maggior parte delle configurazioni.
Termine chiave: un nonce è un numero casuale utilizzato una sola volta che puoi utilizzare 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.
Un criterio di sicurezza del contenuto basato su nonce o hash viene spesso chiamato CSP rigoroso. Quando un'applicazione utilizza un CSP rigoroso, gli aggressori che trovano difetti di attacco di tipo HTML solitamente non possono utilizzarli per forzare il browser a eseguire script dannosi in un documento vulnerabile. Questo perché il CSP rigoroso consente solo script sottomessi ad hashing o script con il valore nonce corretto generato sul server, quindi gli utenti malintenzionati non possono eseguire lo script senza conoscere il nonce corretto per una determinata risposta.
Perché dovresti utilizzare un CSP rigoroso?
Se il tuo sito ha già un CSP simile a script-src www.googleapis.com
,
probabilmente non è efficace contro gli attacchi cross-site. Questo tipo di CSP è chiamato
CSP della lista consentita. Richiedono molta personalizzazione e possono essere
aggirate dagli attaccanti.
I CSP rigidi basati su nonce o hash crittografici evitano questi problemi.
Struttura CSP rigorosa
Un criterio Content Security Policy rigoroso di base utilizza una delle seguenti intestazioni di risposta HTTP:
CSP rigoroso basato su nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
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 attendibili da eseguire nel browser dell'utente. - Imposta
'strict-dynamic'
per ridurre lo sforzo di implementare un CSP basato su nonce o hash consentendo automaticamente l'esecuzione di script creati da uno script attendibile. Inoltre, consente di sbloccare l'utilizzo della maggior parte dei widget e delle librerie JavaScript di terze parti. - Non si basa su liste consentite di URL, quindi non è soggetta ai bypass del CSP più comuni.
- Blocca gli script in linea non attendibili, come i gestori di eventi in linea o gli URI
javascript:
. - Limita
object-src
a disattivare plug-in pericolosi come Flash. - Limita
base-uri
a bloccare l'iniezione di tag<base>
. In questo modo, gli autori di attacchi non possono modificare le posizioni degli script caricati da 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 CSP rigida e impostalo come intestazione di risposta nell'applicazione.
- Esegui il refactoring dei modelli HTML e del codice lato client per rimuovere i pattern incompatibili con CSP.
- Esegui il deployment del CSP.
Puoi utilizzare il controllo delle best practice di Lighthouse (versione 7.3.0 e successive con il flag --preset=experimental
) durante questa procedura per verificare se il tuo sito dispone di un CSP e se è sufficientemente rigoroso per essere efficace contro gli attacchi XSS.
Passaggio 1: decidi se hai bisogno di un CSP basato su nonce o hash
Ecco come funzionano i due tipi di CSP rigorosi:
CSP basato su nonce
Con un CSP basato su nonce, generi un numero casuale in fase di esecuzione, lo includi nel tuo CSP e lo associ a ogni tag script nella pagina. Un malintenzionato non può includere o eseguire uno script dannoso nella tua pagina, perché deve indovinare il numero casuale corretto per lo script. Questo funziona solo se il numero non è deducibile e viene generato di nuovo in fase di esecuzione per ogni risposta.
Utilizza un CSP basato su 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 in linea viene aggiunto al CSP. Ogni script ha un hash diverso. Un malintenzionato non può includere o eseguire uno script dannoso nella tua pagina, perché l'hash dello script deve trovarsi nel tuo CSP per poter essere eseguito.
Utilizza un 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 CSP basato su hash per applicazioni web di una sola pagina create con framework come Angular, React o altri, che vengono pubblicate in modo statico senza il rendering lato server.
Passaggio 2: imposta un CSP rigoroso e prepara gli script
Quando configuri un CSP, hai a disposizione alcune opzioni:
- Modalità solo report (
Content-Security-Policy-Report-Only
) o modalità di applicazione (Content-Security-Policy
). In modalità solo report, il CSP non blocca ancora le risorse, quindi non si verificano interruzioni sul tuo sito, ma puoi vedere gli errori e ricevere report su tutto ciò che sarebbe stato bloccato. A livello locale, quando imposti il CSP, questo non ha molta importanza, perché entrambe le modalità mostrano gli errori nella console del browser. In ogni caso, la modalità di applicazione può aiutarti a trovare le risorse bloccate dalla bozza del CSP, perché il blocco di una risorsa può rendere la pagina non visibile. La modalità Solo report diventa più utile in un secondo momento della procedura (vedi Passaggio 5). - Tag
<meta>
intestazione o HTML. Per lo sviluppo locale, un tag<meta>
può essere più utile per modificare il CSP e vedere rapidamente l'impatto sul tuo sito. Tuttavia:- In seguito, quando esegui il deployment del CSP in produzione, ti consigliamo di impostarlo come un'intestazione HTTP.
- Se vuoi impostare il CSP in modalità solo report, dovrai impostarlo come riga di intestazione, perché i metatag CSP non supportano questa modalità.
Imposta la seguente intestazione di 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 il CSP
Un nonce è un numero casuale utilizzato una sola volta per ogni caricamento pagina. Un CSP basato su nonce può mitigare gli attacchi XSS solo se gli utenti malintenzionati non riescono a indovinare il valore del nonce. Un nonce CSP deve essere:
- Un valore casuale di crittografia sicura (idealmente di almeno 128 bit)
- Generato di nuovo 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 }); });
Aggiungere un attributo nonce
agli elementi <script>
Con un CSP basato su nonce, ogni elemento <script>
deve avere un attributo nonce
che corrisponda al valore nonce random 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 di 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 in linea, la sintassi è la seguente:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Caricare gli script di origine in modo dinamico
Puoi caricare script di terze parti in modo dinamico utilizzando uno script incorporato.
<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>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Considerazioni sul caricamento degli script
L'esempio di script in linea aggiunge s.async = false
per assicurarsi
che foo
venga eseguito prima di bar
, anche se
bar
viene caricato per primo. In questo snippet, s.async = false
non blocca l'interprete durante il caricamento degli script, perché gli script vengono
aggiunti dinamicamente. Il parser si interrompe solo durante l'esecuzione degli script, come accade per gli script async
. Tuttavia, con questo snippet,
tieni presente quanto segue:
-
Uno o entrambi gli script potrebbero essere eseguiti prima del completamento del download del documento. Se vuoi che il documento sia pronto al momento dell'esecuzione degli script, attendi l'evento
DOMContentLoaded
prima di aggiungerli. Se ciò causa un problema di prestazioni perché gli script non iniziano a essere scaricati abbastanza presto, utilizza i tag di precaricamento all'inizio della pagina. -
defer = true
non fa nulla. Se hai bisogno di questo comportamento, esegui lo script manualmente quando necessario.
Passaggio 3: esegui il refactoring dei modelli HTML e del codice lato client
I gestori di eventi in linea (ad es. 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ò iniettare 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 eseguirne il refactoring in alternative più sicure.
Se hai attivato il CSP nel passaggio precedente, potrai vedere le violazioni del CSP nella console ogni volta che il CSP blocca un pattern incompatibile.
Nella maggior parte dei casi, la soluzione è semplice:
Ristrutturare i gestori di eventi in linea
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>
<span onclick="doThings();">A thing.</span>
Ristrutturare gli URI javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Rimuovi eval()
dal codice 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 basato su nonce rigoroso, ma devi utilizzare la parola chiave CSP 'unsafe-eval'
, che rende il tuo criterio leggermente meno sicuro.
Puoi trovare questi e altri esempi di questo tipo di refactoring in questo codelab CSP rigoroso:
(Facoltativo) Passaggio 4: aggiungi soluzioni di riserva per supportare le versioni precedenti dei browser
Se devi supportare versioni precedenti del browser:
- L'utilizzo di
strict-dynamic
richiede l'aggiunta dihttps:
come opzione di riserva per le versioni precedenti di Safari. In questo modo:- Tutti i browser che supportano
strict-dynamic
ignorano il valore alternativohttps:
, pertanto l'efficacia del criterio non verrà ridotta. - Nei browser meno recenti, gli script di origine esterna possono essere caricati solo se provengono da un'origine HTTPS. Questo approccio è meno sicuro di un CSP rigoroso, ma impedisce comunque alcune cause comuni di XSS, come le iniezioni di URI
javascript:
.
- Tutti i browser che supportano
- Per garantire la compatibilità con versioni del browser molto vecchie (oltre 4 anni), puoi aggiungere
unsafe-inline
come opzione di riserva. Tutti i browser recenti ignoranounsafe-inline
se è presente un nonce o un hash CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Passaggio 5: esegui il deployment del CSP
Dopo aver verificato che il CSP non blocchi script legittimi nell'ambiente di sviluppo locale, puoi eseguire il deployment del CSP nell'ambiente di staging e poi nell'ambiente di produzione:
- (Facoltativo) Esegui il deployment del CSP in modalità solo report utilizzando l'intestazione
Content-Security-Policy-Report-Only
. La modalità Solo report è utile per testare una modifica potenzialmente dannosa, come un nuovo CSP in produzione, prima di iniziare a applicare le restrizioni del CSP. In modalità solo report, il CSP non influisce sul comportamento dell'app, ma il browser genera comunque errori della console e report sulle violazioni quando rileva pattern incompatibili con il CSP, in modo da poter vedere cosa non avrebbe funzionato per gli utenti finali. Per ulteriori informazioni, consulta l'API Reporting. - Quando hai la certezza che il tuo CSP non danneggerà il tuo sito per gli utenti finali,
implementalo utilizzando l'intestazione di risposta
Content-Security-Policy
. Ti consigliamo di impostare il CSP utilizzando un'intestazione HTTP lato server perché è più sicuro di un tag<meta>
. Dopo aver completato questo passaggio, il tuo CSP inizia a proteggere la tua app dagli attacchi XSS.
Limitazioni
In genere, un CSP rigoroso fornisce un ulteriore livello di sicurezza che contribuisce a mitigare gli attacchi XSS. Nella maggior parte dei casi, il 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 il CSP non protegge anche la tua app:
- Se inserisci un nonce in uno script, ma è presente un'iniezione direttamente nel corpo o nel parametro
src
dell'elemento<script>
. - Se sono presenti iniettazioni nelle posizioni degli script creati dinamicamente
(
document.createElement('script')
), incluse le funzioni della libreria che creano nodi DOMscript
in base ai valori dei relativi argomenti. Sono incluse alcune API comuni come.html()
di jQuery, nonché.get()
e.post()
in jQuery < 3.0. - Se sono presenti iniettamenti di modelli nelle vecchie applicazioni AngularJS. Un malintenzionato che può eseguire l'injection in un modello AngularJS può utilizzarlo per eseguire JavaScript arbitrario.
- Se il criterio contiene
'unsafe-eval'
, le iniezioni ineval()
,setTimeout()
e in alcune altre API raramente utilizzate.
Gli sviluppatori e gli ingegneri della sicurezza devono prestare particolare attenzione a questi schemi durante le revisioni del codice e i controlli di sicurezza. Puoi trovare ulteriori dettagli su queste situazioni in Content Security Policy: A Successful Mess Between Hardening and Mitigation.
Per approfondire
- CSP è morto, lunga vita a CSP! On the Insecurity of Whitelists and the Future of Content Security Policy
- Valutatore CSP
- LocoMoco Conference: Content Security Policy - A successful mess between hardening and mitigation
- Presentazione Google I/O: Sicurezza delle app web con funzionalità di piattaforme moderne