Pagine

domenica 2 dicembre 2012

SQL Injection...

Girovagando qua e la in internet sono capitato su un sito fatto da un amico.... Perchè non provare un po di sana SQL Injection mi sono detto !! ;-)
Con stupore ho visto che il sito non era protetto. Non essendo un vero malintenzionato ho avvisato subito l'interessato che ovviamente ha provveduto, dopo una bella sequenza di insulti amichevoli rivolta al sottoscritto, a mettere in sicurezza l'applicazione.
Ma cosa è la Sql Injection ? E' una tecnica molto diffusa e conosciuta utilizzata dagli hacker per penetrare nelle web app e non solo e carpirne i dati etc...
Come funziona ? Sfruttando il codice SQL utilizzato dall'RDBMS su cui si appoggia l'applicazione

Immaginiamo una Web app intranet o internet che si connette a SQL Server e richiede uno username ed una password per funzionare. Tali dati sono censiti in una tabella del database utilizzato.

Come posso scoprire se l'app è protetta dalla Sql Injection oppure no ?
Semplice, inserendo il carattere ' (apice singolo) in una text box che si presume utilizzata per "completare" un comando SQL.

Se ottengo un errore simile a "Unclosed quotation mark after the character string " o qualcosa comunque correlato all'RDBS sottostante, ho buone probabilità di penetrare in modo truffaldino nell'app.

Faccio un esempio.
Creo un database contenente una tabella repository delle credenziali d'accesso usate dall'app ed inserisco un paio di credenziali valide.

Create database MyAppDb
Go

Use MyAppDb
Go

Create Table UsersTab
(
 Ulogin varchar(25),
 UPwd varchar(25)
)
Go

Insert into UsersTab values('Luca','LucaPwd')
Go
Insert into UsersTab values('Mauro','MauroPwd')
Go

La pagina iniziale della Web App che accederà al DB è la seguente


Per rendermi più semplice il lavoro ho creato 3 button di Logon, uno NON protetto e 2 protetti in modi differenti che vedremo poi...

Provo a inserire nella textbox LoginId il carattere ' (apice singolo)

Se ottengo un errore (gestito o no dipende dal codice utilizzato nell'app) simile a questo allora posso tentare la Sql Injection, altrimenti il giochino si ferma perchè l'app è protetta.


Come dicevo sopra l'errore è "Unclosed quotation mark after the character string..."

Ma perchè ho ottenuto questo errore ?
Perchè il codice dell'applicazione non è protetto.

Vediamo come è il codice del button "Logon NON protetto"
string strCon = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
            SqlConnection con = new SqlConnection(strCon);
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = "SELECT * FROM UsersTab WHERE Ulogin = '" + TxtLogin.Text + "' AND UPwd = '" + TxtPwd.Text + "'";

            con.Open();
            int result = (Int32)cmd.ExecuteNonQuery();
            con.Close();

            if (result != 0)
            {
                Session.Add("Query", cmd.CommandText);
                Response.Redirect("AppMainPage.aspx");
            }
            else
                lblMessage.Text = "Utente o Pwd non validi";


Come vedete compongo una stringa T-SQL inserendo direttamente le credenziali come parte integrante delle condizioni di ricerca.
Il risultato è che, inserendo il carattere ' in un textbox, a SQL Server giunge questa query che è evidentemente errata :
SELECT * FROM UsersTab WHERE Ulogin = ''' AND UPwd = ''

Vediamo come posso sfruttare a mio vantaggio questa falla ( Mi sono immedesimato in un hacker ;-) ).
Un hacker non conosce il codice dell'app ma è probabile,a questo punto, che l'app vada a ricercare nel DB la corrispondenza tra le credenziali fornite dall'utente e quelle presenti nel Db. Come posso fare in modo che il test delle credenziali vada a buon fine ? inserendo una condizione di ricerca sempre vera: "'Or 1=1 --" fa al caso mio. (Il -- serve per trasformare in commento tutto ciò che viene dopo 1=1 nel T-SQL)

Provo ora ad inserire ' or 1=1 -- nella textbox Login Id.

Ecco il risultato :


Sono riuscito a penetrare nell'app.....
Ok, sono entrato, ma voglio saperne di più sul Db e SQL Server.....
Come si chiama il DB ? Quale è l'utente utilizzato dall'app per connettersi a SQL e a quale ruolo appartiene nell'istanza ?!?!

Facile ora che ho capito che l'app non è sicura....

Cominciamo con lo scoprire il nome del DB

Declare @Dbname sysname
Select @Dbname=Db_Name(DB_id())
raiserror(@Dbname),16,)

Inserendo questo comando scopro il nome del DB e me lo faccio restituire attraverso un errore...


Ecco l'errore che mi restituisce il nome del DB MyAppDb:


E per scoprire l'utente utilizzato nella ConnectionString ?

Uso la stessa tecnica, mi faccio restituire un errore contenente il nome

Declare @SqlLogin varchar(25)
Select @SqlLogin = suser_sname()
raiserror(@SqlLogin,16,1)


Ed ecco l'errore con il nome del Login usato per accedere al server


In effetti MySQLLogin è il Login SQL Server che ho diligentemente inserito nella Connectionstring configurata nel Web Config


    
  

Con questa tecnica posso scoprire praticamente ogni cosa relativa all'istanza e al login utilizzato per connettersi.
Se fossi un hacker e scoprissi che il login è system admin di SQL Server avrei la possibilità di implementare ogni attività possibile nell'istanza... anche il Drop dei DB, lo shutdown etc.... E' indispensabile che l'utente che effettua la connessione sia dotato del più basso livello possibile di privilegi al fine di limitare al max i danni possibili.....

Come posso scoprire però la struttura della tabella contenente le credenziali per accedere all'app?
Sempre attraverso gli errori....

In questo caso non serve usare la raiserror mi basta sfruttare la sintassi

' Or 1=1 Having 1=1-- fa al caso mio.... Having la posso utilizzare solo se preceduta da group by. In questo caso quindi SQL mi restituirà un errore contenente il nome della tabella e della prima colonna in output dalla query...


Ed ecco il nome della tabella e della prima colonna....


Ora con un po di pazienza scopriamo tutti i campi della tabella... Come ?
correggendo di volta in volta la query, cioè inserendo anche la keyword Group By..


Ecco la seconda colonna....


Conosciamo ora la struttura completa della tabella.
Siamo in grado di accedere all'app come ladri attraverso la finestra.... Perchè non uscire dalla porta portandosi via le chiavi ?

Se inserisco delle nuove credenziali nella tabella UsersTab la prossima volta non dovrò "faticare" per accedere all'app.....

Mi basta questa istruzione :
Insert into UsersTab values('Hacker','Hacker')



Ora ho a disposizione un utente tutto mio per accedere.... ;-)




Se lo user nel DB ha poi dei privilegi molto elevati potrei anche cancellare le tabelle, crearne di nuove, modificarle, svuotarle, eliminare il Db intero e molto altro....
Usiamo sempre il più basso livello possibile di privilegi per user configurati sui nostri Db.


Come posso fare per tutelarmi ?
Scrivendo codice che non preveda un'interazione diretta tra T-SQL applicativo e valori inseriti dall'utente.

Prendiamo il codice del button "LogOn Protetto via codice"

string strCon = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
            string strSelect = "SELECT COUNT(*) FROM UsersTab WHERE Ulogin = @Username AND UPwd = @Password";

            SqlConnection con = new SqlConnection(strCon);
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = strSelect;

            SqlParameter username = new SqlParameter("@Username", SqlDbType.VarChar, 25);
            username.Value = TxtLogin.Text.Trim().ToString();
            cmd.Parameters.Add(username);

            SqlParameter password = new SqlParameter("@Password", SqlDbType.VarChar, 25);
            password.Value = TxtPwd.Text.Trim().ToString();
            cmd.Parameters.Add(password);

            con.Open();
            int result = (Int32)cmd.ExecuteScalar();
            con.Close();

            if (result == 1)
            {
                Session.Add("Query", cmd.CommandText);
                Response.Redirect("AppMainPage.aspx");
            }
            else
                lblMessage.Text = "Utente o Pwd non validi";

La differenza principale con quello del pulsante analizzato precedentemente è che quanto inserito dall'utente viene passato come parametro alla query SQL. In questo modo mi proteggo dalla SQL Injection. Provo ad utilizzare la tecnica illustrata qui sopra relativa alla condizione ' Or 1 = 1 --


Il codice non mi lascia passare. Il valore ' Or 1 = 1 -- non ha effetto perchè la stringa digitata viene fornita come parametro alla query, il che significa che a SQL arriva
(@Username varchar(25),@Password varchar(25))
SELECT COUNT(*) FROM UsersTab WHERE Ulogin = @Username AND UPwd = @Password.

L'unico modo che ho per accedere è utilizzare le credenziali in modo corretto :





Il terzo pulsante, "LogOn protetto da SQL", usa la stessa metodologia ma con la piccola differenza che invece di un comando di Select viene invocata una Stored Procedure denominata "up_LogOnSQL" che a sua volta lancia la sp_executeSQL.

Ecco la definizione della sp

Create Procedure up_LogOnSQL
 @Login varchar(25),
 @LoginPwd varchar(25)
As

Declare @Return int = -1;
Declare @strCmd as nvarchar(max) = 'Select @Return = count(*) from UsersTab where Ulogin = @Uid and UPwd = @UPwd'

Exec sp_executeSQL  @strCmd ,N'@Uid varchar(500), @UPwd varchar(500),@Return int OUTPUT',@Login,@LoginPwd,@Return OUTPUT

Select @Return
Go

e quella del codice

string strCon = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

            SqlConnection con = new SqlConnection(strCon);
            SqlCommand cmd = new SqlCommand();
            cmd.Connection = con;
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = "up_LogOnSQL";

            SqlParameter username = new SqlParameter("@Login", SqlDbType.VarChar, 25);
            username.Value = TxtLogin.Text.Trim().ToString();
            cmd.Parameters.Add(username);

            SqlParameter password = new SqlParameter("@LoginPwd", SqlDbType.VarChar, 25);
            password.Value = TxtPwd.Text.Trim().ToString();
            cmd.Parameters.Add(password);

            con.Open();
            int result = (Int32)cmd.ExecuteScalar();
            con.Close();

            if (result == 1)
            {
                Session.Add("Query", cmd.CommandText);
                Response.Redirect("AppMainPage.aspx");
            }
            else
                lblMessage.Text = "Utente o Pwd non validi";




Questa metodologia inoltre solleva lo sviluppatore da tutti i problemi fastidiosi relativi alla gestione degli apostrofi, dei separatori decimali etc...
e consente anche di beneficiare del riciclo dei piani d'esecuzione presenti nella procedure cache... Dopotutto le query parametriche hanno sempre lo stesso hash...
Ci proteggiamo e miglioriamo le performance...

Ovviamente l'argomento non si esaurisce qui, ci sono tonnellate di post che trattano in modo molto dettagliato l'argomento :

http://msdn.microsoft.com/it-it/library/cc185099.aspx
http://www.html.it/articoli/tecniche-sql-injection-1/
http://www.unixwiz.net/techtips/sql-injection.htm
https://www.owasp.org/index.php/Blind_SQL_Injection

e molti altri ancora !!!!

Ciao

Luca

Nessun commento:

Posta un commento