Security Headers da F ad A: Content Security Policy

Uno dei primi video che ho pubblicato sul canale Rev3rse Security parlava di Content Security Policy. Mi sono accorto però che avevo affrontato l'argomento in maniera troppo semplicistica e superficiale. Ho deciso quindi di riprendere l'argomento in una serie di video e post sul blog che parleranno di security headers, non solo per quanto riguarda CSP ma per tutti i response headers che incrementano la sicurezza dell'utente.

Molto probabilmente saranno 4 puntate:

Puntata 1:
Content Security Policy e CSP Report-Only

Puntata 2:
Cookie Flags, Cookie Prefix, SameSite Cookie

Puntata 3:
Referrer-Policy e Feature-Policy

Puntata 4:
HTTP Strict Transport Security, X-Frame-Options e X-Content-Type-Options

Prima di partire, vi consiglio di visitare il blog di Scott Helme che ha affrontato magistralmente questo tema, e rappresenta una risorsa estremamente completa e aggiornata.

Ogni puntata sarà divisa tra presentazione e pratica, implementeremo ogni security header sopra elencati su questo sito (blog.reverse.it) usando Nginx. Per verificare lo stato di configurazione dei security header vi consiglio di utilizzare https://securityheaders.com che fornisce un punteggio (da F ad A+) e molti consigli su come configurare al meglio i vari header. Durante questa serie partiremo da F e cercheremo di arrivare a guadagnare una A. Voglio chiamare la serie di video "Security Headers: da F ad A" anche se ancora non mi convince al 100% (se avete suggerimenti sono bene accetti).

Una buona alternativa potrebbe essere utilizzare ZAP Baseline Scan, che è uno script presente nell'immagine docker di OWASP ZAP che esegue lo spider ZAP verso uno specifico target. Genera autonomamente un file di configurazione e può esportare report in diversi formati. Un esempio:

docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-weekly \
    zap-baseline.py \
    -t https://blog.rev3rse.it \
    -g blog.rev3rse.it.conf \
    -r report.html

Per maggiori informazioni: https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan

Stato attuale blog.rev3rse.it:

risultato ottenuto su securityheaders.com per blog.rev3rse.it

Partiamo

Anche il più semplice dei siti web, per poter funzionare correttamente, richiede diversi contenuti: CSS, JavaScript, Fonts, Immagini, ecc... Lo user agent non è sempre in grado di capire se uno di questi contenuti rappresenta un problema per la sicurezza dell'utente. Per questo motivo, attivare contromisure su vulnerabilità che possono sembrare banali (come stored o reflected XSS) può risultare molto complicato.

Content Security Policy (CSP) è un response header ideato per identificare quali risorse il browser può caricare, eseguire o contattare, definendo una serie di policy che costituiscono una vera e propria whitelist. CSP rappresenta una valida contromisura al XSS ed è ampiamente supportato dai browser più utilizzati.

Abbiamo detto che CSP è un response header, quindi negli header di ogni risposta HTTP dovrà essere presente qualcosa del genere:

Content-Security-Policy: default-src 'self'; script-src 'self' https://*.google.com

La sintassi è molto semplice ed è una successione di parametri e risorse.

<parametro> <risorsa-1> <risorsa-2>; <parametro> <risorsa-3> ecc...

Questa semplice policy autorizza il browser a caricare o eseguire JavaScript solo se provenienti da se stesso (origin, non subdomain) o da https://*.google.com. Per esempio, se nella nostra pagina HTML fossero presenti i tag:

<script src=“/self”>
<script src=“https://example.com/evil.js”>

il risultato sarebbe il seguente:

evil.js bloccato dal CSP

Si potrebbe pensare che il concetto alla base sia molto semplice: una whitelist. La realtà è che CSP permette configurazioni molto più complesse, modalità diverse per autorizzare contenuti che non sempre si basano sulla provenienza e addirittura può autorizzare alcuni comportamenti del browser come blocco popup o blocco plug-in.

Quali ambiti possiamo proteggere oltre a script-src?

default-src: default policy in caso non sia definita una delle seguenti (fallback)
script-src: definisce quale scripts e da dove può essere caricato
object-src: sorgenti autorizzate per i plugins
style-src: sorgenti autorizzate per CSS
img-src: sorgenti autorizzate per le immagini
media-src: sorgenti autorizzate per video e audio
frame-src: da dove, il sito, può essere incluso in un iframe
font-src: sorgenti autorizzate per i font
connect-src: URI autorizzati ad essere contattati da script
form-action: URI autorizzati nel parametro action di un HTML form
sandbox: specifica alcune restrizioni come blocco popups, blocco plugin, ecc…
script-nonce: accetta una risorsa JavaScript solo se presente il parametro nonce
reflected-xss: attiva o disattiva contromisure a reflected XSS
report-uri: URI a cui CSP invia ogni violazione delle policy

CSP permette di utilizzare keyword che rappresentano determinate sorgenti o caratteristiche della risorsa:

'none' blocca il tipo di risorsa (es. script-src 'none' blocca qualsiasi JavaScript).

'self' rappresenta l’origin (ma non eventuali subdomains).

'unsafe-inline' abilita l’utilizzo di inline JS e CSS (es. onclick="javascript:foo()").

'unsafe-eval' abilita l’utilizzo di funzioni come eval() interpretazione di codice da stringa.

CSP Report Only

Il principale problema quando si implementa CSP, è che non si è mai abbastanza consapevoli di tutto ciò che il browser degli utenti deve caricare ed eseguire per visualizzare correttamente il nostro sito o applicazione. Per questo motivo, è praticamente impossibile applicare CSP su un sito già in produzione senza causare almeno un blocco dovuto a un falso positivo. La soluzione è utilizzare la versione "report only" del CSP che, con uno sforzo incredibile di fantasia e creatività, si chiama Content-Security-Policy-Report-Only.

Esegue praticamente tutti i controlli di CSP ma si limita a notificare al browser che il contenuto che ha appena caricato sarebbe stato bloccato. Così facendo possiamo confezionare CSP senza correre il rischio di rompere la visualizzazione del sito.

Report URI

Uno dei parametri di CSP è report-uri. Questo parametro ci permette di specificare un URI (interno o esterno) a cui il browser invierà ogni violazione, specificando la pagina in cui è avvenuta e l'elemento che ha causato il blocco (o il report nel caso di CSP Report Only). Riprendendo l'esempio di CSP fatto prima, la sintassi è simile a tutti gli altri parametri:

Content-Security-Policy: script-src 'self' https://*.google.com; report-uri /csp-report

Il browser invia una request POST con Content Type application/csp-report contenente un body JSON simile al seguente:

{
  "csp-report": {
    "document-uri": "http://example.com/signup.html",
    "referrer": "",
    "blocked-uri": "http://example.com/css/style.css",
    "violated-directive": "style-src cdn.example.com",
    "original-policy": "default-src 'none'; style-src cdn.example.com; report-uri /_/csp-reports",
    "disposition": "report"
  }
}

esistono servizi gratuiti che permettono all'amministratore di raccogliere queste segnalazioni e analizzarle. Uno tra i più famosi è stato creato proprio da Scott Helme e si chiama Report URI (la fantasia per la scelta dei nomi è una caratteristica molto presente in questo campo).

State pensado a ciò che penso io.

Ci sono due aspetti interessanti in tutto questo. Il primo è che se è il browser dell'utente a mandare la violazione verso il report-uri, basterebbe un piccolo script che replica la stessa request POST di una violazione per essere inondati di fake CSP logs. Il punto è che report-uri è uno strumento che permette all'amministratore di risolvere eventuali falsi positivi su segnalazione degli utenti. Fornendo l'ora del blocco e l'IP si hanno buone probabilità di recuperare il log e intervenire sulla policy.

Secondo aspetto divertente: Un utente che naviga sul vostro sito ha un Adware. Un Adware è un software che visualizza messaggi pubblicitari nel browser (spesso anche all'interno delle pagine che visita) iniettando codice HTML e JavaScript, il più delle volte all'insaputa dell'utente. Ogni volta che un utente infetto da un Adware visiterà il vostro sito, riceverà il response header CSP che avete faticosamente confezionato. L'Adware tenterà di iniettare codice HTML e JavaScript ma, questa volta, il browser dell'utente si rifiuterà di caricarlo ed eseguirlo ("not today" cit.). A questo punto il browser invierà la violazione all'URL specificato nel report-uri in cui dirà di aver bloccato la risorsa "x" sulla pagina "y".

Tratto da una storia vera: scrivo questo perché vedersi arrivare un report di violazione sulla pagina "/login" per aver bloccato il caricamento dello script JavaScript https://evil.adv.xyz/dildos-adv.js potrebbe causare qualche secondo di panico.

Per maggiori informazioni su come intercettare Adware usando CSP, potete leggere l'aricolo che ho pubblicato su secjuice qualche tempo fa, e che parla proprio di questo:

https://www.secjuice.com/csp-adware-malware-detection/

CSP su blog.rev3rse.it

Iniziamo implementando CSP su questo blog: