Perché è comune inserire i token di prevenzione CSRF nei cookie?


284

Sto cercando di capire l'intero problema con CSRF e modi appropriati per prevenirlo. (Risorse che ho letto, compreso e in accordo con: OWASP CSRF Prevenzione CHeat Sheet , Domande su CSRF .)

Da quanto ho capito, la vulnerabilità intorno a CSRF è introdotta dal presupposto che (dal punto di vista del server web) un cookie di sessione valido in una richiesta HTTP in arrivo riflette i desideri di un utente autenticato. Ma tutti i cookie per il dominio di origine sono magicamente collegati alla richiesta dal browser, quindi in realtà tutto il server può dedurre dalla presenza di un cookie di sessione valido in una richiesta è che la richiesta proviene da un browser che ha una sessione autenticata; non può assumere più nulla del codicein esecuzione in quel browser o se riflette realmente i desideri degli utenti. Il modo per evitarlo è includere ulteriori informazioni di autenticazione (il "token CSRF") nella richiesta, trasportate in qualche modo diverso dalla gestione automatica dei cookie del browser. A grandi linee, quindi, il cookie di sessione autentica l'utente / browser e il token CSRF autentica il codice in esecuzione nel browser.

Quindi, in breve, se si utilizza un cookie di sessione per autenticare gli utenti dell'applicazione Web, è inoltre necessario aggiungere un token CSRF a ciascuna risposta e richiedere un token CSRF corrispondente in ciascuna richiesta (mutante). Il token CSRF effettua quindi un ritorno di andata e ritorno dal server al browser al server, dimostrando al server che la pagina che effettua la richiesta è approvata da (generato da, anche) da quel server.

Alla mia domanda, che riguarda il metodo di trasporto specifico utilizzato per quel token CSRF su quel roundtrip.

Sembra comune (ad esempio in AngularJS , Django , Rails ) inviare il token CSRF dal server al client come cookie (ovvero in un'intestazione Set-Cookie), quindi avere JavaScript nel client estrarlo dal cookie e collegarlo come intestazione XSRF-TOKEN separata da rispedire al server.

(Un metodo alternativo è quello raccomandato ad es. Express , in cui il token CSRF generato dal server è incluso nel corpo della risposta tramite l'espansione del modello sul lato server, collegato direttamente al codice / markup che lo restituirà al server, ad es. come input di moduli nascosti. Quell'esempio è un modo più web-1.0 di fare le cose, ma generalizzerebbe bene a un client più pesante di JS.)

Perché è così comune utilizzare Set-Cookie come trasporto a valle per il token CSRF / perché è una buona idea? Immagino che gli autori di tutti questi framework abbiano considerato attentamente le loro opzioni e non abbiano sbagliato. Ma a prima vista, usare i cookie per aggirare ciò che è essenzialmente una limitazione di progettazione sui cookie sembra stupido. Infatti, se si utilizzavano i cookie come trasporto di andata e ritorno (Set-Cookie: intestazione a valle per il server per comunicare al browser il token CSRF e Cookie: intestazione a monte per il browser per restituirlo al server) reintrodurre la vulnerabilità che si stanno cercando di risolvere.

Mi rendo conto che i framework di cui sopra non utilizzano i cookie per l'intero roundtrip per il token CSRF; usano Set-Cookie a valle, poi qualcos'altro (ad es. un'intestazione X-CSRF-Token) a monte, e questo chiude la vulnerabilità. Ma anche usare Set-Cookie come trasporto a valle è potenzialmente fuorviante e pericoloso; il browser ora collegherà il token CSRF a ogni richiesta, comprese le richieste XSRF dannose; nella migliore delle ipotesi, ciò rende la richiesta più grande del necessario e, nel peggiore dei casi, un codice del server ben intenzionato ma mal orientato potrebbe effettivamente tentare di utilizzarlo, il che sarebbe davvero negativo. Inoltre, poiché il destinatario effettivo del token CSRF è Javascript lato client, ciò significa che questo cookie non può essere protetto solo con http.


È un'ottima domanda colpire il posto giusto.
kta,

Risposte:


263

Un buon motivo, che hai toccato in qualche modo, è che una volta ricevuto il cookie CSRF, è quindi disponibile per l'uso in tutta l'applicazione nello script client per l'uso in entrambi i moduli regolari e POST AJAX. Ciò avrà senso in un'applicazione pesante JavaScript come quella utilizzata da AngularJS (l'utilizzo di AngularJS non richiede che l'applicazione sia un'app a pagina singola, quindi sarebbe utile dove lo stato deve fluire tra diverse richieste di pagina in cui il valore CSRF normalmente non può persistere nel browser).

Considera i seguenti scenari e processi in un'applicazione tipica per alcuni pro e contro di ogni approccio che descrivi. Questi sono basati sul modello di token Synchronizer .

Richiedi l'approccio del corpo

  1. L'utente accede correttamente.
  2. Il server emette cookie di autenticazione.
  3. L'utente fa clic per passare a un modulo.
  4. Se non ancora generato per questa sessione, il server genera un token CSRF, lo memorizza sulla sessione dell'utente e lo emette in un campo nascosto.
  5. L'utente invia il modulo.
  6. Il server controlla che il campo nascosto corrisponda al token memorizzato della sessione.

vantaggi:

  • Semplice da implementare.
  • Funziona con AJAX.
  • Funziona con i moduli.
  • I cookie possono effettivamente essere solo HTTP .

svantaggi:

  • Tutti i moduli devono generare il campo nascosto in HTML.
  • Anche i POST AJAX devono includere il valore.
  • La pagina deve sapere in anticipo che richiede il token CSRF in modo da poterlo includere nel contenuto della pagina in modo che tutte le pagine debbano contenere il valore del token da qualche parte, il che potrebbe richiedere molto tempo per essere implementato per un sito di grandi dimensioni.

Intestazione HTTP personalizzata (downstream)

  1. L'utente accede correttamente.
  2. Il server emette cookie di autenticazione.
  3. L'utente fa clic per passare a un modulo.
  4. La pagina viene caricata nel browser, quindi viene effettuata una richiesta AJAX per recuperare il token CSRF.
  5. Il server genera un token CSRF (se non è già stato generato per la sessione), lo memorizza sulla sessione dell'utente e lo emette in un'intestazione.
  6. Modulo di invio utente (il token viene inviato tramite un campo nascosto).
  7. Il server controlla che il campo nascosto corrisponda al token memorizzato della sessione.

vantaggi:

  • Funziona con AJAX.
  • I cookie possono essere solo HTTP .

svantaggi:

  • Non funziona senza una richiesta AJAX per ottenere il valore dell'intestazione.
  • Tutti i moduli devono avere il valore aggiunto al suo HTML in modo dinamico.
  • Anche i POST AJAX devono includere il valore.
  • La pagina deve prima fare una richiesta AJAX per ottenere il token CSRF, quindi ogni volta significherà un viaggio di andata e ritorno extra.
  • Potrebbe anche aver semplicemente emesso il token sulla pagina che salverebbe la richiesta extra.

Intestazione HTTP personalizzata (upstream)

  1. L'utente accede correttamente.
  2. Il server emette cookie di autenticazione.
  3. L'utente fa clic per passare a un modulo.
  4. Se non ancora generato per questa sessione, il server genera un token CSRF, lo memorizza sulla sessione dell'utente e lo emette da qualche parte nel contenuto della pagina.
  5. L'utente invia il modulo tramite AJAX (il token viene inviato tramite intestazione).
  6. Il server controlla che l'intestazione personalizzata corrisponda al token memorizzato della sessione.

vantaggi:

  • Funziona con AJAX.
  • I cookie possono essere solo HTTP .

svantaggi:

  • Non funziona con i moduli.
  • Tutti i POST AJAX devono includere l'intestazione.

Intestazione HTTP personalizzata (upstream e downstream)

  1. L'utente accede correttamente.
  2. Il server emette cookie di autenticazione.
  3. L'utente fa clic per passare a un modulo.
  4. La pagina viene caricata nel browser, quindi viene effettuata una richiesta AJAX per recuperare il token CSRF.
  5. Il server genera un token CSRF (se non è già stato generato per la sessione), lo memorizza sulla sessione dell'utente e lo emette in un'intestazione.
  6. L'utente invia il modulo tramite AJAX (il token viene inviato tramite intestazione).
  7. Il server controlla che l'intestazione personalizzata corrisponda al token memorizzato della sessione.

vantaggi:

  • Funziona con AJAX.
  • I cookie possono essere solo HTTP .

svantaggi:

  • Non funziona con i moduli.
  • Tutti i POST AJAX devono includere anche il valore.
  • Per ottenere il token CRSF, la pagina deve prima fare una richiesta AJAX, quindi ogni volta significherà un viaggio di andata e ritorno extra.

Set-Cookie

  1. L'utente accede correttamente.
  2. Il server emette cookie di autenticazione.
  3. L'utente fa clic per passare a un modulo.
  4. Il server genera un token CSRF, lo memorizza nella sessione dell'utente e lo invia a un cookie.
  5. L'utente invia il modulo tramite AJAX o tramite modulo HTML.
  6. Il server controlla l'intestazione personalizzata (o il campo modulo nascosto) corrispondente al token memorizzato della sessione.
  7. Il cookie è disponibile nel browser per l'uso in ulteriori AJAX e richieste di moduli senza ulteriori richieste al server per recuperare il token CSRF.

vantaggi:

  • Semplice da implementare.
  • Funziona con AJAX.
  • Funziona con i moduli.
  • Non richiede necessariamente una richiesta AJAX per ottenere il valore del cookie. Qualsiasi richiesta HTTP può recuperarla e può essere aggiunta a tutti i moduli / richieste AJAX tramite JavaScript.
  • Una volta recuperato il token CSRF, poiché è memorizzato in un cookie, il valore può essere riutilizzato senza richieste aggiuntive.

svantaggi:

  • Tutti i moduli devono avere il valore aggiunto al suo HTML in modo dinamico.
  • Anche i POST AJAX devono includere il valore.
  • Il cookie verrà inviato per ogni richiesta (ovvero tutti i GET per immagini, CSS, JS, ecc., Che non sono coinvolti nel processo CSRF) aumentando la dimensione della richiesta.
  • Il cookie non può essere solo HTTP .

Quindi l'approccio con i cookie è abbastanza dinamico offrendo un modo semplice per recuperare il valore del cookie (qualsiasi richiesta HTTP) e usarlo (JS può aggiungere automaticamente il valore a qualsiasi modulo e può essere impiegato nelle richieste AJAX come intestazione o come valore del modulo). Una volta ricevuto il token CSRF per la sessione, non è necessario rigenerarlo poiché un utente malintenzionato che utilizza un exploit CSRF non ha alcun metodo per recuperare questo token. Se un utente malintenzionato tenta di leggere il token CSRF dell'utente in uno dei metodi sopra indicati, ciò sarà impedito dalla stessa politica di origine . Se un utente malintenzionato tenta di recuperare il lato server token CSRF (ad es. Tramitecurl) quindi questo token non sarà associato allo stesso account utente in quanto il cookie della sessione di autenticazione della vittima mancherà dalla richiesta (sarebbe l'attaccante - pertanto non sarà associato dal lato server alla sessione della vittima).

Oltre al modello di token Synchronizer c'è anche il doppio cookie di invioMetodo di prevenzione CSRF, che ovviamente utilizza i cookie per memorizzare un tipo di token CSRF. Questo è più facile da implementare in quanto non richiede alcun stato lato server per il token CSRF. Il token CSRF infatti potrebbe essere il cookie di autenticazione standard quando si utilizza questo metodo e questo valore viene inviato tramite i cookie come al solito con la richiesta, ma il valore viene anche ripetuto in un campo nascosto o in un'intestazione, di cui un utente malintenzionato non può replicare come non possono leggere il valore in primo luogo. Tuttavia, si consiglia di scegliere un altro cookie, diverso dal cookie di autenticazione, in modo tale che il cookie di autenticazione possa essere protetto marcando HttpOnly. Quindi questo è un altro motivo comune per cui dovresti trovare la prevenzione CSRF usando un metodo basato sui cookie.


7
Non sono sicuro di aver compreso come "La richiesta AJAX è stata fatta per recuperare il token CSRF" (passaggio 4 in entrambe le sezioni "intestazione personalizzata: downstream") può essere eseguito in modo sicuro; poiché questa è una richiesta separata, il server non sa da chi proviene; come fa a sapere che è sicuro divulgare il token CSRF? Mi sembra che se non riesci a ottenere il token dal caricamento della pagina iniziale, perdi (il che rende, sfortunatamente, l'intestazione della risposta a valle personalizzata un non avviatore).
Metamatt,

6
Perché il falsario non avrà il cookie di sessione. Potrebbero avere il proprio cookie di sessione, ma poiché il token CSRF è associato a una sessione, il loro token CSRF non corrisponderà a quello della vittima.
SilverlightFox,

32
Nella mia comprensione dell'attacco CSRF, il falsario ha il mio cookie di sessione. Bene, in realtà non riescono a vedere il cookie, ma hanno la possibilità di fornirlo nelle loro richieste false, perché le richieste provengono dal mio browser e il mio browser fornisce il mio cookie di sessione. Quindi dal punto di vista del server, il cookie di sessione da solo non può distinguere una richiesta legittima da una richiesta falsa. Questo è in realtà l'attacco che stiamo cercando di prevenire. A proposito, grazie per la pazienza dimostrata, soprattutto se sono confuso al riguardo.
Metamatt,

8
Hanno la capacità di fornire il cookie di autenticazione, ma non possono leggere la risposta che contiene il token CSRF.
SilverlightFox

8
@metamatt Ci scusiamo per il necro, ma lo farò per le persone che si aggirano. Secondo la mia comprensione, l'attaccante in genere non ha accesso alla risposta. CSRF viene utilizzato principalmente per causare effetti collaterali , piuttosto che raccogliere direttamente i dati. Ad esempio, uno script di attacco CSRF potrebbe forzare un utente privilegiato a intensificare i privilegi dell'attaccante, disabilitare un'impostazione di sicurezza o forzare un utente paypal connesso ad inviare un trasferimento a un indirizzo e-mail specifico. In nessuno di questi casi l'attaccante si preoccupa della risposta, che viene comunque inviata al browser della vittima; solo il risultato dell'attacco.
jonathanbruder,

61

L'uso di un cookie per fornire il token CSRF al client non consente un attacco riuscito poiché l'attaccante non è in grado di leggere il valore del cookie e quindi non può inserirlo nel punto in cui la convalida CSRF sul lato server lo richiede.

L'attaccante sarà in grado di inoltrare una richiesta al server con sia il cookie del token di autenticazione sia il cookie CSRF nelle intestazioni della richiesta. Ma il server non sta cercando il token CSRF come cookie nelle intestazioni della richiesta, sta cercando nel payload della richiesta. E anche se l'attaccante sa dove mettere il token CSRF nel payload, dovrebbe leggere il suo valore per inserirlo. Ma la politica di origine incrociata del browser impedisce la lettura di qualsiasi valore di cookie dal sito Web di destinazione.

La stessa logica non si applica al cookie del token di autenticazione, poiché il server lo prevede nelle intestazioni della richiesta e l'utente malintenzionato non deve fare nulla di speciale per inserirlo.


Sicuramente, tuttavia, un utente malintenzionato non deve prima leggere il cookie. Possono semplicemente inserire un'immagine sul sito compromesso con src='bank.com/transfer?to=hacker&amount=1000cui il browser richiederà, completo dei cookie associati per quel sito ( bank.com)?
developius

2
CSRF è per la convalida dell'utente sul lato client e non per la protezione del sito in genere da un compromesso sul lato server come suggerito.
Tongfa,

2
@developius l'invio del cookie non è sufficiente per soddisfare la protezione CSRF. Il cookie contiene il token csrf, come inviato dal server. Il client legittimo deve leggere il token csrf dal cookie e quindi passarlo nella richiesta da qualche parte, come un'intestazione o nel payload. La protezione CSRF verifica che il valore nel cookie corrisponda al valore nella richiesta, altrimenti la richiesta viene respinta. Pertanto, l'utente malintenzionato deve leggere il cookie.
Will M.

1
Questa risposta è stata molto puntuale alla domanda del poster originale ed è stata molto chiara. +1 Grazie.
java-addict301

@Tongfa - grazie, questo mi ha aiutato a capire meglio. Ho ragione a supporre che il token CSRF NON debba essere inserito nell'intestazione? deve essere da qualche parte nel corpo?
zerohedge,

10

La mia ipotesi migliore sulla risposta: considera queste 3 opzioni su come ottenere il token CSRF dal server al browser.

  1. Nel corpo della richiesta (non un'intestazione HTTP).
  2. In un'intestazione HTTP personalizzata, non Set-Cookie.
  3. Come cookie, in un'intestazione Set-Cookie.

Penso che il primo, il corpo della richiesta (mentre dimostrato dal tutorial di Express che ho collegato nella domanda ), non sia portatile per un'ampia varietà di situazioni; non tutti generano dinamicamente ogni risposta HTTP; dove si finisce per dover inserire il token nella risposta generata potrebbe variare notevolmente (in un input in forma nascosta; in un frammento di codice JS o in una variabile accessibile da un altro codice JS; forse anche in un URL sebbene ciò sembri generalmente un brutto posto per mettere i token CSRF). Quindi, sebbene realizzabile con un po 'di personalizzazione, il numero 1 è un luogo difficile in cui adottare un approccio a misura unica.

Il secondo, l'intestazione personalizzata, è attraente ma in realtà non funziona, perché mentre JS può ottenere le intestazioni per un XHR invocato, non può ottenere le intestazioni per la pagina da cui è caricato .

Ciò lascia il terzo, un cookie portato da un'intestazione Set-Cookie, come approccio facile da usare in tutte le situazioni (il server di chiunque sarà in grado di impostare le intestazioni dei cookie per richiesta e non importa che tipo di i dati sono nel corpo della richiesta). Quindi, nonostante i suoi lati negativi, è stato il metodo più semplice per implementare ampiamente i framework.


7
Potrei affermare l'ovvio, questo significa che i cookie non possono essere httponly corretti?
Fotone,

1
solo per richieste Ajax (dove JS deve conoscere il valore del cookie csrf per poterlo inviare nuovamente alla richiesta successiva nel secondo canale (come dati del modulo o intestazione)). Non c'è motivo per richiedere che il token csrf sia HttpOnly solo se il cookie della sessione è già HttpOnly (per proteggere contro XSS) poiché il token csrf non è prezioso da solo senza la sessione associata.
cowbert,

2

Oltre al cookie di sessione (che è un po 'standard), non voglio usare cookie extra.

Ho trovato una soluzione che funziona per me durante la creazione di un'applicazione Web a pagina singola (SPA), con molte richieste AJAX. Nota: sto usando Java lato server e lato client JQuery, ma niente di magico quindi penso che questo principio possa essere implementato in tutti i linguaggi di programmazione più diffusi.

La mia soluzione senza cookie extra è semplice:

Dalla parte del cliente

Conservare il token CSRF che viene restituito dal server dopo un accesso riuscito in una variabile globale (se si desidera utilizzare l'archiviazione Web anziché un globale, va bene ovviamente). Indicare a JQuery di fornire un'intestazione X-CSRF-TOKEN in ogni chiamata AJAX.

La pagina principale "indice" contiene questo frammento JavaScript:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Lato server

Al login successivo, crea un token CSRF casuale (e abbastanza lungo), memorizzalo nella sessione lato server e restituiscilo al client. Filtra determinate richieste (sensibili) in arrivo confrontando il valore dell'intestazione X-CSRF-TOKEN con il valore memorizzato nella sessione: devono corrispondere.

Le chiamate AJAX sensibili (dati modulo POST e dati JSON GET) e il filtro lato server che le cattura, si trovano in un percorso / dataservice / *. Le richieste di accesso non devono colpire il filtro, quindi si trovano su un altro percorso. Le richieste per HTML, CSS, JS e risorse immagine non si trovano anche nel percorso / dataservice / *, quindi non vengono filtrate. Questi non contengono nulla di segreto e non possono nuocere, quindi va bene.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   

Penso che vorresti CSRF su una richiesta di accesso. Sembra che tu stia utilizzando il token CSRF anche come token di sessione di accesso. Funziona anche per averli come token separati e quindi è possibile utilizzare CSRF su qualsiasi endpoint, indipendentemente dal fatto che l'utente abbia effettuato l'accesso o meno.
Tongfa,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.