Una piaga chiamata SQL Injection
Da Raffaele Rialdi, Microsoft MVP – Visual Developer – Security
In questa pagina
Introduzione
Quanti danni può fare?
Quale linguaggio e quale tecnologia?
I vantaggi nascosti
La bottega dei luoghi comuni
Introduzione
SQL Injection consiste in un attacco informatico che sfrutta la cattiva pratica di molti sviluppatori nel concatenare le stringhe destinate ad un database server. La concatenazione delle stringhe SQL viene solitamente associata a problemi di sicurezza e, sebbene questo sia sicuramente vero, affligge anche performance e provoca subdoli errori di runtime in relazione ai separatori numerici e delle date.
Prendiamo ad esempio la seguente stringa che verrà eseguita come query su un database:
"select * from customers where companyname like ' " + TextBox1.Text + "%' "
Un utente del web che dovesse cercare "Carlo" causerebbe l'esecuzione della query:
"select * from customers where companyname like 'Carlo%' "
Lo sviluppatore web non ha purtroppo validato il contenuto della TextBox e un hacker potrebbe sfruttare questa vulnerabilità semplicemente inserendo nella casella di testo la stringa:
" ' or 'a' like 'a "
Lo statement SQL risultante dalla concatenazione sarebbe il seguente
"select * from customers where companyname like '' or 'a' like 'a%' "
In sostanza la clausola where è sempre vera causando quindi la restituzione del contenuto completo della tabella customers.
Questo esempio non è che la punta dell'iceberg ed è solo il primo assaggio. Il peggio ha ancora da venire ma questo deve cominciare a far riflettere gli sviluppatori poco accorti in quanto essere vulnerabili a questo tipo di attacco è palesemente contrario alla normativa vigente in materia di privacy (http://www.garanteprivacy.it), con tutto ciò che ne consegue per legge.
Un altro punto interessante è che questo tipo di attacco non è direttamente dipendente dalla tecnologia utilizzata o per dirlo in altri termini è un attacco cross-platform, funziona per esempio su SQL Server, MySQL, Access, Oracle o FireBird, solo per citarne alcuni. Naturalmente l'hacker in alcuni casi dovrà iniettare codice differente a seconda del database usato, ma nessuno è immune a priori. La vulnerabilità è figlia della stessa natura del linguaggio SQL che è espresso per mezzo di stringhe di testo.
Come vedremo più avanti le soluzioni definitive esistono ma purtroppo sono snobbate da molti sviluppatori che sottovalutano la portata di questa vulnerabilità.
Fino a che punto SQL Injection è pericolosa? Niente di meglio che un altro esempio per rispondere a questa domanda. Partendo dalla stessa query iniziale:
"select * from customers where companyname like ' " + TextBox1.Text + "%' "
l'hacker potrebbe scrivere nella TextBox (cioè iniettare) questo codice:
"' ; select * from sys.tables -- "
che risulterebbe in questa stringa:
"select * from customers where companyname like '' ; select * from sys.tables -- ' "
Questo statement ha tre punti fondamentali. Il primo è il ";" che permette la dichiarazione di due distinte query all'interno di una stessa stringa; il secondo è l'uso di "--" per commentare ciò che resta nella stringa. Non tutti i database permettono l'uso di ";" e/o di "--" ma questo non è altro che uno degli esempi possibili. Il terzo punto è l'uso della view sys.tables di SQL2005. In SQL2000 esiste la tabella sysobjects mentre in mySQL esiste la tabella INFORMATION_SCHEMA.TABLES, supportata anche da SQL2005. Ancora una volta queste differenze non costituiscono alcun ostacolo per l'attuazione dell'injection.
L'esecuzione della query appena vista restituisce l'elenco delle tabelle presenti nel database. Fatto questo l'hacker passa a sys.columns (o syscolumns) e così via ottenendo la struttura completa del database. Non contento potrebbe anche andare oltre e con sys.databases ottenere la lista di tutti i database installati su quel server. L'hacker può così copiarsi il contenuto del database e chi ritiene che ci voglia troppa pazienza certosina per portare a compimento questo tipo di attacco, sappia che esistono tool che, in automatico, fanno la copia di un database sfruttando semplicemente un web vulnerabile alla SQL Injection.
Quanti danni può fare?
Dopo il furto, arrivano i danni e per esempio l'hacker può cancellare una intera tabella iniettando:
"'; drop orders --"
Non bastasse, i danni più grossi arrivano iniettando query come queste:
"'; exec master..xp_cmdshell 'notepad.exe'--"
La extended stored procedure xp_cmdshell consente l'esecuzione di un arbitrario comando o eseguibile analogo a quanto si potrebbe eseguire da command prompt del server. Avendo i permessi di eseguire una query di questo genere, il server è completamente compromesso. Eseguendola una serie di volte si possono progressivamente portare su quel server (ad esempio via tftp) delle utility per fare le cose più disparate: controllo remoto del server via socket, dump della SAM per ottenere le password del server, furto dei dati, crash del server, sniffing della rete interna, e quant'altro ci si possa immaginare, il tutto solo con SQL Injection anche in totale assenza di bug o di vulnerabilità del database o del sistema operativo.
Questo rischio ha portato Microsoft ha decidere di disabilitare per default la xp_cmdshell ma, ancora una volta, questo non è certamente l'unico modo per poter sfruttare SQL Injection e la sua disabilitazione non è certamente una panacea.
Perché è possibile portare a compimento un attacco di questo genere? Per portare a compimento gli attacchi che abbiamo visto su SQL2005, oltre ad essere vulnerabili alla SQL Injection, sono stati fatti almeno tre errori di configurazione, ciascuno non meno grave dell'altro.
Il primo errore consiste nell'accedere al database con privilegio amministrativo. Tipicamente per pigrizia si accede con l'utente "sa" acronimo di "system administrator" che ha i massimi privilegi possibili sul database. L'uso di sa fa risparmiare una decina di minuti, giusto il tempo di creare un nuovo utente 'normale' e configurarlo con i minimi privilegi necessari. Un utente sprovvisto del permesso "control server" non può eseguire xp_cmdshell e questo ripaga già dei dieci minuti spesi per la configurazione. Attenzione che questo non è sufficiente a garantire la sicurezza. Ad esempio chiunque nel ruolo public può effettuare la schedulazione dei job in SQL Server. Con l'aiuto di una stored procedure scritta male è possibile schedulare l'esecuzione di un job con il privilegio assegnato al servizio SQL Server Agent.
Il secondo errore è quello di permettere l'accesso diretto alle tabelle. Una buona pratica è quella di far accedere l'utente appena creato alle sole stored procedure in modo da limitare il raggio di azione dell'hacker. Ancora una volta è importante non cadere nel tranello di una equazione "stored procedure = sicurezza e performance" che non è sempre vera.
Il terzo errore origina dall'installazione database server, quando abbiamo configurato il servizio di SQL Server. Se il servizio gira sotto le credenziali di localsystem o di un utente amministratore del PC, i comandi eseguiti da xp_cmdshell saranno anch'essi eseguiti con le stesse credenziali. Questo è quello che consente all'hacker di prendere il controllo completo del server.
Quale linguaggio e quale tecnologia?
Fino ad ora non abbiamo neppure citato il linguaggio di sviluppo o la tecnologia che il web server (o il windows client) usano per eseguire le query sul database. Questo perché sono ininfluenti. Sia questo un sito asp classico, php, jsp, asp.net o cgi, il problema è solo causato dallo sviluppatore che non valida l'input dell'utente e non dalla tecnologia usata.
Validare bene l'input utente non è una cosa da inventare e poi perché rifare una cosa che esiste già ed è collaudata? La soluzione bella e pronta sta nella collection dei parametri del command. Sia che parliamo di ado classico che di ado.net, entrambi prevedono l'uso dei command e di una collection di parametri.
public DataTable GetEmployee(DateTime StartDate) { SqlConnection Cnn = new SqlConnection( "Server=Zeus;Database=pubs;Trusted_Connection=True;"); SqlCommand Cmd = new SqlCommand( "Select * from employee where hire_date > @HireDate", Cnn); Cmd.Parameters.Add("@HireDate", SqlDbType.DateTime).Value = StartDate; SqlDataAdapter Da = new SqlDataAdapter(Cmd); DataTable Dt = new DataTable(); try { Da.Fill(Dt); } catch(Exception) { return null; } return Dt; }
Avendo utilizzato il parametro (@HireDate nell'esempio) e l'aggiunta del parametro nella collection dei parameters, abbiamo eliminato i rischi da injection.
Questa costituisce la migliore soluzione per fronteggiare la SQL Injection ma la sicurezza non si costruisce mai su un solo fronte. Oltre a scrivere codice sicuro è fondamentale aggiungere le altre contromisure sulla configurazione del database server citate precedentemente.
I vantaggi nascosti
Per rendere più allettante l'uso dei parameters nei command anche ai più scettici, vediamo adesso quali altri vantaggi ne derivano.
Il vantaggio più evidente per lo sviluppatore riguarda i rognosi problemi dei separatori dei decimali, date e ora. Un comando SQL che non faccia uso dei parametri deve necessariamente convertire il DateTime in stringa:
string str = "select * from orders where OrderDate>" + MyDate.ToString();
Il DateTime internamente non ha cognizione dei separatori, tantomeno del nome dei giorni o dei mesi. Nel Framework.Net il DateTime è un numero a 64bit in cui ogni unità rappresenta cento nanosecondi a partire dall'anno zero. In sostanza è un offset da una data convenzionale ed ha una granularità molto bassa che ci permette di rappresentare con esattezza un istante temporale.
Il risultato della conversione da DateTime a stringa è relativo a dove viene fatta la conversione. Negli USA la data del 6 Luglio 2005 sarà 7/6/2005, in Italia 06/07/2005 ed in Svizzera italiana 06.07.2005. Questa impostazione deriva dalle impostazioni internazionali di Windows associate al profilo dell'utente il cui token ha fatto partire il processo della nostra applicazione.
Se quindi l'applicazione parte a seguito di un doppio click, il formato della data dipenderà dalle nostre impostazioni internazionali. Nel caso di un web server l'applicazione web tipicamente gira con un utente senza profilo (aspnet, network_service, etc.) perció vengono utilizzate le impostazioni di 'default' che sono quelle scelte da chi ha installato il server e registrate nella chiave di registro HKEY_USERS/.Default/....
La soluzione sbagliata è quella dell'uso della funzione CONVERT del database che interpreta la stringa e la riconverte in data. La soluzione corretta è quella di non far mai avvenire le due conversioni da data a stringa (che avviene nella nostra applicazione) e da stringa a data (che avviene all'interno del database server).
Usando i parameters non vengno fatte conversioni. Il DateTime viene portato dalla applicazione al webserver nel formato binario nativo, senza trasformazioni in stringa con gioia del programmatore che eviterà i problemi dei separatori, gioia del database administrator che eviterà l'uso di CONVERT all'interno delle query, gioia del cliente che avrà una applicazione più performante.
Il guadagno in performance non è solo dato dalla mancata doppia conversione. Il vantaggio sta anche nella compilazione della query. Se la stringa della query cambia ogni volta a causa dei paramteri, questa dovrà essere ricompilata ogni volta dal database.
Se invece la query è parametrizzata, verrà compilata solo la prima volta e le successive esecuzioni saranno molto più performanti. Il performance monitor di Windows (SQL Statistics - SQL Compilations/sec e SQL Re-Compilations/sec) ci rivela in modo lampante questo vantaggio.
La bottega dei luoghi comuni
Ogni tanto, parlando di sicurezza, mi capita di sentire commenti del tipo "il mio sito è piccolo, chi verrà mai a cercarmi?" oppure "è solo una intranet , non è esposta e quindi non sto rischiando nulla".
Uno dei miti da sfatare è quello dell'hacker, nella stanza buia mentre sferra attacchi informatici dietro la sua amata tastiera. Il primo errore l'ho fatto io stesso associando la parola hacker con l'immagine del delinquente; hacker è solo chi ha fame di conoscenza e così come un coltello, la conoscenza si può sfruttare nel bene o nel male.
L'oscura figura che porta gli attacchi inizia il suo lavoro tramite tools automatizzati che cercano in modo indiscriminato su tutti gli indirizzi internet un PC che soffra di vulnerabilità note. L'assenza di distanza fisica e di conoscenza dell'importanza o meno del sito rende vulnerabili tutti a pari merito, siano essi utenti in dialup o superserver con informazioni supersegrete.
L'altro mito da sfatare è che nella intranet non si corrano rischi. Le statistiche dicono il contrario e la intranet è fonte di attacchi portati a compimento da utenti a volte consapevoli ed altre volte inconsapevoli. Questo è un buon motivo per pensare alla sicurezza anche in ambiente intranet, non dimenticando mai che è anche un obbligo di legge nel nostro paese. Oltre alla protezione da SQL Injection ci sono una lunga serie di buone pratiche come l'adozione di credenziali che abbiano i minimi privilegi necessari, la politica del "least privilege".