SQL Injection, come proteggere le applicazioni Web

Alcuni anni fa realizzare un sito web significava produrre immagini, scrivere del buon codice HTML e assemblare il tutto in modo armonico. Tutto era statico, per modificare una pagina era necessario cambiare manualmente il codice HTML e caricarla di nuovo sul server. L'avvento degli application server ha permesso di integrare il Web con i database e produrre prima siti dinamici, ovvero in grado di autoaggiornarsi estraendo dati da un database, poi vere e proprie applicazioni, che in alcuni casi hanno soppiantato i programmi tradizionali. Per vari motivi, che vedremo in seguito, la filosofia costruttiva di una Web application differisce notevolmente dalla programmazione tradizionale, cosė come i criteri di protezione che devono essere utilizzati.

Come funziona la SQL Injection

Il concetto è molto semplice: si tratta di fare in modo che il Web application server esegua sul database di back end codice SQL arbitrario "iniettato" da un utente non privilegiato (un normale utente del sito Web per intenderci) nella Web application.

Anzitutto è bene chiarire che per ottenere ciò è abbastanza raro che si possano sfruttare bug o problemi intrinsechi dell'application server, molto più spesso è il fattore umano, ovvero la negligenza di chi ha progettato o realizzato la Web application, a offrire le migliori opportunità.

Gli scopi possono essere molteplici, dalla sottrazione illecita di dati alla manipolazione degli stessi, fino alla e propria azione di distruzione delle informazioni.

Un esempio: fingiamo di iscriverci al sito di pura fantasia www.webapp-debole.com. Al termine della procedura d'iscrizione effettuiamo il login e veniamo riconosciuti dal sito che, immediatamente, si adegua e ci chiama per nome. C'è anche un piccolo menu per gli utenti registrati, che consente tra le altre cose di visualizzare e aggiornare i propri dati. Clicchiamo per visualizzare i nostri dati e veniamo trasferiti a una pagina che ci mostra chi siamo, l'indirizzo, i recapiti e tutto il resto. Notiamo che l'URL della pagina è http://www.webapp-debole.com/showuser.asp?id=3264: se ne deduce con facilità che la query SQL generata da questa pagina è qualcosa del tipo SELECT *FROM Utenti WHERE id = 3264.

Modifichiamo quindi manualmente l'id nell'URL del nostro browser nel modo seguente http://www.webapp-debole.com/showuser.asp?id=3263.

In assenza di ulteriori controlli, la Web application eseguirà la seguente query SELECT *FROM Utenti WHERE id = 3263 e ci mostrerà tutti i dati dell'utente che si è iscritto prima di noi. Ora sappiamo che questa Web application è debole per vari motivi: perché non effettua un controllo di coincidenza tra l'utente autenticato e i dati dell'utente richiesto e perché utilizzando id numerici sequenziali è troppo facile indovinare gli identificativi degli altri utenti.

A questo punto, se volessimo rubare l'intero database utenti del sito in questione sarebbe sufficiente iniettare una condizione booleana sempre vera nella query. Trasformiamo il nostro URL in questo modo http://www.webapp-debole.com/showuser.asp?id=3263%20OR%201=1: cosė facendo forziamo la Web application a eseguire la seguente query SELECT *FROM Utenti WHERE id = 3263 OR 1=1.

L'operazione di OR tra una qualsiasi condizione e una tautologia (espressione sempre vera) è a sua volta in una condizione che è sempre vera per ogni record contenuto nel database. Questa query porta quindi all'estrazione di tutti i dati di tutti gli utenti del database del sito. Da notare che questo tipo di attacco avrebbe funzionato anche se la tipologia di id scelta fosse stata più sicura rispetto a quella dei numeri sequenziali.

Campi hidden che non nascondono

I progettisti della nostra Web application immaginaria, avvertiti del problema, decidono di spostare il campo id dall'URL all'interno del codice HTML delle pagine cambiando da get a post il metodo per le richieste HTTP. Questo si rivela però un falso rimedio, in quanto è sempre possibile salvare la pagina, modificarla offline ed eseguire il post partendo dalla pagina modificata sul nostro computer. Questo è ancor più vero quando si tratta, per esempio, di e-commerce.

Poniamo caso di voler acquistare un prodotto del valore di 750 euro, ma di volerlo pagare solamente 7,5 euro. Al termine della procedura di acquisto, il sito produrrà una pagina con diversi campi hidden contenenti i dati necessari per la transazione da passare al Web server della banca che deve effettuare l'addebito sulla carta di credito. È un passaggio delicato, in quanto per alcuni secondi l'utente abbandona il sito del merchant e passa su quello della banca. Il sito della banca non sa a priori se i dati che gli arrivano sono stati in qualche modo modificati.

Identifichiamo dunque l'ultima pagina del sito del merchant, quella che passa i dati alla Web application bancaria e nel codice HTML rileviamo i seguenti campi

<input type="hidden" name="OrderID" value="36527">
<input type="hidden" name="Description" value="Ordine Prodotto X">
<input type="hidden" name="Amount" value="750.00">
.

Salviamo la pagina sul nostro hard disk e velocemente (prima che scada la sessione) modifichiamo il terzo campo hidden facendolo diventare <input type="hidden" name="Amount" value="7.50">.

Carichiamo la pagina dal nostro hard disk nel browser e "postiamo" i dati al Web server della banca. Come supponevamo, non viene effettuato un controllo incrociato e quindi il nostro pagamento è approvato (per un totale di 7,50 euro). Il successivo ritorno al sito di origine causerà la conferma dell'ordine numero 36527, in quanto la banca lo segnala come pagato correttamente.

I risultati su più pagine

Vi sarà capitato di visitare un sito che consente di effettuare ricerche nel database dei prodotti in vendita. Se questi sono molti, è comodo avere una visualizzazione suddivisa in più pagine, diciamo 10 prodotti per pagina. Un tipo di esposizione dei risultati molto comodo, mutuato dai motori di ricerca. Ci possiamo muovere tra una pagina e l'altra utilizzando dei pratici bottoni "precedente" e "successiva" posti in calce alla tabella dei risultati parziali. Possiamo immaginare che per presentare la sequenza di risultati corretta, la Web application esegua query differenti a seconda di quale deve essere il primo elemento della pagina e di quanti elementi desideriamo visualizzare nella singola pagina. Infatti, andando a sbirciare nel codice HTML, nel form generato per costruire la pagina successiva di risultati, notiamo il seguente campo hidden: <input type="hidden" name="start_row" value="11"><input type="hidden" name="sql" value="SELECT TOP 20 * FROM Prodotti WHERE Descrizione LIKE '%pippo%' ORDER BY id">.

Si può facilmente immaginare che cosa succederebbe se un utente del sito salvasse la pagina sul proprio hard disk e la modificasse come segue <input type="hidden" name="sql" value="DELETE FROM Prodotti">.

Questo caso è riscontrato più spesso sugli script di ricerca a più pagine, ma numerosi sono le occorrenze in cui, all'interno del codice HTML delle pagine Web (non necessariamente di ricerca multipagina), si può trovare del codice SQL esposto alla modifica da parte dell'utente.

SQL Injection tramite cookie

Visti i fallimenti esposti negli esempi precedenti, i progettisti della nostra applicazione Web decidono di riconoscere l'utente attraverso un cookie a cui è associata (lato server) una struttura contenente variabili di sessione. In questo modo sarà possibile evitare di passare informazioni importanti tramite l'URL o mediante l'uso di campi nascosti. Diciamo che quando l'utente viene autenticato, il server gli invia un cookie contenente l'id dell'utente che è stato verificato. Da quel momento in poi, il server riconoscerà quel client attraverso tale cookie, assegnandogli diritti e privilegi in base a quanto stabilito nel database per l'utente con quell'id. Una domanda sorge spontanea: anche gli utenti con privilegi amministrativi saranno nello stesso database? È probabile. Quindi modifichiamo il nostro cookie (è un comune file di testo) e cambiamo l'id con uno dei primi. È infatti abitudine degli amministratori auto-assegnarsi un id utente tra i primi disponibili. Ed ecco, infatti, che quando il nostro cookie assume il valore 11, il sito ci riconosce come administrator e diventiamo utenti plenipotenziari. Possiamo fare ciò che vogliamo, amministrare il sito, i contenuti, il database, creare, modificare o cancellare qualunque cosa. Ovviamente si tratta di un caso limite, ma è comunque esplicativo di quali potenzialità offra la manipolazione di questi elementi. Questo procedimento è conosciuto col nome di cookie tampering o anche cookie poisoning.

Come proteggersi

La minaccia affonda le proprie radici non già nella pura tecnologia, bensì nello sfruttamento di tutte quelle leggerezze o negligenze commesse dai progettisti e dai programmatori che realizzano le applicazioni Web. Da parte di chi è preposto alla realizzazione di tali strumenti è da caldeggiare l'adozione di tutti quei criteri di obfuscation tesi a evitare che qualunque frammento di codice possa essere in qualsiasi modo manipolato dagli utenti dell'applicazione.

Volendo semplificare e indicare solo alcuni punti cruciali in modo schematico:

  1. dove possibile, evitare di trasferire parametri o addirittura intere query via URL, campi hidden o cookie; a questi metodi è preferibile l'uso di cookieless session variables (variabili di sessione lato server che non fanno uso dei cookie);
  2. non utilizzare come chiave primaria di accesso ai dati un id troppo facile da ricostruire in sequenza; gli id numerici autoincrementanti sono comodi, ma rappresentano un fronte sguarnito nei confronti degli hacker;
  3. verificare sempre la correttezza formale dei parametri ricevuti rispetto a quelli attesi prima di proseguire nell'elaborazione dello script; per esempio, se attendevamo un numero (per esempio, 3263) e riceviamo una stringa (3263%20OR%201=1), sicuramente è in corso una SQL injection;
  4. verificare sempre la correttezza formale e sostanziale dei risultati delle elaborazioni rispetto a quelli attesi; per esempio, se una query deve estrarre un record, ma il recordset risultante ne contiene più di uno, allora lo script ha avuto un funzionamento anomalo e potrebbe essere in corso qualche attività non ammessa;
  5. per i siti di e-commerce: utilizzare gli hook messi a disposizione dai vari gateway bancari per effettuare la verifica server-to-server dei parametri passati da un sito all'altro. Se non corrispondono significa che sono stati modificati nel corso del passaggio dall'utente che sta tentando di effettuare l'acquisto.

In fin dei conti questa è una delle differenze principali che determinano la qualità di una Web application e conseguentemente la professionalità di chi la realizza.

La realizzazione di applicazioni Web più sicure comporta un maggior dispendio di tempo e di risorse e, conseguentemente, un maggior costo per il committente. Ma comprereste mai un'automobile sapendo che il produttore ha montato un serbatoio che potrebbe esplodere, solo perché costa meno di un'auto sicura?