#25 SQL Injection
Hai davvero chiamato tuo figlio Robert'); DROP TABLE Students; -- ?
- XKCD No. 327
Nei prossimi episodi parleremo di sicurezza e di protezione del sito dagli hacker. Una delle prime regole della sicurezza è quella di non fidarsi mai dei dati immessi dagli utenti. In Rails ciò significa prendersi cura degli elementi all’interno dell’hash params. L’utente ha potenzialmente il controllo sia sulle chiavi, sia sui valori dell’hash params, per cui tutti i dati provenienti dall’utente sono da considerarsi sospetti. Lo stesso dicasi per l’hash dei cookie; l’utente ha il pieno controllo del suo contenuto. Di contro, l’hash di sessione è impostato solo lato server, per cui di quello possiamo fidarci.
Una delle più classiche problematiche di sicurezza è nota con il nome di SQL injection. Questo genere di problema emerge quando nei dati immessi dall’utente compare direttamente uno statement SQL. Se l’utente fosse a conoscenza del fatto che i dati immessi vengono direttamente trasposti all’interno di una query, potrebbe deliberatamente compilare un campo di una certa form in modo tale da sovrascrivere ciò che in condizioni normali non dovrebbe poter nemmeno vedere o peggio cancellare interi dati. Spieghiamo come simili attacchi funzionano e anche come proteggerci con un esempio semplice.
Recupero dei Task
Qui sotto abbiamo una semplice form di ricerca in una pagina che esegue un recupero SQL LIKE per caricare i task con un nome che corrisponda approssimativamente a quanto inserito dall’utente in maschera.
Il problema è che l’input fornito dall’utente viene direttamente passato nel nostro controller, alla query sul database.
class TasksController < ApplicationController def index @tasks = Task.find(:all,:conditions=>"name LIKE ’%#{params[:query]}%’") end end
Il TasksController che mostra la query potenzialmente vulnerabile.
Il valore per la chiave :conditions
nell’hash della find
è passato direttamente alla query SQL. Il problema di questo approccio è che se l’utente passa come parametro una stringa che contiene un apice, il resto dello statement è considerato SQL. Per cui, se immettessimo un termine di ricerca contenente un apice, per esempio Task 1’TEST, tutto ciò che si troverebbe oltre l’apice verrebbe trattato come SQL e Rails solleverebbe un errore.
Processing TasksController#index (for 127.0.0.1 at 2009-02-01 21:29:26) [GET] Parameters: {"query"=>"Task 1’TEST"} Task Load (0.0ms) SQLite3::SQLException: near "TEST": syntax error: SELECT * FROM "tasks" WHERE (name like ’%Task 1’TEST%’) ActiveRecord::StatementInvalid (SQLite3::SQLException: near "TEST": syntax error: SELECT * FROM "tasks" WHERE (name like ’%Task 1’TEST%’) ):
Il codice SQL non valido generato dalla immissione, da parte dell’utente, di un apice nella stringa di ricerca.
Nel comando SQL precedente abbiamo il nostro termine di ricerca, Task 1’TEST; e possiamo vedere come l’apice in esso contenuto completi lo statement SQL. La parte rimanente a destra, , TEST%’), non è SQL valido, da cui l’errore. Tutto ciò è molto pericoloso poichè significa che potenzialmente qualsiasi codice SQL potrebbe essere mandato in esecuzione sul database compilando opportunamente la maschera web. Come tappiamo questa falla?
La risposta è: facciamo l’escape degli apici. Rails fornisce un modo semplice per farlo. Possiamo passare un array di opzioni, il primo elemento del quale è la nostra condizione con il valore sostituito da un punto interrogativo. Ogni punto interrogativo all’interno del primo elemento sarà attualizzato con il valore degli ulteriori elementi dell’array, sequenzialmente, ma processati con l’escape, in modo tale da renderli sicuri. Se avessimo, per dire, tre parametri, avremmo tre punti interrogativi all’ interno della stringa passata come primo argomento e altri tre elementi che rappresenterebbero i valori attuali della query: in totale l’array conterrebbe quattro elementi.
@tasks = Task.find(:all, :conditions=> [ "name LIKE ?", "%#{params[:query]}%" ]
Un modo più sicuro per passare i parametri di ricerca passati dall’utente.
Ora che abbiamo aggiornato la nostra query, se effettuiamo nuovamente una ricerca usando un termine contenente un apice, Rails ne farebbe l’escape in modo sicuro. Per essere certi che nella nostra ricerca LIKE sia mantenuto, il simbolo percento va posto esternamente alle parentesi graffe di escaping. In caso contrario non funzionerebbe perchè ne verrebbe fatto l’escape.
Il codice SQL nei log ora appare così: Processing TasksController#index (for 127.0.0.1 at 2009-02-01 21:59:31) [GET] Parameters: {"query"=>"Task 1’TEST"} Task Load (0.5ms) SELECT * FROM "tasks" WHERE (name like ’%Task 1’’TEST%’)
Il termine di ricerca è ora gestito correttamente.
Ora possiamo notare come l’apice sia correttamente trattato. Si noti anche come, poichè stiamo usando Sqlite come database, l’apice venga tradotto con due apici singoli. Altri database potrebbero usare un backslash prima dell’apice per ottenere lo stesso risultato.
In Rails non c’è bisogno di preoccuparsi di gestire l’escape dell’input dell’utente destinato ad una find SQL, se si utilizza il parametro :conditions. Se la query viene fatta mediante metodi dynamic find_by visti in precedenti episodi (episodio 2), Rails farebbe automaticamente l’escape di ogni input, per garantire protezione rispetto ad attacchi di SQL injection.