Introduzione
Capita ancora troppo spesso che gli utenti di molti forum/newsgroup chiedano o propongano soluzioni che non sono sicure. Alcuni propongono concatenazione di stringhe SQL direttamente dall'applicazione, altri chiedono come fare per creare
Dynamic SQL (SQL Dinamico, in alcuni casi pericoloso).
Che cos'è il SQL Injection
Che cos’è il
SQL Injection? È la tecnica per cui un utente malintenzionato utilizza la parte di input per creare
SQL dinamico per poter accedere al database. La pericolosità dell'attacco poi aumenta se la connessione al database si ha senza definizione accurata di un livello di privilegi. Molte applicazioni infatti tendono ancora ad accedere alla base dati tramite la classica e famosa combinazione user/pass
sa/sa o utenti amministratori. Se la nostra applicazione permette
SQL Injection la sicurezza è del tutto compromessa e i danni potenziali sono infiniti.
Scenario tipico
Immaginiamo di avere una semplice applicazione che effettua ricerche su una lista di utenti, costituita da una sola pagina
.aspx con due
TextBox ed un
Button che invia i dati al server.
Inoltre avremo un database
Utili con all'interno una tabella così definita e popolata:
CREATE TABLE dbo.Utenti
(
IDUtente int IDENTITY(1,1) NOT NULL
, Nome varchar(30) NOT NULL
, Cognome varchar(30) NOT NULL
, CONSTRAINT PK_dboUtenti PRIMARY KEY CLUSTERED
(
IDUtente
)
)
GO
INSERT INTO dbo.Utenti (Nome, Cognome)
VALUES ('Alessandro', 'Alpi')
INSERT INTO dbo.Utenti (Nome, Cognome)
VALUES ('Marco', 'Rossi')
INSERT INTO dbo.Utenti (Nome, Cognome)
VALUES ('Michael', 'Denny')
INSERT INTO dbo.Utenti (Nome, Cognome)
VALUES ('Daniele', 'Zanella')
INSERT INTO dbo.Utenti (Nome, Cognome)
VALUES ('Manuele', 'Carra')
GO
La pagina è la seguente:
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtNome" runat="server" />
<asp:Button ID="btnCercaProtetto" runat="server" Text="Ricerca Protetta" OnClick="btnCercaProtetto_Click" />
<asp:Button ID="btnCercaInjection" runat="server" Text="SQL Injection" OnClick="btnCercaInjection_Click" />
<asp:GridView ID="GWRisultati" runat="server" AutoGenerateColumns="true">
</asp:GridView>
</div>
</form>
</body>
La
ConnectionString definita per connetterci al nostro
SQL Server 2005 prevede accesso tramite l'utente
sa (pratica da evitare assolutamente, ma utile per l’esempio).
Ipotizziamo inoltre di utilizzare un metodo scritto da noi per effettuare la connessione e la ricerca sulla tabella sopra definita:
private void BindRicerca(String strNome, String strConn)
{
DataTable dt = new DataTable("Risultati");
SqlConnection oConn = new SqlConnection(strConn);
SqlCommand oCmd = new SqlCommand("SELECT * FROM dbo.Utenti WHERE Nome = '" + strNome + "'", oConn);
SqlDataAdapter da = new SqlDataAdapter(oCmd);
da.Fill(dt);
GWRisultati.DataSource = dt;
GWRisultati.DataBind();
}
Come avviene un SQL Injection Attack
Eseguendo una ricerca utilizzando la nostra
TextBox txtNome avremo il risultato sperato, ovvero una griglia con tante righe quante sono quelle che hanno il campo nome che eguaglia il nostro input.
La query restituita dalla concatenazione tra il nostro codice e l’input sarà simile a questa:
SELECT * FROM dbo.Utenti WHERE Nome = 'Alessandro'Ma con una semplice modifica, un utente malintenzionato, anche se non particolarmente "skillato" in materia di database, può effettuare una richiesta pericolosissima. Immaginiamo di inserire nella TextBox il valore
"x' OR 'a'='a", ecco la
SELECT risultante:
SELECT * FROM dbo.Utenti WHERE Nome = 'x' or 'a'='a'Come si vede subito la query risponderà sempre con tutto l'elenco dei record della tabella, in quanto
'a'='a' è una condizione sempre vera.
Questo è un primo esempio, molto semplice ma allo stesso tempo molto pericoloso. Pensate solo ad una query che deve controllare la login di un utente durante la fase di autenticazione. Normalmente la query si aspetta come risultato una sola riga. A questo punto concatenando statement SQL opportunamente seguendo l'esempio precedente è possibile avere accesso ad un sito Internet o ad un'area riservata amministrativa senza conoscerne la password. Questo esempio non viene mostrato perchè l'articolo non è un Tutorial o una guida per diventare
Hacker ma è a scopo didattico.
Ricordiamo inoltre che la
ConnectionString è basata su
"sa" e quindi un
sysadmin, con il massimo delle permission. Proviamo ora a mettere questa la stringa come Input:
"x’; DROP TABLE dbo.Utenti; --"nella textbox. Eseguiamo la prima ricerca senza ottenere nulla e appena lanciamo la seconda ?
N.B. Ricordiamo che tramite l'uso del punto e virgola
(;) è possibile concatenare più statement SQL da passare in un'unica volta a SQL Server. Mentre l'uso del doppio meno
(--) consente di commentare/remmare ciò che viene dopo nella stringa di comandi, quindi ignora completamente eventuali statement SQL successivi.
L'errore, che probabilmente gli sviluppatori ed i DBA non avevano previsto è drammatico. La tabella
dbo.Utenti non esiste più. È vero che per trovare il nome della tabella, bisogna fare alcuni tentativi (o usare altri metodi di
SQL Injection). Però una volta arrivati lì, il gioco è fatto ed è grave che un utente che naviga possa arrivare fino a quel punto. Fortunatamente esistono tanti rimedi.
Come difendersi dal SQL Injection
Come fare? L’oggetto è andato perduto. Se esiste un piano di
Backup, il restore ci permette di ricaricare lo stato del database all'ultimo backup effettuato. Ma tutto quello che intercorre tra quest'ultimo e l'attacco dell'utente malintenzionato è andato perso completamente.
Quindi la vera risposta è pensare a come difendersi la prossima volta. E le soluzioni ci sono:
1) Stored Procedures
Col passaggio di parametri si riduce di molto l'attacco di malintenzionati, ma è necessario richiamare le
Stored Procedures sfruttando la collection
Parameters dell'oggetto
SqlCommand e non tramite codice SQL (CommandType = Text). Nel secondo caso infatti è possibile passare input non filtrato per effettuare con semplicità un attacco
SQL Injection.
La
Stored Procedure potrebbe essere simile a questa:
CREATE PROCEDURE dbo.proc_GetUsers
(
@Nome varchar(20)
)
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM dbo.Utenti
WHERE Nome = @Nome
END
La chiamata da codice sarà questa:
private void BindRicercaSP(String strNome, String strConn)
{
DataTable dt = new DataTable("Risultati");
SqlConnection oConn = new SqlConnection(strConn);
// nome Stored Procedure
SqlCommand oCmd = new SqlCommand("dbo.proc_GetUsers", oConn);
// Tipo comando Stored Procedure
oCmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(oCmd);
// collection Parameters
oCmd.Parameters.Add("@Nome", SqlDbType.VarChar, 20).Value = strNome;
Response.Write(oCmd.CommandText);
da.Fill(dt);
GWRisultati.DataSource = dt;
GWRisultati.DataBind();
}
2) Query parametriche
Allo stesso modo l'attacco viene ridotto se si utilizzano le
Query Parametriche. Indicare parametri con i PlaceHolder "@nomeparametro" ed utilizzare la collection
Parameters dell'oggetto
SqlCommand è necessario per ridurre la superficie di attacco.
Ecco come modificare il nostro codice per le
Query Parametriche:
private void BindRicerca(String strNome, String strConn)
{
DataTable dt = new DataTable("Risultati");
SqlConnection oConn = new SqlConnection(strConn);
// parametro indicato con @NomeParametro
SqlCommand oCmd = new SqlCommand("SELECT * FROM dbo.Utenti WHERE Nome = @Nome", oConn);
SqlDataAdapter da = new SqlDataAdapter(oCmd);
// collection Parameters
oCmd.Parameters.Add("@Nome", SqlDbType.VarChar, 20).Value = strNome;
Response.Write(oCmd.CommandText);
da.Fill(dt);
GWRisultati.DataSource = dt;
GWRisultati.DataBind();
}
3) Utilizzo degli oggetti forniti da Visual Studio e dal Framework
Alcuni oggetti, come il
SQLDataSource o l'
AccessDataSource, forniscono un buon livello di protezione da attacchi di tipo
SQL Injection. Quindi, appena possibile, si consiglia il loro utilizzo con la definizione dei
Parameters:
<asp:SqlDataSource ID="SQLDS1" runat="server" SelectCommand="SELECT * FROM dbo.Utenti WHERE Nome = @Nome" ConnectionString="<%$ ConnectionStrings:UtiliConnectionString %>">
<SelectParameters>
<asp:ControlParameter ControlID="txtNome" Name="Nome" />
</SelectParameters>
</asp:SqlDataSource>
4) Validazione dell'Input
La gestione della sicurezza di un'applicazione e del rischio di eventuali danni dall'esterno non è una cosa che si risolve rapidamente come per accendere una luce, sicurezza on/sicurezza off, è una cosa graduale che si raggiunge man mano nel lifecycle della costruzione di un'applicazione e bisognerebbe tenerne conto sin dall'inizio quando si disegna o progetta l'applicazione stessa.
La validazione dell'Input dell'utente riveste un ruolo di fondamentale importanza, perchè bloccando già lì eventuali tentativi d'attacco, si interrompe subito la catena che può portare danni nei livelli inferiori.
Per esempio è sempre buona norma nelle applicazioni Web ASP.NET e non applicare la proprietà
MaxLength alle
TextBox e quindi limitare il numero di caratteri. In questo modo eventuali query maligne o malformate non riuscirebbero a passare perchè troncate alla sorgente.
Utilizzare i
Page Validators è un'altra tecnica molto utile. Se abbiamo una
TextBox che accetta in Input un
CAP quindi numerico, mi aspetto che il mio validatore
Javascript verifichi che nella
TextBox ci siano 5 numeri nè più nè meno.
Qualcuno potrebbe semplicemente disabilitare il
Javascript e bypassare la validazione Client-side. Fortunatamente via codice ASP.NET si può comunque richiedere una validazione di tutti i validatori della Webform richiamando il
Page.IsValid e verificare prima di procedere oltre se è tutto ok.
5) Livello di sicurezza ed autorizzazione su database
Progettare bene il livello di sicurezza e di accesso degli utenti al database è una pratica fondamentale che chi progetta l'architettura del database e la sua security dovrebbe tenere sempre a mente.
Come abbiamo visto esistono vari modi per evitare il
SQL Injection.
Vorrei ripetere che è del tutto fondamentale la progettazione di un livello di sicurezza direttamente sulla base dati, con tanto di definizione di utenti e ruoli e con diversi livelli di autorizzazione/profilazione.
Molti di noi trovano che quest'ultima parte sia "inutile", ma al contrario è fondamentale per la protezione delle proprie applicazioni. E' un po come per i backup, è certamente inutile fare il backup, perchè richiede tempo e voglia, ma tutta l'utilità viene recuperata istantaneamente nel momento in cui perdiamo tutti o parte dei dati.
Il caso del Virus da SQL Injection
Un'errata programmazione delle applicazioni sul fronte della security ha consentito per esempio al virus o pseudo-virus di cui parlo adesso di creare ingenti danni a livello planetario. Il funzionamento del virus è molto banale, sfrutta la
SQL Injection e fa delle chiamate
HTTP o POST alle pagine modificando i valori dei parametri e inserendo un blocco di codice
T-SQL opportunamente modificato e codificato in
Base64 che una volta in esecuzione enumera tutte le colonne di tipo
text,varchar,char e omologhi
Unicode e va ad appendere al testo di ogni campo uno script
Javascript che una volta renderizzato nella pagina Web a sua volta dovrebbe collegarsi in remoto e installare un ulteriore virus sulla macchina locale o comunque qualcosa di maligno che probabilmente aumenta la diffusione del fenomeno stesso su scala mondiale. Qui trovate un post su dove provengono gli attacchi a livello geografico:
Analisi geografica degli IP del virus da SQL Injection Attacks (Blog MVP David De Giacomi) Il Virus in sè non causa gravi danni, se non il fatto che va a "sporcare" il database e nel caso dei campi
text fa un
CAST a varchar(4000) quindi tronca il testo e quindi vi fa perdere parte del contenuto del campo. Quindi in questo caso bisogna avere un backup a disposizione per recuperare i dati perduti.
Il meccanismo del Virus è ben spiegato in questo Bullettin:
SANS SQL Injection: More of the same Tools per combattere il Virus SQL Injection su SQL Server
Dopo che il danno è stato fatto e non si riesce in breve tempo a fixare tutti i problemi e le falle di sicurezza bisogna cercare rapidamente di arginare il problema, ma in che modo ?
Per difendersi da questo pseudo-virus anche
Microsoft ha dovuto rapidamente e/o aggiornare dei tools per i suoi clienti colpiti ripetutamente da questi attacchi.
Ripulitura del Database SQL ServerL'esecuzione di questo Virus "sporca" il database, riempiendo tutti i campi
text,varchar,char e omologhi
Unicode, con del codice HTML. La prima cosa da fare è restorare un backup recente oppure eseguire una ripulitura manuale del database ove possibile. La ripulitura può essere fatta riga per riga (se poche sono le righe/tabelle infettate) oppure semi-automatica, sfruttando la stessa tecnica del virus ma al contrario, con l'uso dei cursori, del
Dynamic SQL e enumerando le colonne e le tabelle usando le tabelle di sistema (
syscolumns e
sysobjects) facendo dei
REPLACE. Se non siete capaci di eseguire un'operazione del genere meglio farla fare ad altri perchè potreste creare più danni di quelli che già ci sono.
URLScan Filter 3.0 BetaQuesto tool consente di filtrare, limitare e bloccare certi tipi di
HTTP Request che arrivano su IIS. Per installarlo e utilizzarlo è necessario avere il controllo del server su cui è ospitata la nostra soluzione. Nel caso di spazi shared hosting ciò non è possibile a meno chè il vostro Hoster provveda ad installarlo e a configurarlo per voi.
Download URL Scan Filter3.0 Beta Istruzione per l'uso di URL Scan Filter Microsoft Source Code Analyzer for SQL InjectionUn Tool per analizzare pagine .asp statiche e rilevare eventuali punti critici e vulnerabilità che possono compromettere la sicurezza e la stabilità dell'applicazione web. Stranamente pare che non analizzi pagine
ASP.NET ma solo le vecchie
ASP 3.0. Richiede installato sulla macchina il
.NET Framework 3.0.
Se ne parla anche sul mio blog e su quello del collega
MVP Lorenzo Benaglia:
Prova di Source Code Analyzer per SQL Injection (Blog MVP Alessandro Alpi) Source Code Analyzer for SQL Injection (Blog MVP Lorenzo Benaglia) Download URL Scan Filter3.0 Beta Istruzione per l'uso di Source Code Analyzer for SQL Injection Costruzione di un HttpModule che filtri le richieste HTTPSe non avete accesso a
IIS potete sempre costruirvi un
HttpModule personalizzato in .NET che fa praticamente la stessa cosa dell'
URLScan Filter 3.0 e lo potete deployare facilmente nella root della vostra applicazione web e abilitare. In questo modo eventuali richieste non conformi possono essere bloccate.
Costruzione di un HttpModule che blocchi a livello di IPIn questo caso questa tecnica non dà nessun risultato significativo, perchè il virus si distribuisce su vari client (attacco distribuito) nel mondo quindi gli attacchi possono provenire da blocchi di IP appartenenti a nazioni diverse, quindi si rischierebbe di tagliare fuori dal proprio sito anche ignari utenti che appartengono a un certo
IP netblock bloccato. C'è da dire però che a seguito di un'analisi fatta sugli IP che effettuano gli attacchi si può desumere facilmente che questi provengono da stati non industrializzati ma emergenti dove probabilmente non c'è nessun tipo di legislazione in merito.
Qui trovate una dimostrazione del perchè una soluzione basata sul blocco degli IP potrebbe non essere efficiente per questo tipo di minaccia:
Analisi geografica degli IP del virus da SQL Injection Attacks (Blog MVP David De Giacomi) Link Utili sulla sicurezza delle applicazioni e sul SQL Injection
Per finire vorrei indicare alcuni link utili a mio avviso da leggere se non si ha bene a mente come si scrive codice sicuro e quindi non violabile:
Creazione di codice di accesso ai dati protetto Injection Protection Preventing SQL Injections in ASP How To: Protect From SQL Injection in ASP.NET Coding techniques for protecting against Sql injection Filtering SQL injection from Classic ASP SQL Injection Attack Un articolo del collega italiano
MVP Raffaele Rialdi:
Una piaga chiamata SQL Injection E per finire ancora da un collega
MVP Erland Sommarskog un trattato sul
Dynamic SQL molto interessante:
The Curse and Blessings of Dynamic SQL Disclaimer
Tutte le tecniche mostrate in questo articolo sono a solo scopo didattico e tese a sensibilizzare
Developers, WebMasters e DBA al tema della
Security e a migliorare la loro conoscenza in questo ambito. Gli attacchi portati a termine dal Virus o manualmente da voi comportano comunque un danno per chi li riceve e nonostante il fatto che questi attacchi possano essere messi a segno su applicazioni non sicure o dove c'è qualche falla aperta a causa della scarsa competenza o disattenzione di chi le ha sviluppate, ciò non toglie che qualcuno potrebbe richiedervi un risarcimento danni, quindi prestate attenzione alle vostre azioni e magari evitate di ritrovarvi a dover risarcire il danno con gli sviluppatori incauti che hanno sviluppato l'applicazione.
;-)