Si sente spesso parlare di data leak in cui moltissimi dati, tra cui dati utente, vengono rubati a società anche molto famose. In questo articolo vedremo una delle vulnerabilità che spesso ha portato a tali leak e che pare abbia giocato un ruolo principale in uno dei breach più famosi per noi veri nerd, quello del Playstation Network nel 2011: la SQL Injection.
Ho scelto di trattare le SQL Injection per due motivi principali: sono uno dei miei attacchi preferiti e nonostante sia una tecnica nota dal 1998 circa è ancora al primo posto nella top ten vulnerabilities dell’OWASP.
Il primo posto è dovuto alla pericolosità e alla pervasività di questa vulnerabilità che nei casi più gravi permette a un attaccante di ottenere pieno controllo del server.
Nel corso degli anni ne sono state trovate evidenze nei servizi più disparati e anche tra nomi importanti (WordPress, Joomla, Yahoo, US Election Commission, Dipartimento della Funzione Pubblica, Playstation Network… giusto per dare un’idea) e considerando stime ufficiali e esperienze di prima mano avute per lavoro posso affermare che circa il 30% degli applicativi web presenta una vulnerabilità di questo tipo.
SQL
Partiamo col dire che oggi qualsiasi web application/blog/etc che non sia un sito statico del ’96 utilizza un database, formato da tabelle, da cui prendere e in cui riporre i dati che utilizza. Quando ad esempio si compila un form di login o di ricerca gli input vengono utilizzati per comporre una cosiddetta query che andrà ad effettuare un’operazione sul database.
Le query sono istruzioni in SQL (Structured Query Language), linguaggio che serve per gestire l’interazione coi più comuni tipi di database relazionali. Esistono in realtà varianti di SQL a seconda del database utilizzato, ma si basano tutte sugli stessi principi e non serve scendere nel dettaglio in questo articolo.
Una query SQL è una cosa del genere (porto come esempio una query che recupera informazioni dal database):
[code]
SELECT *
FROM utenti
WHERE username = ‘pippo’
[/code]
Non pretendo di insegnare SQL in tre righe, anche se è semplice, ma evidenzio parte della sintassi del linguaggio che ci aiuterà in alcuni esempi più avanti:
FROM indica la tabella da cui voglio recuperare le informazioni, SELECT indica cosa voglio recuperare (quali attributi, * indica tutte le colonne presenti nella tabella) e WHERE specifica una condizione per scremare tra le righe della tabella (“voglio solo le righe con username che vale ‘pippo’ ”, in questo caso verosimilmente la riga ritornata sarà una perché solitamente non è consentito avere più utenti con lo stesso username).
Notare che il valore di tipo stringa va tra ‘ ‘ non essendo una variabile, inoltre nella clausola (si chiamano così) WHERE è possibile combinare più condizioni utilizzando gli operatori logici AND e OR.
Per chi volesse colmare eventuali lacune sui database relazionali (DBMS):
- Modello Relazionale (wikipedia.it)
Fin qui abbiamo imparato che se, ad esempio, un sito ti propone un form di due textbox per inserire nome utente e password, questi saranno poi utilizzati per creare una query SQL per andare a cercare se esiste tale utente con tale password nel database.
Injection
Un bel giorno qualcuno si è chiesto: è possibile modificare la query eseguita sul database inserendo parametri di input opportunamente studiati?
La risposta, grazie alla quale state leggendo questo articolo, è ovviamente si (se non vengono prese le giuste precauzioni!).
Entriamo nella pratica, e teniamo buono l’esempio del form di login.
Immaginiamo che la query per recuperare un utente dal database avendo username e password sia una cosa del tipo:
[code]
SELECT *
FROM utenti
WHERE username = ‘usernameinserito’ AND password = ‘passwordinserita’
[/code]
In realtà utenti e password possono stare su tabelle distinte, le password non sono solitamente (si spera!) salvate in chiaro e la query può essere multi tabellare (FROM utenti, password) ma sono dettagli che non ci servono per capire il concetto.
Ipotizzando la query sopra, è possibile loggare senza conoscere le credenziali?
La query ritorna qualcosa solo se la clausola WHERE è vera, ma noi non conosciamo credenziali valide, di conseguenza dobbiamo studiare un modo per rendere comunque vera la clausola.
Se io inserissi come nome utente Pluto, e come password ‘ or 1=1 — come sarebbe la query?
L’applicazione andrebbe a interrogare il database con questa query:
[code]
SELECT *
FROM utenti
WHERE username = ‘Pluto’ AND password = ‘‘ OR 1=1 —
[/code]
Questa query cercherebbe prima di tutto, a causa della precedenza dell’operatore AND, nella tabella utenti una riga con username ‘Pluto’ e password vuota, condizione che verosimilmente è falsa, ma la parte di cui abbiamo fatto injection fa si che la condizione risulti sempre vera, perché 1=1 è per forza vero e viene messo in OR con la condizione falsa dell’AND, e True OR False restituisce sempre True.
Il doppio trattino indica in SQL di trattare come commento tutto quello che c’è dopo, quindi eventuali istruzioni successive alla nostra injection verranno ignorate (anche qui i caratteri esatti usati per i commenti dipendono in realtà dal database utilizzato, ma — è uno dei principali).
In questo modo in realtà non serve neanche sapere uno username, la query recupererà tutte le righe della tabella e ne troverà uno per noi (probabilmente quello nella prima riga).
E se si volesse loggare come uno specifico utente?
Supponiamo di conoscere un nome utente valido (Cthulhu nell’esempio che segue) e di volerci fingere lui, potremmo inserire come username Cthulhu’ – – e come password qualsiasi cosa
La query sarà:
[code]
SELECT *
FROM utenti
WHERE username = ‘Cthulhu’ – – ‘ AND password = ‘123123123’
[/code]
Inserendo come utente Cthulhu’ – – abbiamo chiuso noi la coppia di apici che racchiude il valore username, e il doppio trattino indica in SQL di trattare come commento tutto quello che c’è dopo, quindi il controllo sulla password non verrà eseguito.
Le SQL injection non sono soltanto limitate al login, questo è il più semplice degli esempi per spiegare la logica dietro a questa vulnerabilità, ma possono portare anche a data leaks, esecuzione di istruzioni di modifica del database o addirittura in alcuni casi a esecuzione di comandi sul sistema operativo sottostante.
Ad esempio modificando quanto inserito nell’esempio precedente in Cthulhu’; drop table articoli; – -:
[code]
SELECT *
FROM utenti
WHERE username = ‘Cthulhu’; drop table articoli; – -’ AND password = ‘123123123’
[/code]
Vengono eseguite due query, una ci permette di loggare come Cthulhu e l’altra, l’istruzione DROP TABLE, cancella l’intera tabella passata come argomento, in questo caso articoli. Il discorso va poi a complicarsi ovviamente: all’interno della categoria SQL injection, esistono una moltitudine di varietà più complesse che utilizzano cose come, ad esempio, il tempo di risposta in caso di query vera o falsa per ottenere informazioni riguardanti il database e il suo contenuto, ed in base alla tipologia di injection a cui il sito è suscettibile si riescono o meno a fare determinate azioni.
Entrano poi in gioco anche i permessi dell’utente SQL in uso dal sistema, struttura del database, etc. ma per rendere l’idea questo esempio basta e avanza.
A inizio articolo ho menzionato i famosi data leak, perchè le sql injections sono uno dei principali vettori che causano questi eventi.
Una delle tecniche utilizzate per raggiungere tale risultato, sempre dipendentemente dal tipo di sql injection trovata, si chiama UNION Exploitation.
UNION è un operatore SQL che permette di concatenare i risultati di due query, a patto che abbiano un numero uguale di colonne nella clausola SELECT.
Supponiamo di avere un sito che contiene informazioni sensibili come numeri di carte di credito per gestire transazioni, e che per una qualche sua funzionalità consenta di cercare il profilo degli utenti.
La query di ricerca utente potrebbe essere una cosa come:
[code]
SELECT Nome, Cognome
FROM utenti
WHERE nomeutente = ‘utente1’
[/code]
dove utente1 è un valore specificato nell’apposito campo di ricerca.
A me però non interessano i nomi degli utenti, a me piacciono i soldi e voglio le carte di credito!
Se si mette nel campo di ricerca utente1’ UNION SELECT numeroCarta,1 FROM cartedicredito si ottiene:
[code]
SELECT Nome, Cognome
FROM utenti
WHERE nomeutente = ‘utente1’
UNION
SELECT numeroCarta, 1
FROM cartedicredito
[/code]
In questo modo l’operatore UNION fa si che, oltre al Nome e Cognome dell’utente utente1, mi compaiano nei risultati tutti i numeri di carta di credito presenti sulla tabella cartedicredito! Il numero 1 accanto a numeroCarta nella SELECT è un semplice segnaposto, serve per avere un numero di colonne risultato uguale a quello della prima query.
Da notare che è necessaria una certa conoscenza della struttura del database bersaglio: nome della tabella coi numeri carta, nome del campo della tabella associato ai numeri di carta, numero di colonne effettivamente ritornate dalla prima query.. Oppure si procede per guessing.
Ci sono però tool che aiutano molto sia nella scoperta della struttura del database che nell’exploitation vera e propria utilizzando payload standard, il più famoso è forse sqlmap. Questi tool permettono anche a un bambino di tre anni di condurre tentativi di exploitation verso un portale (o quasi, qui un ricercatore che a scopo funny/provocatorio si è filmato mentre usava insieme a suo figlio di tre anni uno strumento su Windows), quindi si può dire senza dubbio che le SQL injection, almeno nelle forme standard e supportate dai tool, possono essere eseguite da quasi chiunque.
In realtà non è difficile proteggersi contro questo tipo di attacco, ci sono vari accorgimenti tra i quali voglio citare l’input sanitization, ossia pulire l’input da tutti quei caratteri che potrebbero dare problemi (apici, trattini, spazi, tutto quello che non è nelle policy di valori accettati per un determinato input) o l’utilizzo di query parametrizzate, dove quindi l’input utente non è concatenato direttamente alla query ma messo in un parametro che viene poi utilizzato dalla query. (info qui)
Oltre a essere best practices tutt’altro che impossibili da implementare, esistono anche librerie che svolgono parte di questi compiti per lo sviluppatore.
Conclusioni
A questo punto è lecito porsi una domanda: se è così facile proteggersi da tali attacchi, perché ci sono ancora oggi, a 20 anni di distanza, moltissimi siti ancora vulnerabili?
I motivi sono vari, spesso ad esempio vengono utilizzate come sviluppatori persone che non conoscono le best practice per sviluppare applicazioni sicure, o che non hanno esperienza nel campo.
Altro motivo tutt’altro che raro è la pressione a cui sono sottoposti molti sviluppatori da parte di managers per sviluppare software funzionale piuttosto che software sicuro (meno tempo –> meno costi –> più guadagno –> finché non c’è un data leak o altro danno)
Una terza causa, che si riallaccia alla prima citata, è l’utilizzo software di terze parti, come plugin o estensioni che possono essere aggiunti nei vari CMS. Se non si ha la certezza che sia un prodotto di qualità si può rischiare di andare rendere vulnerabile il proprio sito (un esempio: il plugin ninja forms di wordpress, la cui vulnerabilità a SQL injection è stata scoperta nell’agosto 2016, o la più recente injection scoperta nel plugin WP Statistics).
In sostanza l’ignoranza è la vera causa dietro alla presenza di questa vulnerabilità, e sarebbe auspicabile la diffusione di una “cultura” di applicazioni sicure oltre che funzionali.
- SQL Injection (wikipedia.it)
NdItomi
In testa e in cover: il terminale di Fallout. Non c’entra una mazza, ma mi piaceva.