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 i 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 Autenticazione di Windows, 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 avere ancora 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 che mostra la pagina My A S P dot NET M V Application Index (Indice dell'applicazione my A S P DOT NET M V C). La scheda Rete è aperta.

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 "18+ anni di età" o "è un amministratore" per qualsiasi altro elemento. 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 restituisce in caso contrario una stringa non appropriata per l'uso come identificatore univoco per l'utente.

Molte distribuzioni che usano l'autenticazione basata sulle attestazioni usano in particolare Azure Controllo di accesso Service (ACS). ACS consente allo sviluppatore di configurare singoli provider di identità (ad esempio ADFS, provider di account Microsoft, provider OpenID come 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 potrebbero essere anonimizzati come un PPID (Private Personal Identifier). Indipendentemente dal fatto che la tupla (provider di identità, identificatore del nome) funga da un token di rilevamento appropriato per un determinato utente durante l'esplorazione del sito, quindi il runtime di ASP.NET Web Stack può 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 del nome sono :

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

Per altre info, vedi questa pagina del documento ACS .

Durante la generazione o la convalida di un token, il runtime di ASP.NET Web Stack proverà a eseguire l'associazione 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 il IIIIdentity dell'utente corrente implementa o sottoclassi uno di questi tipi, la funzionalità 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 tale tupla non è presente, 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 sulle attestazioni specifico in uso. Per altre informazioni, vedere la sezione Configurazione ed estendibilità .

Autenticazione OAuth/OpenID

Infine, la funzionalità anti-XSRF offre un supporto speciale per le applicazioni che usano l'autenticazione OAuth o OpenID. Questo supporto è euristico: se il IIdentity.Name corrente inizia con http:// o https://, i confronti dei nomi utente verranno eseguiti usando un operatore di confronto ordinale anziché l'operatore di confronto OrdinalIgnoreCase predefinito.

Configurazione ed estendibilità

In alcuni casi, gli sviluppatori potrebbero voler controllare in modo più rigoroso i comportamenti di generazione e convalida anti-XSRF. Ad esempio, il comportamento predefinito degli helper MVC e Web Pages per l'aggiunta automatica dei cookie HTTP alla risposta è indesiderato e lo sviluppatore potrebbe voler rendere persistenti i token altrove. Esistono due API per facilitare 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 , ad esempio, non verrà sottoposto a wrapping in un <tag di input> . Il valore newCookieToken può essere null; se ciò si verifica, il valore oldCookieToken è ancora valido e non è necessario impostare alcun nuovo cookie di risposta. Il chiamante di GetTokens è responsabile della persistenza dei cookie di risposta necessari o della generazione di eventuali markup necessari; Il metodo GetTokens stesso non modificherà la risposta come effetto collaterale. Il metodo Validate accetta i token di sessione e di campo in ingresso ed esegue la logica di convalida sopra di esse.

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 del 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 utilizzato 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 "secure" impostato e le API anti-XSRF genereranno se chiamate dall'interno di una richiesta non 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 presupporrà che IIdentity.Name sia appropriato per l'uso come identificatore univoco per utente e non tenterà di specificare IClaimsIdentity o ClClaimsIdentity, come descritto nella sezione autenticazione basata su attestazioni/WIF/ACS. Il valore predefinito è false.
UniqueClaimTypeIdentifier Stringa che indica il tipo di attestazione appropriato per l'uso come identificatore univoco per utente. Se questo valore è impostato e l'IIdentity corrente è basato sulle 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 del campo. Se il tipo di attestazione non viene trovato, il sistema non riuscirà a eseguire la 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 trip dei 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 può 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 all'ora in cui è stato archiviato al momento della creazione del token), una routine di controllo nonce o qualsiasi altra logica desiderata.

Decisioni di progettazione e considerazioni 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 da 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 del programma di sincronizzazione. Esistono tuttavia scenari validi per la protezione delle pagine di accesso rilevate 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 venga mai compromesso da un utente malintenzionato, poiché l'impostazione o l'individuazione del 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 stessa origine che regolano le richieste AJAX non si applicano necessariamente ai cookie HTTP). Il runtime di ASP.NET Web Stack fornisce alcune mitigazioni in quanto il nome utente è incorporato nel token del 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 difendersi dall'hijacking della sessione o da XSRF di accesso.

Le routine anti-XSRF attualmente non si difendono contro il clickjacking. Le applicazioni che desiderano difendersi da 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 Internet Explorer, il blog SDL e OWASP. Il runtime ASP.NET Web Stack potrebbe in futuro rendere gli helper anti-XSRF MVC e Web Pages impostano automaticamente questa intestazione in modo che le applicazioni siano protette automaticamente da questo attacco.

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

Acknowledgment (Riconoscimento)

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