Prevenzione di XSRF/CSRF in ASP.NET MVC e pagine Web

di Rick Anderson

La richiesta tra siti falsi (nota anche come XSRF o CSRF) è un attacco contro le applicazioni ospitate sul Web, in cui un sito Web dannoso può influenzare l'interazione tra un browser client e un sito Web considerato attendibile da tale browser. Questi attacchi sono resi possibili perché i Web browser invieranno automaticamente token di autenticazione con ogni richiesta a un sito Web. Un classico esempio è un cookie di autenticazione, ad esempio un ticket di autenticazione basata su form di ASP.NET. Tuttavia, i siti Web che usano qualsiasi meccanismo di autenticazione permanente (ad esempio Windows Autenticazione, Basic e così via) possono essere mirati da questi attacchi.

Un attacco XSRF è diverso da un attacco di phishing. Gli attacchi di phishing richiedono un'interazione da parte della vittima. In un attacco di phishing, un sito Web dannoso imita il sito Web di destinazione e la vittima viene ingannata per fornire informazioni riservate all'utente malintenzionato. In un attacco XSRF spesso non è necessaria alcuna interazione da parte della vittima. Invece, l'utente malintenzionato si basa sul browser inviando automaticamente tutti i cookie pertinenti al sito Web di destinazione.

Per altre informazioni, vedere Open Web Application Security Project(OWASP) XSRF.

Anatomia di un attacco

Per eseguire un attacco XSRF, prendere in considerazione un utente che vuole eseguire alcune transazioni bancarie online. Questo utente visita prima WoodgroveBank.com e accede, a quel punto l'intestazione della risposta conterrà il cookie di autenticazione:

HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }

Poiché il cookie di autenticazione è un cookie di sessione, il cookie verrà cancellato automaticamente dal browser all'uscita del processo del browser. Tuttavia, fino a quel momento, il browser includerà automaticamente il cookie con ogni richiesta di WoodgroveBank.com. L'utente vuole ora trasferire $1000 a un altro conto, quindi compila un modulo sul sito bancario e il browser effettua questa richiesta al server:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00

Poiché questa operazione ha un effetto collaterale (avvia una transazione monetaria), il sito bancario ha scelto di richiedere un HTTP POST per avviare questa operazione. Il server legge il token di autenticazione dalla richiesta, cerca il numero di conto dell'utente corrente, verifica che esistano fondi sufficienti e quindi avvia la transazione nell'account di destinazione.

Il suo online banking è completo, l'utente si allontana dal sito bancario e visita altre località sul web. Uno di questi siti, fabrikam.com, include il markup seguente in una pagina incorporata all'interno di un <iframe>:

<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
    <input type="hidden" name="toAcct" value="67890" />
    <input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
    document.getElementById('theForm').submit();
</script>

In questo modo il browser effettua questa richiesta:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00

L'utente malintenzionato sta sfruttando il fatto che l'utente potrebbe ancora avere un token di autenticazione valido per il sito Web di destinazione e sta usando un piccolo frammento di Javascript per fare in modo che il browser crei automaticamente un HTTP POST nel sito di destinazione. Se il token di autenticazione è ancora valido, il sito bancario avvierà un trasferimento di $ 250 nell'account scelto dall'utente malintenzionato.

Mitigazioni inefficaci

È interessante notare che nello scenario precedente, il fatto che WoodgroveBank.com è stato eseguito l'accesso tramite SSL e che un cookie di autenticazione solo SSL non fosse sufficiente per contrastare l'attacco. L'utente malintenzionato è in grado di specificare lo schema URI (https) nell'elemento <del modulo> e il browser continuerà a inviare cookie non scaduti al sito di destinazione purché tali cookie siano coerenti con lo schema URI della destinazione prevista.

Si potrebbe sostenere che l'utente non deve semplicemente visitare siti non attendibili, perché visitare solo siti attendibili aiuta a rimanere al sicuro online. C'è qualche verità a questo, ma purtroppo questo consiglio non è sempre pratico. Forse l'utente "considera attendibile" il sito di notizie locale ConsolidatedMessenger.com e va a visitare il sito, ma tale sito presenta una vulnerabilità XSS che consente a un utente malintenzionato di inserire lo stesso frammento di codice in esecuzione in fabrikam.com.

È possibile verificare che le richieste in ingresso abbiano un'intestazione Referer che fa riferimento al dominio. Questa operazione arresterà le richieste inviate involontariamente da un dominio di terze parti. Tuttavia, alcune persone disabilitano l'intestazione referer del browser per motivi di privacy e gli utenti malintenzionati possono talvolta eseguire lo spoofing dell'intestazione se la vittima ha installato un determinato software non sicuro. La verifica dell'intestazione Referer non è considerata un approccio sicuro per prevenire gli attacchi XSRF.

Mitigazioni XSRF di Web Stack Runtime

Il runtime di ASP.NET Web Stack usa una variante del modello di token di sincronizzazione per difendersi dagli attacchi XSRF. La forma generale del modello di token del programma di sincronizzazione è che due token anti-XSRF vengono inviati al server con ogni HTTP POST (oltre al token di autenticazione): un token come cookie e l'altro come valore del modulo. I valori del token generati dal runtime di ASP.NET non sono deterministici o prevedibili da un utente malintenzionato. Quando i token vengono inviati, il server consentirà alla richiesta di procedere solo se entrambi i token superano un controllo di confronto.

Il token di sessione di verifica della richiesta XSRF viene archiviato come cookie HTTP e contiene attualmente le informazioni seguenti nel payload:

  • Token di sicurezza costituito da un identificatore casuale a 128 bit.
    L'immagine seguente mostra il token di sessione di verifica della richiesta XSRF visualizzato con gli strumenti di sviluppo F12 di Internet Explorer: (Si noti che si tratta dell'implementazione corrente ed è soggetto, anche probabilmente, a cambiare).

Screenshot that shows the My A S P dot NET M V C Application Index page. The Network tab is open.

Il token del campo viene archiviato come e <input type="hidden" /> contiene le informazioni seguenti nel payload:

I payload dei token anti-XSRF vengono crittografati e firmati, quindi non è possibile visualizzare il nome utente quando si usano gli strumenti per esaminare i token. Quando l'applicazione Web ha come destinazione ASP.NET 4.0, i servizi di crittografia vengono forniti dalla routine MachineKey.Encode . Quando l'applicazione Web ha come destinazione ASP.NET 4.5 o versione successiva, i servizi di crittografia vengono forniti dalla routine MachineKey.Protect , che offre prestazioni migliori, estendibilità e sicurezza. Per altri dettagli, vedere i post di blog seguenti:

Generazione dei token

Per generare i token anti-XSRF, chiamare il metodo @Html.AntiForgeryToken da una visualizzazione MVC o @AntiForgery.GetHtml() da una pagina Razor. Il runtime eseguirà quindi i passaggi seguenti:

  1. Se la richiesta HTTP corrente contiene già un token di sessione anti-XSRF (il cookie anti-XSRF __RequestVerificationToken), il token di sicurezza viene estratto da esso. Se la richiesta HTTP non contiene un token di sessione anti-XSRF o se l'estrazione del token di sicurezza ha esito negativo, verrà generato un nuovo token anti-XSRF casuale.
  2. Un token di campo anti-XSRF viene generato usando il token di sicurezza del passaggio (1) precedente e l'identità dell'utente connesso corrente. Per altre informazioni sulla determinazione dell'identità dell'utente, vedere la sezione Scenari con supporto speciale di seguito. Inoltre, se è configurato un oggetto IAntiForgeryAdditionalDataProvider , il runtime chiamerà il metodo GetAdditionalData e includerà la stringa restituita nel token del campo. Per altre informazioni, vedere la sezione Configurazione ed estendibilità .
  3. Se un nuovo token anti-XSRF è stato generato nel passaggio (1), verrà creato un nuovo token di sessione per contenerlo e verrà aggiunto alla raccolta di cookie HTTP in uscita. Il token di campo del passaggio (2) verrà sottoposto a wrapping in un <input type="hidden" /> elemento e questo markup HTML sarà il valore restituito di Html.AntiForgeryToken() o AntiForgery.GetHtml().

Convalida dei token

Per convalidare i token anti-XSRF in ingresso, lo sviluppatore include un attributo ValidateAntiForgeryToken sull'azione o sul controller MVC oppure chiama @AntiForgery.Validate() dalla pagina Razor. Il runtime eseguirà i passaggi seguenti:

  1. Il token di sessione in ingresso e il token di campo vengono letti e il token anti-XSRF estratto da ognuno. I token anti-XSRF devono essere identici per ogni passaggio (2) nella routine di generazione.
  2. Se l'utente corrente è autenticato, il nome utente viene confrontato con il nome utente archiviato nel token del campo. I nomi utente devono corrispondere.
  3. Se è configurato un oggetto IAntiForgeryAdditionalDataProvider , il runtime chiama il metodo ValidateAdditionalData . Il metodo deve restituire il valore booleano true.

Se la convalida ha esito positivo, la richiesta può continuare. Se la convalida non riesce, il framework genererà un'eccezione HttpAntiForgeryException.

Condizioni di errore

A partire da The ASP.NET Web Stack Runtime v2, qualsiasi HttpAntiForgeryException generata durante la convalida conterrà informazioni dettagliate su ciò che è andato storto. Le condizioni di errore attualmente definite sono:

  • Il token di sessione o il token del modulo non è presente nella richiesta.
  • Il token di sessione o il token del modulo non è leggibile. La causa più probabile di questa è una farm che esegue versioni non corrispondenti di The ASP.NET Web Stack Runtime o una farm in cui l'elemento <machineKey> in Web.config differisce tra i computer. È possibile usare uno strumento come Fiddler per forzare questa eccezione manomettendo entrambi i token anti-XSRF.
  • Il token di sessione e il token del campo sono stati scambiati.
  • Il token di sessione e il token di campo contengono token di sicurezza non corrispondenti.
  • Il nome utente incorporato all'interno del token del campo non corrisponde al nome utente dell'utente connesso corrente.
  • Il metodo IAntiForgeryAdditionalDataProvider.ValidateAdditionalData ha restituito false.

Le funzionalità anti-XSRF possono anche eseguire controlli aggiuntivi durante la generazione o la convalida del token e gli errori durante questi controlli possono causare la generazione di eccezioni. Per altre informazioni, vedere le sezioni Autenticazione basata su attestazioni/WIF/ACS/Configurazioneed estendibilità .

Scenari con supporto speciale

Autenticazione anonima

Il sistema anti-XSRF contiene un supporto speciale per gli utenti anonimi, in cui "anonimo" viene definito come utente in cui la proprietà IIdentity.IsAuthenticated restituisce false. Gli scenari includono la protezione XSRF per la pagina di accesso (prima dell'autenticazione dell'utente) e gli schemi di autenticazione personalizzati in cui l'applicazione usa un meccanismo diverso da IIdentity per identificare gli utenti.

Per supportare questi scenari, tenere presente che i token di sessione e di campo vengono aggiunti da un token di sicurezza, ovvero un identificatore opaco generato in modo casuale a 128 bit. Questo token di sicurezza viene usato per tenere traccia della sessione di un singolo utente durante la navigazione nel sito, in modo da servire in modo efficace lo scopo di un identificatore anonimo. Una stringa vuota viene usata al posto del nome utente per le routine di generazione e convalida descritte in precedenza.

Autenticazione basata su attestazioni/WIF/ACS

In genere, le classi IIdentity incorporate in .NET Framework hanno la proprietà che IIdentity.Name è sufficiente per identificare in modo univoco un determinato utente all'interno di una determinata applicazione. Ad esempio, FormsIdentity.Name restituisce il nome utente archiviato nel database di appartenenza (univoco per tutte le applicazioni a seconda di tale database), WindowsIdentity.Name restituisce l'identità qualificata del dominio dell'utente e così via. Questi sistemi forniscono non solo l'autenticazione; identificano anche gli utenti in un'applicazione.

L'autenticazione basata sulle attestazioni, d'altra parte, non richiede necessariamente l'identificazione di un determinato utente. I tipi ClaimsPrincipal e ClaimsIdentity sono invece associati a un set di istanze attestazioni , in cui le singole attestazioni potrebbero essere "è di 18+ anni di età" o "è un amministratore" per qualsiasi altra cosa. Poiché l'utente non è necessariamente stato identificato, il runtime non può usare la proprietà ClaimsIdentity.Name come identificatore univoco per questo particolare utente. Il team ha visto esempi reali in cui ClaimsIdentity.Name restituisce Null, restituisce un nome descrittivo (visualizzato) o in caso contrario restituisce una stringa non appropriata per l'uso come identificatore univoco per l'utente.

Molte distribuzioni che usano l'autenticazione basata su attestazioni usano azure Controllo di accesso Service (ACS) in particolare. ACS consente allo sviluppatore di configurare singoli provider di identità (ad esempio ADFS, provider di account Microsoft, provider OpenID, ad esempio Yahoo!e così via) e i provider di identità restituiscono gli identificatori dei nomi. Questi identificatori di nome possono contenere informazioni personali (PII) come un indirizzo di posta elettronica o possono essere anonimizzati come un identificatore personale privato (PPID). Indipendentemente dal fatto che la tupla (provider di identità, identificatore di nome) funzioni sufficientemente come token di rilevamento appropriato per un utente specifico mentre sta esplorando il sito, in modo che il runtime di ASP.NET Web Stack possa usare la tupla al posto del nome utente durante la generazione e la convalida dei token di campo anti-XSRF. Gli URI specifici per il provider di identità e l'identificatore di nome sono :

  • https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

Per altre informazioni, vedere questa pagina della documentazione del servizio Azure Kubernetes .

Quando si genera o convalida un token, il runtime di stack Web ASP.NET prova a eseguire il binding ai tipi:

  • Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (Per WIF SDK.)
  • System.Security.Claims.ClaimsIdentity (Per .NET 4.5).

Se questi tipi esistono e se la tupla IIIIdentity dell'utente corrente implementa o sottoclassi uno di questi tipi, la struttura anti-XSRF userà la tupla (provider di identità, identificatore del nome) al posto del nome utente durante la generazione e la convalida dei token. Se non è presente alcuna tupla di questo tipo, la richiesta avrà esito negativo con un errore che descrive allo sviluppatore come configurare il sistema anti-XSRF per comprendere il meccanismo di autenticazione basato su attestazioni specifico in uso. Per altre informazioni, vedere la sezione Configurazione ed estendibilità .

Autenticazione OAuth/OpenID

Infine, la struttura anti-XSRF ha supporto speciale per le applicazioni che usano l'autenticazione OAuth o OpenID. Questo supporto è basato sull'euristica: se il IIdentity.Name corrente inizia con http:// o https://, i confronti dei nomi utente verranno eseguiti usando un comparer ordinale anziché il comparer OrdinalIgnoreCase predefinito.

Configurazione ed estendibilità

Occasionalmente, gli sviluppatori potrebbero voler controllare più strettamente i comportamenti di generazione e convalida anti-XSRF. Ad esempio, il comportamento predefinito di MVC e Web Pages dell'aggiunta automatica dei cookie HTTP alla risposta è indesiderato e lo sviluppatore potrebbe voler rendere persistenti i token altrove. Esistono due API che consentono di eseguire questa operazione:

AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);

Il metodo GetTokens accetta come input un token di sessione di verifica della richiesta XSRF esistente (che può essere null) e produce come output un nuovo token di sessione di verifica della richiesta XSRF e un token di campo. I token sono semplicemente stringhe opache senza decorazione; il valore formToken non verrà eseguito il wrapping in un <tag di input> . Il valore newCookieToken può essere null; se si verifica questo problema, il valore oldCookieToken è ancora valido e non è necessario impostare alcun nuovo cookie di risposta. Il chiamante di GetTokens è responsabile della conservazione di eventuali cookie di risposta necessari o della generazione di qualsiasi markup necessario; il metodo GetTokens stesso non altererà la risposta come effetto collaterale. Il metodo Validate accetta la sessione in ingresso e i token di campo ed esegue la logica di convalida sopra di essi.

AntiForgeryConfig

Lo sviluppatore può configurare il sistema anti-XSRF da Application_Start. La configurazione è a livello di codice. Di seguito sono descritte le proprietà del tipo AntiForgeryConfig statico. La maggior parte degli utenti che usano le attestazioni vuole impostare la proprietà UniqueClaimTypeIdentifier.

Proprietà Descrizione
AdditionalDataProvider IAntiForgeryAdditionalDataProvider che fornisce dati aggiuntivi durante la generazione di token e utilizza dati aggiuntivi durante la convalida del token. Il valore predefinito è null. Per altre informazioni, vedere la sezione IAntiForgeryAdditionalDataProvider .
CookieName Stringa che fornisce il nome del cookie HTTP usato per archiviare il token di sessione anti-XSRF. Se questo valore non è impostato, verrà generato automaticamente un nome in base al percorso virtuale distribuito dell'applicazione. Il valore predefinito è null.
Requiressl Valore booleano che determina se è necessario inviare i token anti-XSRF tramite un canale protetto da SSL. Se questo valore è true, tutti i cookie generati automaticamente avranno il flag "sicuro" impostato e le API anti-XSRF genereranno se richiamate dall'interno di una richiesta che non viene inviata tramite SSL. Il valore predefinito è false.
SuppressIdentityHeuristicChecks Valore booleano che determina se il sistema anti-XSRF deve disattivare il supporto per le identità basate sulle attestazioni. Se questo valore è true, il sistema presuppone che IIdentity.Name sia appropriato per l'uso come identificatore univoco per utente e non tenterà di provare a specificare IClaimsIdentity o ClClaimsIdentity come descritto nella sezione WIF/ACS/claims-based authentication. Il valore predefinito è false.
UniqueClaimTypeIdentifier Stringa che indica quale tipo di attestazione è appropriato per l'uso come identificatore univoco per utente. Se questo valore è impostato e l'IIdentity corrente è basata su attestazioni, il sistema tenterà di estrarre un'attestazione del tipo specificato da UniqueClaimTypeIdentifier e il valore corrispondente verrà usato al posto del nome utente dell'utente durante la generazione del token di campo. Se il tipo di attestazione non viene trovato, il sistema avrà esito negativo nella richiesta. Il valore predefinito è Null, che indica che il sistema deve usare la tupla (provider di identità, identificatore del nome) come descritto in precedenza al posto del nome utente dell'utente.

IAntiForgeryAdditionalDataProvider

Il tipo IAntiForgeryAdditionalDataProvider consente agli sviluppatori di estendere il comportamento del sistema anti-XSRF eseguendo il round tripping di dati aggiuntivi in ogni token. Il metodo GetAdditionalData viene chiamato ogni volta che viene generato un token di campo e il valore restituito viene incorporato all'interno del token generato. Un implementatore potrebbe restituire un timestamp, un nonce o qualsiasi altro valore desiderato da questo metodo.

Analogamente, il metodo ValidateAdditionalData viene chiamato ogni volta che viene convalidato un token di campo e la stringa "dati aggiuntivi" incorporata all'interno del token viene passata al metodo. La routine di convalida può implementare un timeout (controllando l'ora corrente rispetto al momento della creazione del token), una routine di controllo nonce o qualsiasi altra logica desiderata.

Considerazioni sulla progettazione e sulla sicurezza

Il token di sicurezza che collega i token di sessione e di campo è tecnicamente necessario solo quando si tenta di proteggere utenti anonimi/non autenticati dagli attacchi XSRF. Quando l'utente viene autenticato, il token di autenticazione stesso (presumibilmente inviato sotto forma di cookie) può essere usato come metà di una coppia di token di sincronizzazione. Tuttavia, esistono scenari validi per proteggere le pagine di accesso colpite da utenti non autenticati e la logica anti-XSRF è stata resa più semplice generando e convalidando sempre il token di sicurezza, anche per gli utenti autenticati. Fornisce anche una protezione aggiuntiva nel caso in cui un token di campo sia mai compromesso da un utente malintenzionato, come impostazione o indovinare il token di sessione sarebbe un altro ostacolo per l'utente malintenzionato da superare.

Gli sviluppatori devono prestare attenzione quando più applicazioni sono ospitate in un singolo dominio. Ad esempio, anche se example1.cloudapp.net e example2.cloudapp.net sono host diversi, esiste una relazione di trust implicita tra tutti gli host nel dominio *.cloudapp.net . Questa relazione di trust implicita consente agli host potenzialmente non attendibili di influire sui cookie dell'altro (i criteri di origine uguali che regolano le richieste AJAX non si applicano necessariamente ai cookie HTTP). Il runtime di ASP.NET Web Stack fornisce alcune mitigazioni in cui il nome utente è incorporato nel token di campo, quindi anche se un sottodominio dannoso è in grado di sovrascrivere un token di sessione non sarà in grado di generare un token di campo valido per l'utente. Tuttavia, quando è ospitato in un ambiente di questo tipo, le routine anti-XSRF predefinite non possono comunque difendere contro il hijacking della sessione o l'account di accesso XSRF.

Le routine anti-XSRF attualmente non difendono contro il clickjacking. Le applicazioni che desiderano difendersi dal clickjacking possono facilmente farlo inviando un'intestazione X-Frame-Options: SAMEORIGIN con ogni risposta. Questa intestazione è supportata da tutti i browser recenti. Per altre informazioni, vedere il blog di IE, il blog SDL e OWASP. Il runtime di ASP.NET Web Stack può in alcune versioni future rendere gli helper MVC e Web Pages anti-XSRF impostano automaticamente questa intestazione in modo che le applicazioni siano protette automaticamente da questo attacco.

Gli sviluppatori Web devono continuare a garantire che il proprio sito non sia vulnerabile agli attacchi XSS. Gli attacchi XSS sono molto potenti e un exploit riuscito interrompe anche le difese di Runtime di Stack Web ASP.NET contro gli attacchi XSRF.

Acknowledgment (Riconoscimento)

@LeviBroderick, che ha scritto gran parte del codice di sicurezza ASP.NET la maggior parte di queste informazioni.