Introduzione alle query injection
In questo articolo faremo una puntatina nel mondo delle arti oscure per vedere un tema piuttosto complesso ma che cercherò di illustrare con il massimo della semplicità ovvero le query injection.
L’argomento è destinato a chi sta sviluppando le sue prime applicazioni web con database e non è ancora completamente in chiaro su quali possano essere le minacce per la sicurezza, ma anche per chi, pur essendo più esperto, vuole darsi una rinfrescata.
Origine delle query injection
Questo tipo di exploit parte dal presupposto che se abbiamo un input di dati (un modulo di ricerca o di login ad esempio o anche la querystring di un URL), e questi dati andranno poi a costituire una parte di una query che verrà eseguita sul database, allora è possibile manipolare la query stessa con risultati che possono essere disastrosi.
Vediamo come.
Esempio di query injection
L’esempio più semplice ed anche più spaventoso (e quindi vale questo per tutti) è quello che ci viene mostrato da una procedura di autenticazione nella quale non abbiamo tenuto conto della sicurezza.
Come avviene normalmente questa procedura?
- Vengono richiesti username e password tramite un semplice form
- Si esegue una query che andrà a cercare nel database la corrispondenza tra l’username e la password forniti.
- Se questa corrispondenza esiste, la procedura di autenticazione è valida
In codice è qualcosa del genere.
$sql = "SELECT * FROM users WHERE username='$_POST[username]' AND password='$_POST[password]'"; $res = mysql_query($sql); if(mysql_num_rows($res)) { // procedi all’autenticazione // normalmente viene valorizzata una sessione } else { // la procedura di autenticazione // non è valida }
Come vedi, se la query trova un’occorrenza, la procedura di autenticazione andrà a buon fine.
Ora osserva attentamente la query.
SELECT * FROM users WHERE username='$_POST[username]' AND password='$_POST[password]'
Cosa succederebbe se dopo l’AND riuscissimo ad infilare un espressione sempre vera?
Ovvio, avremmo l’autenticazione.
E come poteri fare?
Semplice:
Scriveremo come username: admin
E come password: ‘ OR username=’admin
Vediamo come risulterebbe la query
SELECT * FROM users WHERE username='admin' AND password='' OR username='admin'
Questa query andrà cercare la riga dove l’username è admin E la password è una stringa vuota (sempre falso) O l’username è admin (sempre vero).
E’ dunque sufficiente che esista un utente admin per poterne prendere i privilegi senza conoscerne la password.
Con il primo apice chiudiamo la stringa della password ed inseriamo la nostra stringa maligna.
A questo punto qualcuno potrebbe obiettare che le password sono conservate in forma di hash e dunque, il dato password andrà passato da un algoritmo di hashing rendendo inoffensivo l’attacco.
In effetti con questo codice
$password = md5($_POST[‘password’]); $sql = "SELECT * FROM users WHERE username='$_POST[username]' AND password='$password'";
La query che ne risulterebbe sarebbe così
SELECT * FROM users WHERE username='admin' AND password='ffc0e9542109bce5c715daa0e6d09614'
Del tutto inoffensiva.
Ma non basta!
Anche in questo caso posso facilmente arrivare ad un exploit.
Passando questi dati:
username: admin’ —
password: qualsiasi cosa
Vediamo la query come risulterebbe
SELECT * FROM users WHERE username='admin' -- ' AND password='qualsiasi cosa'
In questo caso, dopo avere scritto admin come username chiudo la stringa con il simbolo apice, quindi lascio uno spazio e scrivo due linee (seguite da un altro spazio) che per MySql rappresentano il simbolo del commento. Dunque tutto quello che verrà dopo sarà irrilevante.
In questo modo è sufficiente avere un username valido per accedere all’applicazione in quanto la query che verrà eseguita sul database sarà la seguente:
SELECT * FROM users WHERE username='admin'
Combattere le query injection
Come avrai certamente intuito, una delle cose che più di altre permette questo genere di exploit è il carattere apice (ma attenzione non solo).
Dobbiamo quindi renderlo inoffensivo. La prima idea che potrebbe venire è quella di procedere all’escape del carattere apice tramite backslashes (\). Questa idea appartiene all’archeologia di PHP ed è da considerarsi inefficace
Il passato che nessuno rimpiange
PHP disponeva addirittura della direttiva magic_quotes_gpc che, se attiva, eseguiva l’escape del carattere apice tramite backslashes. Ma questo non è e non è mai stato un buon metodo (come non è un buon metodo utilizzare la funzione addslashes()).
- Le strighe salvate nel database con questo metodo vanno poi ripulite in uscita
- magic_quotes_gpc è OFF di default da PHP 4.3, deprecata da PHP 5.3 e rimossa in PHP 5.4. Dunque dimenticatela.
- Non garantisce assolutamente la sicurezza. Si veda cosa dice il manuale su questo argomento.
Il vero rimedio
La funzione certamente più importante è mysql_real_escape_string. Il fatto che gli sviluppatori di PHP abbiano sentito la necessità di mettere quel “real” nel nome della funzione la dice lunga su altri metodi di escape…
Passare tutte le stringhe destinate a finire in una qualsiasi query ci mette al riparo da quasi tutti i problemi. Per incrementare ulteriormente la sicureza possiamo anche:
- Utilizzare espressioni regolari mirate
- Rendere inoffensivo eventuale codice HTML (che può contenere del codice javascript dannoso) con le apposite funzioni (strip_tags o htmlentities)
- Forzare il tipo numerico dove è richiesto
Prepariamo ora una funzione di base per ripulire le stringhe destinate a diventare parte di una query. Inizieremo con il rimuovere eventuali backslashes aggiunte nel caso in cui magic_quotes_gpc sia attivato. Per sapere se questa direttiva è attiva, PHP mette a disposizione la funzione get_magic_quotes_gpg che ritorna TRUE se la direttiva è ON, altrimenti false.
function _cleanString($string) { if(get_magic_quotes_gpc()) { $string = stripslashes($string); }
Qui però metterei un accorgimento. Se la direttiva magic_quotes_gpc è stata rimossa, probabilmente un giorno verrà rimossa anche la funzione get_magic_quotes_gpc.
Dunque meglio verificarne l’esistenza prima di utilizzarla, in questo modo:
if(function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc()) { $string = stripslashes($string); }
Ed ora passiamo la stringa per mysql_real_escape_string in modo da ottenere l’escape dei caratteri speciali, da strip_tags in modo da eliminare i tag html, ed infine facciamo un trim che non fa mai male. Fino ad ottenere questa funzione.
function _cleanString($string) { if(function_exists('get_magic_quotes_gpc') AND get_magic_quotes_gpc()) { $string = stripslashes($string); } $string = mysql_real_escape_string($string); $string = strip_tags($string); $string = trim($string); return $string; }
Conclusione
In questo articolo abbiamo trattato le basi delle query injection ed abbiamo visto i principali metodi di difesa. Il discorso rimane comunque molto complesso ed esistono degli esempi di exploit che rasentano la follia.
Il consiglio è quello di mai accontentarsi. La funzione appena esposta è una valida base, ma per migliorare la sicurezza considera anche delle espressioni regolari mirate al contenuto atteso.
Un’ultima ed importante nota. L’utilizzo delle tecniche esposte in questo articolo su siti web che non ti appartengono direttamente, costituisce un reato penale (indipendentemente dall’esito dell’exploit).
12 commenti
Trackback e pingback
-
Hack - SQL injection
[...] articolo di introduzione alle query injection è stato pubblicato da YourInspirationWeb: l’argomento è destinato a chi sta sviluppando le sue…
Grazie Maurizio, ottimo articolo come sempre.
Ma una cosa probabilmente più “alla buona” (come in genere faccio), del tipo:
if(strlen($_POST[‘user’])<33){
if(strpos($_POST['user'], "'")!== false Or strpos($_POST['user'], '"')!== false){
die("bla bla bla");
}else{
$inserted_user = md5($_POST['user']);
}
}else{
die("bla bla bla");
}
Ovvero: prima verifico (paranoid..) la lunghezza del dato in input (<33 perché md5 genera una stringa di 32 caratteri), poi verifico la presenza di apici o doppi apici: se tutto ok dò l'input in pasto a md5 e cerco la corrispondenza su db.
Come la giudichi? In teoria dovrebbe mettermi al riparo da injection varie, o mi sfugge qualcosa?
Ciao!
ops, scusa, la verifica della lunghezza del dato in entrata è sì buona (non si sa mai), ma non è correlata con la lunghezza della stringa dopo il “trattamento” md5. diciamo invece che limito i caratteri inseribili sulla base delle indicazioni fornite all’utente in fase di registrazione (ad es. username max 12 caratteri..).
PS: sorry x il doppio post, avrei voluto editare il commento precedente ma non c’è la funzione di editing o_O
Assicurarsi che non vi siano apici non fornisce nessun tipo di sicurezza (e tra l’altro l’apice è un carattere comune e non vedo perché debba essere “bannato”. Solo nella frase che ho scritto ne ho usati legittimamente due).
Posso utilizzare ottali, caratteri multibyte e il tuo sistema verrebbe immediatamente scavalcato.
Senza troppo andare lontano, prova a passare alla tua funzione
%27 OR username=27%admin
ahahahahahaha.
Guarda, ci sono infiniti modi di passare una query injection
Innanzi tutto complimenti per l’articolo e la spiegazione dettagliata.
Mi ero occupato dell’argomento già tempo fa ma una rinfrescata quando si parla di sicerezza informatica non fa mai male..tra l’altro non ero a conoscenza della nuova funzione “real” presentata.
Andrò a dare un’occhiata, buon lavoro & buona giornata!
Ciao Ganfranco e grazie.
beh,… tanto nuova non è… visto che è stata introdotta con PHP 4.3… palriamo del 2002 … :-)
Ho dimenticato una curiosità.
PHP permette di lanciare unicamente una query per volta. E questa è un’importante misura di sicurezza. Altrimenti cosa succederebbe se come username scrivessimo?
admin’); DROP TABLE users; —
Il get_magic_quotes_gpc (così come il register_globals) è stata introdotta per “aiutare” i programmatori inesperti che non erano attenti alle regole della corretta programmazione. Fortunatamente questi obbrobri (per non dire peggio) stanno per essere definitivamente abbandonati.
Nello sviluppo di un qualsiasi progetto fra i primi settaggi che opero è quello di mettere off tali direttive (tramite php.ini o .htaccess).
In questo modo evito di dover eseguire noiosi controlli del tipo:
if(function_exists(‘get_magic_quotes_gpc’) AND get_magic_quotes_gpc()){/*…*/}
e posso fare direttamente l’escape delle stringhe in modo corretto.
Inoltre, l’attuale tendenza di php (sempre più OOP) spinge sempre di più verso l’utilizzo del PDO statement ed il metodo bindParam (http://php.net/manual/en/pdostatement.bindparam.php) che, seppur poco pubblicizzati sui blog tematici, rappresentano la migliore strada da seguire per ripararsi dalle Sql Injection.
Scusatemi cosa che continuo a non capire perchè tanta gente si ostina ad utilizzare php, in asp.net questo problema non si pone … l’uso di passare una stringa come query è un concetto ormai superato … soprattutto in ambito di sicurezza.
Emiliano:
Ogni web developer sceglie il linguaggio che più è consono con il suo stile di programmare. C’è chi preferisce il mix procedurale / OOP di PHP dove si devono ancora sviluppare a mano certe cose che altri linguaggi magari offrono già di base, c’è chi invece preferisce ambienti su framework totalmente OOP come ASP.NET , o Java etc..
Per quanto mi riguarda li ho provati quasi tutti i linguaggi principali ho cominciato con PHP il concetto di programmare su web, poi negli anni ho provato anche altro come ASP, ASP.NET, Java e JSP , e qualche altro..PHP semplicemente è meno chiuso come possibilità di scrivere un applicazione e lascia spazio a più libertà, seppure anche ad alcuni rischi se non si sta attenti..Nonostante forse ad alcuni sembri ancora un linguaggio poco robusto, ad oggi offre comunque strumenti abbastanza moderni per poter scrivere un applicazione valida a mio parere..Poi si ricorre sempre più spesso a CMS o framework su PHP, per creare siti , o comunque si crea la propria base di sviluppo. C’è anche da sottolineare che è economico sviluppare su PHP , un server Miscrosoft per ASP.NET o uno per una macchina virtuale Java per JSP è molto più costoso di un server per PHP.Direi che in generale è tutto soggettivo ! Ogni linguaggio è valido se usato come si deve!
Solita argomentazione stupido/saccente da code war.
Chi programma php seriamente usa gli statment, e se serve anche query dirette ma con l’adozione di filtri il problema di injection non si pone.
Esistono framework robusti in php come synfony e zend totally oop dove il problema non si pone.
Ho visto molte applicatzioni .net e java non passare i vulnerability test al contrario di applicazioni PHP fatte come si deve (ma la colpa è di qualche programmatore php infiltrato suppongo).
Il segreto è nel manico, non nel bastone.
Certo se il tuo termine di paragone è un 16enne che inizia a programmare e lo fa in php perché ha una curva di apprendimento più bassa allora di che cosa stiamo parlando?
Se php ha una pecca è solo il fatto di essere interpretato e non compilato.
Grazie, stavo proprio cercando questo articolo !