9 Jan 2013 5 commenti
Gli attacchi CSRF (Cross Site Request Forgery) determinano che l'utente vittima esegua inconsapevolmente un'azione non voluta su un'applicazione web in cui risulta autenticato (vedi definizione fornita dall'OWASP, progetto open-source per la creazione di applicazioni Internet sicure).
Comprendere le diverse tecniche di difesa contro questo genere di minacce richiede di capire a fondo in cosa consistono praticamente gli attacchi CSRF. Sul tema, seguendo alcuni forum, ci si accorge che vi sono forti confusioni.
Dalle definizioni e dagli esempi presenti su molti siti web (il classico esempio è un involontario trasferimento bancario), per quanto esattissimi, introducendo elementi di complicazione non aiutano la corretta comprensione del problema.
Inoltre, la problematica è spesso affrontata dal punto di vista dell'utente vittima e molto più raramente da punto di vista del programmatore.
Per questa ragione la prima cosa che intendo fare è quella di esporre alcuni esempi, banali ma molto realistici, di un attacco CSRF osservando il codice php che è soggetto a tale minaccia. Il passo successivo sarà quello di illustrare singolarmente le diverse tecniche di difesa illustrando le modifiche che potrebbero essere apportate al codice.
Un esempio di attacco CSRF
Poniamo il caso che il nostro applicativo abbia una pagina/script ospitata al seguente URL http://www.tuosito.it/admin/reset.php che, dopo aver verificato il possesso dei permessi esegua un reset del database.
Un codice di esempio potrebbe essere:
<?php /* apriamo la sessione */ session_start(); /* includiamo la libreria con tutte le funzioni che ci occorrono */ include('libreria_di_funzioni.php'); /* verifichiamo che l'utente sia loggato con la qualifica di amministratore e se non lo è blocchiamo lo script */ if( !is_admin() ){ exit('Solo gli amministratori possono accedere a questa pagina.'); } /* possiamo eseguire il reset del database */ reset_database(); ?>
Tale pagina,in virtù della funzione is_admin() (una generica funzione non meglio specificata con un return boleano), è sicuramente protetta dagli accessi degli utenti non loggati con la qualifica di amministratori.
Quindi, l'hacker di turno anche conoscendo l'URL della pagina (http://www.tuosito.it/admin/reset.php), non essendo loggato con la qualifica di amministratore non potrebbe direttamente eseguire il reset del database.
Sembrerebbe tutto perfetto ma in realtà non lo è. Infatti, questo è il classico scenario in cui l'hacker potrebbe sferrare un attacco CSRF. Vediamo come.
Poniamo che il nostro sito abbia un guestbook/blog in cui gli utenti possono lasciare un commento. Il form costruito a tale scopo contiene il classico campo "sito web" che consente l'indicazione di un link.
Il nostro hacker potrà lasciare tranquillamente un interessante e attraente commento ed indicherà come URL del proprio sito http://www.tuosito.it/admin/reset.php, ovvero la nostra pagina di reset.
Noi, che siamo loggati come amministratori, ricevendo una notifica del nuovo commento, incuriositi, andremo a leggerlo; ci accorgiamo che l'utente ha lasciato anche l'URL del suo sito web e decidiamo così di andare a vedere di cosa si tratta. OPS!
Noi siamo loggati come amministratori e stiamo puntanto con il browser al link lasciato dall'hacker, ovvero la pagina di reset: in maniera inconsapevole abbiamo eseguito il reset del database e la frittata è fatta.
Alcuni potrebbero pensare che avremmo dovuto porre maggiore attenzione a ciò che stavamo cliccando: anche questo è vero, ma fino ad un certo punto.
Infatti, molti blog consentono di lasciare nei commenti alcuni tag html (o per mezzo del bbcode) tra cui delle immagini (tag img). In questo caso l'hacker avrebbe potuto inserire nel commento la seguente immagine:
<img src="http://www.tuosito.it/admin/reset.php" />
In questo caso l'attacco avviene in modo ancor più subdolo dato che non servirebbe neanche il nostro click: la semplice visualizzazione del commento da parte nostra (loggati come amministratori) avrebbe determinato il buon fine (si fa per dire) dell'attacco.
Piccola precisazione: ad essere rigorosi, diversamente dal nostro esempio, sono definiti attacchi Cross Site Request Forgery quelli provenienti da siti esterni rispetto a quello vittima; ad esempio il link vi viene lasciato in un forum che frequentiamo o vi viene inviato all'interno di una email; tuttavia, se avete seguito il ragionamento, capirete che il risultato non cambia.
Come proteggersi dagli attacchi CSRF
Le soluzioni possibili per difendersi da attacchi Cross Site Request Forgery sono generalmente tre:
- verifica del referrer che in php è presente nella variabile $_SERVER['HTTP_REFERER'];
- evitare di eseguire operazioni con metodo GET e preferire a questo il metodo POST;
- verifica del token.
Esaminiamo separatamente le tre soluzioni evidenziando i limiti.
Verifica del referer
Un controllo molto semplice per proteggersti da questo tipo di attacchi è quello di verificare che la richiesta provenga da un URL valido.
Su HTML.IT viene riportato il seguente esempio:
<?php if (isset($_SERVER['HTTP_REFERER']) && $_SERVER['HTTP_REFERER']!="") { if (strpos($_SERVER['HTTP_REFERER'],$_SERVER['HTTP_HOST'])===false) { // Qualcosa non quadra: uscire dal programma, creare file di log, etc etc. } } ?>
Ebbene, tale controllo è tanto sbrigativo quanto scarso. Esso verifica che il referer non sia una pagina esterna al nostro dominio. Ciò ci protegge, ad esempio, se il link ci viene inviato via email, o viene postato su un forum che frequentiamo, essendo questi domini esterni rispetto a quello che ospita il nostro applicativo.
Ma nell'esempio riportato sopra sarebbe del tutto inefficace: infatti il link che noi andremo cliccare è presente nel nostro sito e conseguentemente il referer sarà la pagina del nostro sito in cui è stato lasciato il commento. Quindi, questo controllo non bloccherebbe la nostra inconsapevole richiesta di reset.
Ma è possibile perfezionare lo script.
Per eseguire il reset regolarmente avremo quasi sicuramente un link nel nostro pannello di amministrazione (poniamo che questo sia raggiungibile al seguente URL http://www.tuosito.it/admin/pannello.php).
in questo modo, una possibile soluzione potrebbe essere quella di verificare in maniera più approfondita il referer: se questo è il nostro pannello di amministrazione la richiesta è valida, altrimenti, in caso di referer diverso dal pannello di amministrazione, blocchiamo lo script senza fare eseguire il reset.
Lo script precedente verrebbe a modificarsi come segue:
<?php /* apriamo la sessione */ session_start(); /* includiamo la libreria con tutte le funzioni che ci occorrono */ include('libreria_di_funzioni.php'); /* verifichiamo che l'utente sia loggato con la qualifica di amministratore e se non lo è blocchiamo lo script */ if( !is_admin() ){ exit('Solo gli amministratori possono accedere a questa pagina.'); } /* verifichiamo che la richiesta provenga dal pannello di amministrazione */ if( strpos($_SERVER['HTTP_REFERER'], 'tuosito.it/admin/pannello.php') === FALSE ){ exit('Attacco CSRF: provieni da una pagina non valida!'); } /* possiamo eseguire il reset del database */ reset_database(); ?>
Anche questa soluzione è semplice e non del tutto sbagliata, anzi tutt'altro. Tuttavia neanche in questo caso saremo del tutto al sicuro. Cerchiamo di capire il perchè.
Il referer è un header inviato dal client (il browser solitamente) ma può essere falsato e/o modificato. Una tipica strategia adottata dagli hacker consiste nel creare script con CURL che alterano tale header.
Inoltre, il nostro applicativo potrebbe contenere il link legittimi alla pagina di reset da più fonti (oltre che da http://www.tuosito.it/admin/pannello.php) e ciò complicherebbe il nostro script.
Ma poniamo di trascurare del tutto tali problematiche più complesse e ipotizziamo un caso più banale e semplice da comprendere. Immagginiamo che il nostro pannello di amministrazione ci mostri gli ultimi commenti inseriti nel nostro sito: ebbene il link lasciato dall'hacker farà così comparsa nel nostro pannello di amministrazione e in questo caso il nostro click avrà un valido referer. Non mi sembra un'ipotesi fuori dal comune!
Tutto ciò ci fa capire che affidarsi al referer, per quanto esatto in principio, non ci garantisce la massima sicurezza.
Invio della richiesta con il metodo POST
Questa soluzione elimina alla radice parte del problema. Tramite un semplice link (o la richiesta eseguita con il tag img) non è possibile inviare dati con metodo POST. Quindi, questo tipo di attacco non potrà essere sferrato.
Volendo seguire questa strada all'interno del pannello di amministrazione avremo un form di questo tipo:
<form action="http://www.tuosito.it/admin/reset.php" method="post"> <input type="submit" name="resetdb" value="resetta database" /> </form>
Mentre il precedente script che esegue il reset andrà preventivamente a verificare l'esistenza della della variabile $_POST['resetdb']diventando:
<?php /* apriamo la sessione */ session_start(); /* includiamo la libreria con tutte le funzioni che ci occorrono */ include('libreria_di_funzioni.php'); /* verifichiamo che l'utente sia loggato con la qualifica di amministratore e se non lo è blocchiamo lo script */ if( !is_admin() ){ exit('Solo gli amministratori possono accedere a questa pagina.'); } /* verifichiamo che la richiesta sia eseguita con metodo post */ if( !isset($_POST['resetdb']) OR $_POST['resetdb']!='resetta database'){ exit('Attacco CSRF: assenza di richiesta con metodo post!'); } /* possiamo eseguire il reset del database */ reset_database(); ?>
A questo punto l'unica possibilità per l'hacker di sferrare un attacco CSRF consisterà nel costruire il form visto in precedenza in un sito esterno al nostro e da lui gestito ed invitarci a cliccare il submit con pretesti più o meno efficaci e persuasivi. Ovviamente egli dovrà conoscere oltre che l'URL in cui è ospitato lo script di reset anche il name e il value della variabile POST richiesta dallo script.
Occorre considerare che per esigenze di vario genere, ma spesso ingiustificate, i programmatori preferiscono ricorrere a link e, quindi, vi è un atteggiamento restio rispetto a questa tecnica.
I token
I token, fra quelli descritti in questo articolo, solo lo strumento più efficace per prevenire attacchi CSRF. Questi sono stringhe univoche create in maniera casuale e sempre diverse. Un classico token può essere costituito dall'ID di sessione (diverso e univoco per ogni sessione aperta).
Tale token può essere inviato con metodo GET e verificato all'interno dello script. In pratica il link alla nostra pagina di reset sarà costruito nel seguente modo:
<a href="http://www.tuosito.it/admin/reset.php?token=<?php echo session_id(); ?>">Resetta DB</a>
Lo script visto in precedenza prima di eseguire l'operazione di reset verificherà che il token esiste e che questo corrisponde con l'id di sessione. Pertanto avremo:
<?php /* apriamo la sessione */ session_start(); /* includiamo la libreria con tutte le funzioni che ci occorrono */ include('libreria_di_funzioni.php'); /* verifichiamo che l'utente sia loggato con la qualifica di amministratore e se non lo è blocchiamo lo script */ if( !is_admin() ){ exit('Solo gli amministratori possono accedere a questa pagina.'); } /* verifichiamo che l'esattezza dal token */ if( !isset($_GET['token']) OR $_GET['token'] != session_id() ){ exit('Attacco CSRF: token inesistente o non valido!'); } /* possiamo eseguire il reset del database */ reset_database(); ?>
A questo punto l'hacker per poter sferrare l'attacco CSRF, ovvero lasciare nel commetto con un link alla pagina di reset, deve, non solo deve conoscere l'URL della pagina di reset, ma deve ulteriormente conoscere l'ID della sessione corrente. Tuttavia, egli non avrà nessuno strumento per risalire all'ID di sessione ed, inoltre, questo cambierà ad ogni nuova sessione.
Quest'ultima affermazione è vera salvo il caso di un attacco di Session Hijacking ovvero, furto del cookie di sessione: tuttavia si tratta di un attacco di diverso genere e condotto con altre tecniche rispetto a quelle qui in esame.
Consiglio: combinare i metodi
I token possono considerarsi un ottimo sistema di protezione contro gli attacchi cross site request forgery e già da soli sono un sistema che ragionevolmente ci protegge da questi attacchi. Ma l'utilizzo di uno delle tecniche descritte non esclude l'altra. Infatti, i tre metodi descritti ed esaminati separatamente possono essere adottati congiuntamente: in tal modo raggiugeremo la massima sicurezza possibile e renderemo la vita estremamente difficile all'hacker.
In questo modo andremo ad immunizzare il nostro applicativo di molte delle possibili falle che possono generare gli attacchi descritti. Pertanto:
- eseguiremo l'azione con una richiesta di tipo POST cioè tramite un form: quindi nessun link potrà minacciarci;
- all'interno del form avremo un campo di tipo hidden in cui andremo ad inserire il nostro token che andremo a verificare all'interno dello script che esegue il reset: l'hacker non conosce la stringa di cui è composto il token;
- nello script che esegue il reset fra i controlli verificheremo che il referer sia il pannello di amministrazione.
Vediamo il codice. Avremo il seguente form:
<form action="http://www.tuosito.it/admin/reset.php" method="post"> <input type="hidden" name="token" value="<?php echo session_id(); ?>" /> <input type="submit" name="resetdb" value="resetta database" /> </form>
Mentre lo script che esegue il reset sarà il seguente:
<?php /* apriamo la sessione */ session_start(); /* includiamo la libreria con tutte le funzioni che ci occorrono */ include('libreria_di_funzioni.php'); /* verifichiamo che l'utente sia loggato con la qualifica di amministratore e se non lo è blocchiamo lo script */ if( !is_admin() ){ exit('Solo gli amministratori possono accedere a questa pagina.'); } /* verifichiamo che la richiesta sia eseguita con metodo post */ if( !isset($_POST['resetdb']) OR $_POST['resetdb']!='resetta database'){ exit('Attacco CSRF: assenza di richiesta con metodo post!'); } /* verifichiamo che la richiesta provenga dal pannello di amministrazione */ if( strpos($_SERVER['HTTP_REFERER'], 'tuosito.it/admin/pannello.php') === FALSE ){ exit('Attacco CSRF: provieni da una pagina non valida!'); } /* verifichiamo che l'esattezza dal token */ if( !isset($_POST['token']) OR $_POST['token'] != session_id() ){ exit('Attacco CSRF: token inesistente o non valido!'); } /* possiamo eseguire il reset del database */ reset_database(); ?>
Seguendo questi semplici accorgimenti è possibile garantire alle nostre applicazioni ottimi livelli di sicurezza contro attacchi CSRF.
Olimpio Romanella
Sono un appassionato di Web Developing con un particolare debole per php. Mi dedico principalmente dello sviluppo back-end ed in particolare programmazione lato server con php, sviluppo di database relazionali MySql e progettazione di CMS di piccole e medie dimensioni.
Mi avvalgo del framework javascript Jquery, utilizzando molti dei suoi plugin e nei dei miei progetti utilizzo spesso il framework MVC Codeigniter.
5 Commenti presenti
@davide: Grazie Davide. Si, l'ho usata e ammetto che mi ci trovo molto bene ad utilizzarla.
Bell'articolo, ho letto la classe security di codeigniter per creare un token e verificarlo con l'helper form.
l'hai provata? come ti ci trovi?
Grazie!
Ottimo articolo ! Grazie molto chiao
Articolo perfetto: chiaro, completo, approfondito. Complimenti, e grazie!
oidualc
02 June 2020 ore 00:29
Ottimo articolo. Una domanda, avendo una single page application, accessibile soltanto tramite url di questo tipo inviato all'utente tramite email https://www.myapp.it/?id=10&token=09300199304817733892 dove il token viene confrontato con la stringa nel database e se combacia l'utente accede alla web app. Il token cambia giornalmente con nuovo invio email tramite cron.
È sicura da attacchi CSFR? grazie