Il peccato originale

Il Web – e le tecnologie su cui si basa – è nato agli inizi degli anni 90 con intenti più semplici rispetto alle esigenze attuali. Il protocollo principe di tutta l’architettura, l’HTTP, è del 1991. Queste, insieme ad HTML e agli URL, sono le fondamenta, ad un alto livello di astrazione, su cui poggia la struttura informativa che regge il mondo moderno.

Su queste fondamenta sono state poi calate tutte le sovrastrutture con cui siamo abituati a progettare. Le tecnologie per il Web hanno preso il sopravvento e il risultato finale è una enorme richiesta di sicurezza, velocità e scalabilità su un’architettura che però è stata pensata 30 anni fa principalmente per scambiarsi documenti in maniera più precisa e puntuale (leggasi hypertext).

L’importanza della sessione

HTTP è stateless. Così è stato pensato e così è tutt’ora:

“The Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. It is a generic, stateless, protocol that can be used for many tasks beyond its use for hypertext, such as name servers and distributed object management systems, through extension of its request methods, error codes and headers. A feature of HTTP is the typing and negotiation of data representation, allowing systems to be built independently of the data being transferred”.

Per superare questo limite sono nate soluzioni la cui regina è ancora oggi la sessione server side basata su cookie. Alla prima richiesta HTTP, il server registra un cookie sul client con un codice che, incrociato con il valore tenuto dal server, consente di tenere traccia del chiamante nelle richieste successive.

Questo stratagemma permette l’esistenza delle attuali sessioni autenticate che consentono, per esempio, il login ai siti di Banche, servizi di Webmail, Social Network ecc.

Le basi per un potenziale attacco

Questo funzionamento è trasparente per l’utente. Si traduce nella possibilità di navigare con sessioni autenticate e autorizzate (potremmo dire protette) di un determinato sito. Ciò significa che per esempio, se la pagina post-login di un determinato sito fosse:

http://www.vattelapesca.com/area-riservata/loggedin.php

Riusciremmo ad aprirla solo dopo aver speso le corrette credenziali e aver, appunto, creato la sessione server side.

Ora ipotizziamo che la pagina (o più probabilmente il servizio REST) che si occupa di eseguire un pagamento sul sito della nostra banca sia:

https://www.bancavattelapesca.com/payments/sepa-credit-transfer?amount=500.00&cur=EUR&ibanpayee= IT31K0558401650000000000001&payee=Luigi%20Luzzatti

Questo URL sarebbe raggiungibile solo durante una sessione aperta tra client e server e quindi dopo che il titolare del conto corrente ha eseguito il login sul proprio Home Banking. Il fatto che il metodo della chiamata HTTP sia POST invece di GET per questa vulnerabilità non ha importanza. Uso il GET per comodità.

È chiaro che, quando la sessione è aperta, questo URL “protetto” potrà essere richiamato in qualunque momento. Anche dopo aver lasciato il sito della propria banca senza aver effettuato il logout.

Come viene effettuato l’attacco

L’obiettivo dell’attaccante è indurre inconsciamente la vittima a richiamare un URL protetto da sessione. L’attacco ha successo se l’utente non ha effettuato il logout (e quindi non ha interrotto la sessione server side) e se l’applicazione non ha implementato alcuna protezione.

Il metodo più semplice è realizzare la richiesta HTTP senza la necessità di dover cliccare su alcun link, sarebbe sufficiente aprire una pagina contenente:

<img src="https://www.bancavattelapesca.com/payments/sepa-credit-transfer?amount=500.00&cur=EUR&ibanpayee=IT31K0558401650000000000001&payee=Luigi%20Luzzatti" width="0" height="0">

Per portare a termine l’attacco. Come detto precedentemente, GET o POST poco cambierebbe. Addentrandoci nei servizi REST, se l’API esposta fosse richiamabile con il metodo POST, sarebbe sufficiente realizzare un banale form HTML come il seguente che si auto-invia:

<body onload="document.forms[0].submit();"> <form method="post" target="frameInvisibile" action="https://www.bancavattelapesca.com/payments/sepa-credit-transfer"> <input type="hidden" name="amount" value="500.00"> <input type="hidden" name="cur" value="EUR"> <input type="hidden" name="ibanpayee" value="IT31K0558401650000000000001"> <input type="hidden" name="payee" value="Luigi Luzzatti"> </form>

Questi attacchi, se opportunamente adattati, possono funzionare anche via email. I client di posta (web e non solo) non mostrano più le immagini contenute nelle email in via automatica, proprio per evitare attacchi di questa natura (o simili).

Cosa può ottenere l’attaccante

Fatta salva la presenza di altre vulnerabilità dell’applicazione attaccata (quali XSS: Cross Site Scripting) o di errati utilizzi del CORS (Cross Origin Resource Sharing) da parte dell’applicazione attaccata, non è possibile ottenere informazioni sul contenuto richiamato e presentato durante l’attacco.

L’unica possibilità per questa tecnica, se usata da sola, è esclusivamente quello di eseguire un’azione; che sia l’esecuzione di un pagamento, l’invio di un’email, la modifica della password o l’aggiunta di amici su un social network. Non è possibile per esempio leggere il saldo di conto, il testo di un’email o altro che sia informativo.

L’attacco non lascia alcuna traccia, l’attaccante non può essere rintracciato a meno che non venga recuperata la pagina Web che ha effettuato la chiamata incriminata prima che venga cancellata.

Il fatto che la risorsa sia raggiungibile solo via HTTPS non protegge da questo attacco.

Come proteggersi?

Questo tipo di attacco è noto da tempo. Inizialmente chiamato Session Riding, ha ormai preso il nome di CSRF: Cross Site Request Forgery. La natura dell’attacco non è rivolta all’applicazione in sé, bensì all’utente. Non si sfrutta infatti una debolezza intrinseca di una particolare applicazione, ma si usa il normale funzionamento dell’HTTP per celare una richiesta che potrebbe essere legittima. Il browser non può sapere l’intento malevolo ed esegue la richiesta HTTP, il server legge il cookie di sessione, lo verifica ed esegue regolarmente l’operazione. Si sfrutta appunto la sessione server side aperta dell’utente (da qui il primo nome: Session Riding) per richiamare risorse protette.

L’utente in prima battuta può difendersi da solo, chiudendo sempre le sessioni (e quindi eseguendo il logout appena possibile) prima di procedere con la navigazione Web e l’utilizzo di Internet in generale. Un’altra importante accortezza è evitare di fare altre operazioni (come navigare siti diversi) quando si è all’interno di una qualunque area riservata (Home Banking, Social Network, Webmail ecc.). Esistono inoltre soluzioni applicative e infrastrutturali che proteggono da questo attacco: è compito dell’applicazione, infatti, difendere l’utente finale.

Cosa può fare la singola applicazione

È chiaro ormai che la sola sessione server side non è sufficiente per proteggere le risorse. Come non lo è la sola presenza dell’HTTPS. Esistono diversi accorgimenti che possono essere posti in essere per mitigare il rischio di CSRF.

Il secondo fattore

Una delle modalità di sicurezza per verificare la richiesta di esecuzione di un’operazione è l’utilizzo del secondo fattore. Il suo impiego impedisce che l’operazione vada a buon fine a meno che non venga autorizzata al di fuori del suo contesto originale: per esempio un tap sull’app dello smartphone, la digitazione di un OTP (One Time Password) preso da un SMS o da un token software/hardware, una telefonata e così via.

L’assenza del secondo fattore inibisce l’esecuzione dell’operazione (per esempio un pagamento) ma non impedisce che questa richiesta arrivi comunque al server. Se per esempio, dall’app della vostra Banca arrivassero richieste per autorizzare pagamenti che non avete eseguito voi, come la vivreste? La presenza del secondo fattore impedirebbe sì la perdita di denaro, ma non la perdita di fiducia. Questa grave ricaduta avrebbe comunque un danno notevole nel rapporto con il cliente finale.

Per evitare ciò, è possibile inserire un’interazione utente che avvenga però prima dell’invio della richiesta al server, come una ri-autenticazione o la digitazione di un CAPTCHA. L’intervento non è trasparente per l’utente finale e potrebbe degradare la user experience. A seconda, infatti, dell’importanza dell’operazione da eseguire, l’utente potrebbe essere più o meno disponibile a tollerare un rallentamento della propria operatività.

Attributo SameSite per i cookie di sessione

Google ha introdotto l’attributo SameSite per mitigare il CSRF. Il cookie di sessione può essere salvato con questo attributo impostato su due differenti valori:

Strict: il sito non è esposto alle chiamate cross dominio e quindi, un attacco CSRF fallirebbe perché il browser non passerebbe il cookie di sessione al server.

Lax: di default. Il sito è esposto alle chiamate cross dominio.

La soluzione ha l’unica pecca di non essere supportata da tutti i browser: circa il 20% dei software di navigazione ignora questo attributo e quindi espone l’utilizzatore al CSRF (https://caniuse.com/#feat=same-site-cookie-attribute). È comunque una soluzione altamente consigliata in quanto facilmente spendibile anche a livello infrastrutturale, da subito funzionante e in grado di coprire la maggior parte dei navigatori in maniera completamente trasparente, senza impatti sulla user experience.

Token di allineamento tra client e server

Premesso che l’attacco CSRF non può leggere il contenuto delle informazioni richiamate a meno che l’applicazione non abbia altre vulnerabilità quali l’XSS, la presenza di un Token di allineamento tra client e server è un’ottima forma di mitigazione di questo attacco.

Il Token può essere di sessione, e quindi rinnovato ad ogni nuova sessione server side creata, oppure One Shot e quindi spendibile una sola volta.

La soluzione One Shot è nata inizialmente per evitare il doppio invio di un form HTML, del tipo:

<form action="post" …> … <input type="hidden" name="oneshot-token" value="vpakqS#F34FESS636Ssd.fwqgs-sq061Dk*klopf…"> </form>

Quando il server prepara la pagina per il client, stacca un Token One Shot, lo inserisce nella pagina e si salva il suo valore per la validazione. Quando il client manda in esecuzione la propria operazione, spedisce anche il Token che viene verificato dal server. Un eventuale attacco CSRF fallirebbe in quanto sprovvisto di un Token corretto. A questo proposito, più è complicato il Token, meno probabile sarà per un malintenzionato indovinare la stringa corretta, soprattutto in caso di applicazioni molto visitate che quindi generano un numero di codici molto alto.

Token di Sessione o One Shot?

Il Token One Shot è sicuramente più sicuro in quanto diverso ad ogni richiesta. Quando però è necessario proteggere risorse che possono essere richiamate più volte all’interno di una pagina (per esempio nei casi di Single Page Application) il Token fisso per sessione è la via più semplice. Il Token One Shot si brucerebbe alla prima richiesta e le successive (anche quelle lecite) fallirebbero. Gli attacchi CSRF, anche in caso di Token di sessione, non avrebbero accesso a questa informazione e non potrebbero avere successo. Per irrobustire questa soluzione, è possibile collegare al Token di sessione l’username dell’utente e il timestamp di connessione e inviare queste informazioni crittografate al client (si veda “Token crittografato?” più avanti).

In una Single Page Application, il Token per le chiamate AJAX può essere inserito nel metodo setRequestHeader dell’oggetto XMLHttpRequest:

XMLHttpRequest.open(method, url); … XMLHttpRequest.setRequestHeader(header, sessionToken); … XMLHttpRequest.send();

Token via POST o GET?

La spesa del Token One Shot come metodo HTTP GET (e, quindi, all’interno della Querystring tipo https:// vattelapesca.com/REST?oneshot=gls2FLPcxm09837fFLSDO3920) comporta meno rischi rispetto al Token di Sessione in quanto, nel momento in cui viene speso, viene anche verificato e invalidato.

Quando invece si prevede l’utilizzo di un Token valido per tutta la sessione, è consigliabile spenderlo all’interno del metodo POST (o PUT, DELETE ecc.). L’URL della risorsa è un valore decisamente più esposto rispetto al body di una chiamata POST (browser history, log, referrer ecc.) e sono tutti punti di caduta dove un malintenzionato potrebbe trovare falle per ottenere il Token ed eseguire un attacco CSRF nonostante la protezione applicativa.

Token crittografato?

Per irrobustire le soluzioni basate su Token, invece di generare un codice random che poi venga semplicemente confrontato sul server, è possibile generare un codice comprendente alcune informazioni di base come l’username dell’utente, il timestamp della prima chiamata e poi crittografare il tutto prima di spedirlo al client.

Quando poi questo Token viene re-inviato al server per la validazione dell’esecuzione, il server lo decrittografa, confronta username e timestamp e ne verifica la validità.

Architettura per la generazione/validazione dei Token

In architetture enterprise può essere interessante gestire centralmente la generazione/validazione dei token per le applicazioni esistenti. Se l’infrastruttura già possiede un OAuth 2 Server, si potrebbe collegare questo building block e prevedere una soluzione basata su OAuth 2 Client Credentials per autorizzare le applicazioni tramite un Access Token che venga poi speso come CSRF Token dal Front End.

A seconda delle esigenze, il Token può essere di Sessione – e quindi salvato dall’applicazione per la verifica durante tutta la sessione server side – oppure One Shot. In questo caso, a ogni richiesta l’applicazione richiederà la verifica all’OAuth 2 Server prima di procedere con l’operazione. Questa soluzione, per quanto più sicura, prevede un maggiore traffico di rete.

Architettura a prova di sviluppatori

Tutte le soluzioni viste finora, benché funzionanti dal punto di vista della mitigazione del rischio per il CSRF, fanno pieno affidamento sull’impegno degli sviluppatori. In una realtà enterprise, dove le società coinvolte nello sviluppo del codice sono diverse e dove i programmatori si susseguono di continuo, delegare a questa funzione il pieno impegno sulla difesa dai CSRF rappresenta un punto debole. Basterebbe il rilascio di una singola funzionalità non protetta per mostrare il fianco e subire un attacco.

È necessario pensare a una soluzione che possa essere funzionante su un’architettura più complessa senza necessità di altri interventi e senza doversi fidare del fatto che tutti gli sviluppatori coinvolti nella progettazione del software si ricordino di collegare una o più protezioni dal CSRF.

Verifica degli header HTTP Referer e Origin

Se la vostra architettura si basasse su un API Gateway/Manager, un Reverse Proxy, un WAF o una qualunque infrastruttura di BackEnd For FrontEnd che disintermedi (o protegga) le chiamate a BackEnd, potreste fare affidamento a questo layer per governare tutte le connessioni che non valorizzano correttamente questi header HTTP. In caso di anomalia infatti, potreste richiedere una nuova autenticazione arricchita da un Token di controllo, così da impattare meno utenti possibili (quelli con browser obsoleti e insicuri) e respingere i CSRF.

Facciamo chiarezza: il Referer (più correttamente sarebbe Referrer con due r ma tant’è, così si chiama l’header HTTP) contiene l’URL esatto di provenienza del browser. Con ciò intendo la pagina precedente in una normale sessione di navigazione, o la pagina chiamante in una richiesta ad un’API REST. Viene valorizzato durante le connessioni HTTPS ma spesso nascosto, per motivi di privacy, nelle connessioni http (si veda l’header Referrer-Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy). L’header Origin invece, specifica il solo dominio di provenienza (così da rispettare la privacy dell’utente) ma non è valorizzato sui browser meno moderni e sicuri: per esempio Internet Explorer 11 ignora questa direttiva.

Esempi di header HTTP Referer e Origin valorizzati:

Referer: https://site.com/privateArea/loggedIn;

Origin: https://site.com.

Se nella vostra infrastruttura sfruttaste l’HTTPS (come spero) potreste fare affidamento alla presenza del Referer. Quando presente anche Origin, dovrete verificarli entrambi. La verifica deve essere robusta, non affidatevi solo alla presenza del vostro nome di dominio (site.com) perché un malintenzionato potrebbe registrare il dominio commercialvattelapesca.com e attaccarvi dal subdomain site.commercialvattelapesca.com e superare il controllo di sicurezza (site.com presente in site.commercialvattelapesca.com). Usate piuttosto un’espressione regolare che verifichi l’inizio della stringa https://site.com/ o https://www.site.com/ per il Referer (da notare la forward slash finale), del tipo:

/^https:\/\/(w{3}\.)?site.com\//.test(referrer); // Node.JS

E l’esatta corrispondenza per l’Origin (senza però la forward slash finale):

/^https:\/\/(w{3}\.)?site.com$/.test(origin);

Questa architettura sarebbe in grado di respingere gli attacchi effettuati su API REST che non rispettano la SOP (Same Origin Policy). In caso di diverse indicazioni del CORS (ad esempio la Allow-Control-Allow-Origin: *) o richieste POST in <form> nascosti (come già visto precedentemente), le chiamate effettuate da domini non accettati verrebbero comunque respinte dal controllo sugli headers. Se la necessità fosse quella di ricevere chiamate anche da altri domini, basterebbe aggiungerli alla verifica (e al CORS se arrivassero direttamente dai client).

Nuovi attacchi legati al CSRF: attenzione al login!

L’attacco CSRF ha vissuto una nuova vita dopo l’introduzione dei sistemi di controllo sulle abitudini dell’utente e sulla verifica della piattaforma durante il login. Sarà capitato a tutti di effettuare il login al proprio conto corrente o alla propria Webmail da un computer diverso dal solito, o dall’estero. Immediatamente arrivano segnalazioni che allertano l’utente, su altri canali, che è stato effettuato un login non conforme alla regolare operatività. In questo modo, anche in caso di furto di credenziali, diventa difficile per un malintenzionato, aggredire le informazioni desiderate.

Tutto bello, tutto funzionante… fino a quando l’attaccante non fa effettuare il login direttamente all’utente tramite un CSRF. Una volta che la vittima stessa, inconsciamente, ha effettuato il login, sarà possibile eseguire operazioni, sempre tramite CSRF, per portare a termine l’attacco. Questo perché i sistemi di verifica del login fallirebbero in quanto è l’utente stesso, seppur inconsciamente, a effettuare l’accesso, sul suo solito computer.

Come proteggere le applicazioni dai CSRF in fase di login

Le modalità di protezione delle applicazioni dai CSRF in fase di login sono più o meno le stesse già viste, considerando però che non è possibile fare affidamento sulla sessione server side, perché la sessione non è ancora stata creata. O, se esistesse per ragioni diverse dalla protezione delle risorse, non sarebbe ancora stata autenticata.

Altre soluzioni che ho scartato

Esistono altre soluzioni per mitigare il CSRF che ho scartato dall’elenco di quelle papabili perché personalmente le ritengo deboli o perché sono già state corrotte:

Doppio cookie (debole se esistono sottodomini su cui non abbiamo il pieno controllo);

Header HTTP personalizzato con valore fisso (già corrotto: https://hackerone.com/reports/44146);

CORS (Cross Origin Resource Sharing): è stato creato per superare la SOP (Same Origin Policy) del client e abilitare quindi le chiamate cross dominio.

Altre soluzioni vincenti

Esistono poi altre soluzioni che funzionano bene ma che non ho trattato nell’articolo perché strettamente legate ad un linguaggio di programmazione o a un particolare framework di sviluppo e, quindi, fortemente vincolate alle scelte aziendali.

Per esempio, in Java, Spring consente di abilitare una robusta protezione dai CSRF: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html.

In PHP, Symfony offre una protezione ad hoc anche per il login: https://symfony.com/doc/current/security/csrf.html.

Ruby on Rails prevede anch’esso una protezione per il CSRF: https://guides.rubyonrails.org/security.html#csrf-countermeasures.

E così via per la maggior parte di linguaggi e relativi framework più usati. Ciò ha permesso una proficua riduzione delle vulnerabilità e conseguentemente una diminuzione degli attacchi riusciti.

Conclusioni

Il CSRF è un tipo di attacco invisibile che permette di eseguire operazioni, utile ai malintenzionati anche in caso di furto di credenziali perché consente di far effettuare inconsciamente il login alle stesse vittime, eludendo i sistemi di alerting tutt’ora presenti sulle abitudini di utilizzo di piattaforma e Paese di connessione.

Esistono diverse soluzioni per difendere le applicazioni. Tra tutte, l’unione delle due soluzioni:

il cookie con l’attributo SameSite;

la validazione degli header HTTP Origin e Referer (in ambiente HTTPS);

se usate in coppia, possono offrire una robusta protezione dagli attacchi CSRF. Sono trasparenti per l’utente e possono essere gestiti a livello infrastrutturale in un’architettura enterprise. Poi, se uno di questi controlli fallisse, si potrebbe reindirizzare l’utente a una nuova richiesta di login corredata da Token crittografato One Shot. Soluzione che resta molto valida ma che comporta un’attenzione importante da parte degli sviluppatori che devono sempre ricordarsi di inserire la verifica del Token.

Ho ribadito più volte nell’articolo che, se l’applicazione fosse esposta ad altre vulnerabilità (quali l’XSS), i sistemi di protezioni previsti per il CSRF fallirebbero miseramente.

Articolo a cura di Roberto Abbate

Autore

Autore Bio Roberto Abbate è Technical Leader in Deutsche Bank e in precedenza Enterprise Solution Architect in Banco BPM. Relatore di corsi ed eventi, ha pubblicato 3 libri sullo Sviluppo Web oltre a centinaia di articoli tecnici sulle principali riviste di informatica.