In questo brevissimo articolo volevo condividere con voi una “riflessione profonda”: ma è proprio vero che non con gli strumenti moderni (Es. Visual Studio.NET) e con le tecnologie moderne (ES. ASP.NET) chiunque è in grado di scrivere un’applicazione funzionante ?
Probabilmente la risposta è si: con i 30.000 wizard e le “autocreazioni” di form è diventato veramente semplice scrivere un’applicazione.
Spesso però, utilizzando in modo “approssimato” tali strumenti e/o tecnologie si ottengono risultati mediocri, sia in termini di performance, che di scalabilità, che di protezione delle informazioni.
Sappiamo tutti che, utilizzando un database server serio come SQL Server, è bene utilizzare le stored procedure per accedere in lettura/scrittura alle informazioni. E’ assolutamente vero che perdiamo un po’ di tempo nella scrittura dell’applicazione rispetto a scrivere gli statement SQL direttamente nel codice delle pagine, ma è altrettanto vero che i benifici che ne derivano sono molteplici: velocità di esecuzione, riutilizzo di SP da più applicazioni, migliore tipizzazione delle informazioni in entrata, migliore leggibilità dei parametri solo per citarne alcuni.
Fin quì non credo di avervi detto niente di nuovo. Volevo porre all’attenzione un problema, già conosiciuto da chi sviluppa/sviluppava nelle precedenti versioni di ASP e in generale da coloro che sviluppa per il web.
Un’applicazione Web è soggetta ad attacchi di vario tipo: si parte dall’accesso ai servizi, per passare all’accesso alle informazioin fino ai vari tipi di Denial of Service.
Anche se il nostro sistema hw e sw è a prova di bomba (si fa per dire, non esiste un sistema veramente veramente sicuro) è probabile che proprio noi sviluppatori apriamo delle brecce insospettabili per l’ammistratore del sistema e per il sistema stesso J
Mi riferisco ad esempio ai controlli sui dati che un utente può inserire nelle nostre Web Forms ed in particolare a quanto vedremo in questo articolo, che vuole essere semplicemente una riflessione.
SQL Injection significa iniettare del codice SQL in una applicazione. Questa tecnica di attacco è solitamente praticabile solo quando lo sviluppatore si è dimenticato di effettuare alcuni controlli sulle pagine che accedono ai dati e quando lo sviluppatore accede ai dati in modo non molto ortodosso.
Partiamo come sempre da un esempio semplice per poi complicare lo scenario.
Ad esempio, ho visto molte pagine ASP.NET (ma anche ASP 3.0) con il seguente codice:
Dim strSQL as String = “SELECT * FROM Customers WHERE CustomerID = ‘” & txtClient.Text & “’”
Probabilmente la pagina ASP.NET chiede all’utente (supponiamo per adesso) il codice del cliente all’interno di un campo Textbox della Web Form.
Il codice della pagina ASPX è il seguente
FILE: SIMPLE.ASPX
...
<form id="Form1" method="post" runat="server">
<p>Codice Cliente
<asp:TextBox ID="txtCliente" Runat="server"></asp:TextBox>
<asp:Button ID="btnCerca" Runat="server" Text="Cerca" />
<asp:DataGrid AutoGenerateColumns="True" ID="dgCustomer" Runat="server" />
</form>
...
Mentre il codice del CodeBehind è il seguente
FILE: SIMPLE.ASPX.CS
private void btnCerca_Click(object sender, System.EventArgs e)
{
string sConn = “Server=XXX; uid=...”
SqlConnection oConn = new SqlConnection(sConn);
string strSQL = "SELECT * FROM Customers WHERE CustomerID = '" + txtCliente.Text + "'";
SqlCommand oCmd = new SqlCommand(strSQL, oConn);
oConn.Open();
SqlDataReader oReader = oCmd.ExecuteReader(CommandBehavior.CloseConnection);
dgCustomer.DataSource = oReader;
Page.DataBind();
oReader.Close();
oConn.Close();
}
Quando l’utente preme il pulsante “Ricerca” la pagina estrae il record corrispondente con lo statement sopra indicato e ritorna i dati in una data list.
E senza grosse formattazioni la pagina si presenta così:
L’utente potrebbe prova a digitare il simbolo percentuale “%” per cercare di estrarre tutti i record della tabella Customer. Fortunatamente il nostro “super codice” non contiene nessuna clausola LIKE, quindi l’utente non otterrebbe nessun record in risposta.
Se però l’utente, invece di inserire un ID di cliente (ANTON nel nostro esempio), inserisce questa simpatica stringa
ANTON' OR CustomerID LIKE '%
Vi immaginate cosa accade nella nostra pagina ?
Il risultato della stringa SQL che la nostra pagina invia a SQL Server sarebbe
SELECT * FROM Customers WHERE CustomerID = ‘ANTON' OR CustomerID LIKE '%’
L’output della pagina è ovvio J
E’ vero che l’utente maligno deve conoscere il nome del campo della nostra tabella per impostare la corretta clausola LIKE, ma è altrettanto vero che il cosiddetto Lazy Programmer (letteralmente Programmatore Stanco...o scarpone J) costruisce la tabella SQL con un nome di campo facile da ricordare che probabilmente corrisponde al nome della colonna della tabella da presentare all’utente.
Se il programmatore ha usato nomi tipo AS/400 il problema non si presenta J
Probabilmente il campo CodiceCliente si chiamerebbe “ACCC01A”...è anche vero però che esistono delle convenzioni, di conseguenza protrebbe addirittura essere più semplice scoprirli J
Vediamo come risolvere il problema di scoprire come si chiamano i campi della tabella sorgente
Provate la seguente stringa
ANTON’ OR 1=1 --
Il risultato è “perfetto”, e senza conoscere i nomi dei campi.
N.B. i due trattini (--) sono la sintassi per definire un commento in T-SQL. Nel caso di Oracle basta usare un “;” (punto e virgola) e per MySQL è sufficente un “#” (cancelletto, o sharp come va di moda adesso)
Ma, l’attacco SQL Injection, consente all’hacker di provare a scoprire qualcosa in più sul nostro database server.
La prima tecnica si chiama SQL Union Attack e consiste nel cercare di estrarre dal db informazioni di altre tabelle (il nome Union deriva proprio dalla omonima clausola SQL).
Proviamo subito a digitare nella textbox CodiceCliente la seguente stringa
ANTON’ UNION SELECT @@SERVERNAME –
La stringa SQL inviata al database sarà quindi
SELECT * FROM Customers WHERE CustomerID = ‘ANTON' UNION SELECT @@SERVERNAME--
Vediamo come appare la nostra Web Form di esempio, anzi risparmiatevelo perchè in questo caso viene generato un errore, ma il motivo non è quello che si pensa e cioè “non si potrà chiedere il nome del server a SQL”.
L’errore deriva semplicemente dal fatto che la seconda select non ritorna un numero uguale di colonne rispetto alla prima. Non è però difficile scoprire il numero delle colonne: basta contare il numero delle colonne presentate a video e ripetere n volte @@servername nel campo textbox. Anche andare a tentativi non è poi così difficile.
Scrivendo nel nostro caso 11 volte @@servername, il risultato è il seguente
Provate anche con @@ServiceName e con @@Version...è divertente.
Con @@Version ad esempio il buon databse ritorna la sua versione, la piattaforma con la relativa versione e build.
Ad esempio “Microsoft SQL Server 2000 - 8.00.534 (Intel X86) Nov 19 2001 13:23:50 Copyright (c) 1988-2000 Microsoft Corporation Developer Edition on Windows NT 5.1 (Build 2600: )”
Così sapete come si chiama la macchina con cui abbiamo scritto questo articolo, nonchè la versione del nostro SQL Server e il nostro sistema operative (NT 5.1 è Windows XP)
La soluzione al problema non esiste in un wizard o un automatismo tecnologico e quindi implica la scrittura di qualche riga di codice. In fondo è il nostro lavoro ed è spesso quello che differenzia un’applicazione che crasha o da problemi da un’applicazione seria e con bassa manutenzione.
Un semplice metodo e come tale non così efficace può essere semplicemente impostare la lunghezza massima della TextBox.
Mettiamo quindi la maxlength HTML nel campo textbox, ma invece di provare a vedere se funziona, ricordiamoci che una controllo client-side può essere eluso facilmente da un utente medio. Anche inserendo script lato client nessuno impedisce all’utente di salvare la pagina sul suo disco, rimuovere tutti i controlli, ricarica la pagina dal suo disco e farci una POST Http con le informazioni che vuole. Anche senza questo giro, può simulare una POST con un client diverso dal browser.
Abbiamo capito che il controllo delle informazioni va fatto sempre anche server-side, unico meccanismo per evitare le tecniche di spoofing più comuni.
Non esistendo un Validator Control in ASP.NET che controlli la lunghezza di un campo, dobbiamo procedere a mano controllando, dove possibile, la lunghezza della stringa di input.
E’ assolutamente ovvio che questo controllo lasci un po’ il tempo che trova, visto che già un campo lungo 11 caratteri consente all’utente di scrivere “A OR 1=1 –“ nella input box.
Di conseguenza la soluzione è controllare che non siano presenti caratteri speciali (!,;,@,#,-, etc etc) oppure clausole SQL (UNION, AND, OR, etc etc).
In questo caso esiste un Validator Control che ci può dare una mano per il controllo dei caratteri digitati in un campo input: RegularExpressionValidator. Ricordiamo che tutti i Validator Control effettuano anche i controlli server-side.
Scrivere le regular expression non è così banale come scrivere un po’ di if nel codice, ma sicuramente ne guadagnamo in pulizia ed eleganza di codice. Scrivere 39 if nel click di un pulsante non rende il nostro codice così leggibile.
Ecco un esempio di Regular Expression
[^/=%-]*
che consente di controllare che non esistano i seguenti caratteri in una stringa:
/ = % - (Slash, Uguale, Percento, Meno)
(N.B. Ricordatevi di mettere il “–“ come ultimo carattere essendo un carattere speciale)
Applicando questa espressione alla proprità ValidationExpression del controllo RegularExpressionValidator abbiamo risolto il problema, almeno per adesso....
Nel prossimo articolo infatti vedremo qualche altro tipo di SQL Injection e speriamo....la soluzione ad essi.