Layer 06

SQL Injections

devscripts.com e la non curanza dei programmatori a fare il parsing od il controllo perlomeno formale dei dati inseriti nelle form. Sempre più spesso si vedono siti defacciati proprio per una debolezza dei programmi utilizzati. Ma vediamo cosa potrebbe succedere, di seguo riporto l’esempio di come si protrebbe fare ad accedere ad un sito che utilizza lo script che sta su questo sito nella sezione ProgrammingASP Questo articolo è a puro scopo didattico, quindi l’autore ed etechs.it declinano ogni responsabilità sul possibile utilizzo che ne potrebbe essere fatto. L’articolo è stato scritto allo scopo di illustrare le possibili operazioni per prevenirle all’opera della programmazione. In questo articolo su come costruire una pagina di login abbiamo costruito una pagina con nome utente e password che ne lanciava un’altra che, con il supporto del database, controlla l’esistenza dell’utente del database stesso. L’errore più comune che può commettere un programmatore di linguaggi di scripting è quello di supporre che l’utente finale utilizzi i campi proprio come intende il programmatore, ma sopratutto il programmatore da per scontato che l’utente malizioso non esista. Infatti nella pagina della form si possono inserire delle stringhe complete alfanumeriche senza che esse siano controllate. Nel migliore dei casi inserendo una stringa malformata si può ottenere un’errore, ripeto è il migliore dei casi, anche se questo non è bene, dobbiamo sempre ricordarci che qualsiasi cosa trapeli dal nostro server su come è costruito il programma o come è strutturato il DB non è assolutamente bene, sono informazioni in più che diamo al nostro utente malizioso. Nel peggiore dei casi possiamo garantire l’accesso alla pagina senza averne l’autorizzazione! Bruttissima cosa! Vediamo come: Analisi del programma Nel programma che riceve il nome utente e la password costruiamo un SQL per interrogare il database:

<% strsql = “SELECT * from Users where UserID = ‘” & Request.Form(“user”) & “‘” %> La stringa risultante sa questa: SELECT * from Users where UserID = ‘valoreinserito’ e subito dopo nel programma viene eseguita l’interrogazione del Database, dopo le dovute connessioni, con il comando:

<% Set objRs = Server.CreateObject(“ADODB.Recordset”) Set objRs = objConn.Execute(strsql) %>

Latheral Thinking Supponiamo ora di non essere l’utente ordinario e di voler entrare a tutti i costi anche non sapendo nè utente nè pwd. Abbiamo detto che il campo della form ci permette di inserire qualsiasi valore alfanumerico, quindi… proviamo con l’apostrofo:

Premendo invio quando il programma processerà la pagina la stringa SQL risulterà come segue:

SELECT * from Users where UserID = ”’

Così il programma genererà un’errore, cioè mi visualizzerà una pagina dicendo che l’SQL

SELECT * from Users where UserID = ”’

e lo visualizza, è errato. Adesso noi maliziosi abbiamo a disposizione il nome della tabella utenti, users. ACCESSO! Bene, proseguendo per ragionamento logico e vedendo che non viene tagliato l’apostrofo, possiamo supporre che sarà possibile inserire anche qualche altro carattere speciale: Proviamo con la stringa or ‘A’=’A La strigna risultante sarà: SELECT * from Users where UserID = ” or ‘A’=’A’ a questo punto il database ottiene per ogni riga della tabella utenti una corrispondenza esatta cioè ‘A’=’A’, ma cos’è successo? non avendo controllato la validità dei dati inseriti, il programmatore ci da la possibilità di chiudere la stringa di ricerca e di inserirne una nostra, la qual cosa non è da farsi. Con il Primo apostrofo chiudo la stringa di ricerca con l’operatore logico or imposto un nuovo criterio, con ‘A’=’A immetto un criterio sempre vero, ho tralasciato appositamente l’ultimo apostro perchè sarà il programma stesso a chiuderlo. Quanto a noi abbiamo avuto accesso con il primo utente restituito dall’interrogazione.

Scaliamo i privilegi Essendoci collegati con questo trucchetto non abbiamo il controllo su quale utente utilizzare, ovviamente a noi però serve un amministratore, non un iscritto ad una mailing list, l’amministratore sarà comunque e quasi sempre un nome utente tipo “admin” allora proviamo ad inserire una stringa tipo ‘ having 1=1 (utilizzo questa stringa perchè suppongo di avere SQL server o access. Il server perocesserà la pagina e troverà un errore si sintassi nella strigna SQL, restituirà il seguente errore:

Microsoft OLE DB Provider for SQL Server (0x80040E14)

Column ‘users.userID’ is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.

/login.asp, line 16

Siamo a cavallo! abbiamo il nome della tabella ed il nome del campo di ricerca. Allora possiamo sforzarci di utilizzare la fantasia, come ogni database, esistono funzioni di like che ermettono di effettuare ricerche “avanzate” dicendo che il campo tal dei tali deve assomigliare ad un valore … etc etc. proviamo ad inseritre questa stringa: ‘ or users.userName like ‘a%’ Che succede? stiamo proprio cercando quegli utenti che hanno il nome che comincia per a… possimao fare tentativi multipli con a, ad, adm, amm, ammini, pri, insomma sbizzariamoci, possiamo sempre arrivare ad avere i privilegi di amministratore. Entrare o cancellare, questo è il dilemma! La maggior parte dei linguaggi di scripting permettono di eseguire più query contemporaneamente separandole da punto e virgola (;), ovviamente compatibilmente al database utilizzato. Vi faccio alcuni esempi con SQL Server che è sempre molto utilizzato. Cancelliamo Se volessimo far dei danni e cancellare la tabella degli utenti, mbhè basta che costruiamo una stringa come segue: ‘ or 1=1; drop table users; A questo punto la nostra stringa ci permetterà di entrare come il primo utente restituito dalla query e succesivamente di cancellare la tabella degli utenti ed al prossimo accesso otterremo un’errore in quanto si sta tentando di interrogare una tabella inesistente. Spegniamo il DB Possiamo anche scrivere dei comandi che spengano il DB, questi comandi sono legati a doppio filo con l’utente amministratore, rima però che la maggior parte delle pagine create che devono accedere a SQL Server vi accedono come SA (System Admin) questo utente può effettuare molte operazioni, quali creare e cacellare constraints, trigger particolari, insomma un po’ di tutto, compreso lo spegnimeto del DB immediato, proviamo quindi questa stringa: ‘; shutdown with nowait; — Che dire, il database si spegnerà ed avrà bisogno di essere riavviato direttamente dalla management consolle di SQL server. Eseguire codice arbitrario Molto più interessante dei precedenti e distruttivi comandi, esiste in SQL la possibilità di eseguire delle stored procedure, utilizzado il comando XP_storedprocedure, queste procedure sono spesso collegate direttabente a dll o shell di sistema, allora basta inserire una stringa come questa per eseguire dei comandi da shell del DOS: ‘; exec master..xp_cmdshell ‘iisreset’; —  In questo modo possiamo eseguire il comando iisreset (maggiori informazioni sui HowTOWebIISReset howto) da una semplice richiesta da una form non controllata. Che dire, è un malfunzionamento molto pericoloso, personalmente spero che le applicazioni che ho fatto io fino ad ora non abbiano questo tipo di vulnerabilità, non ne sono assolutamente sicuro, ma almeno io ho fatto del mio meglio. NON ACCESSO! Non tutti i programmatori sono però degli sprovveduti, quindi un programmatore ha un paio di metodi per scavalcare questo tipo di SQL Injections. Nel nostro articolo non viene fatto il parsing del valore inserito quindi diciamo che ci sta bene qualsiasi cosa venga inserita. Abbiamo però implementato il controllo della veridicità dei dati restituiti, una specie ci CRC, un controllo di coerenza, con queste istruzioni: Do while NOT objRs.EOF   If objRs(“UserID”) = Request.Form(“user”) Then     VerifyMember = True   End If   If objRs(“Password”) = Request.Form(“pass”) Then       VerifyPass = True   End If objRs.Movenext Loop

In pratica scorriamo tutto il recordset restituito in cerca della corrispondenza tra la stringa nel database e la stringa inserita nell’utente e nella password. Putroppo però non è abbastanza, dopo vedremo come. In questo modo poi non controlliamo l’esistenza contemporanea di nome utente e password nello stesso record, quindi potremmo accede con il nome utente di un utente e la password di un’altro, e se supponiamo che le password sono sempre una scocciatura troveremo (per Database estesi) qualcuno con una password corta, pronunciabile o componobile, quindi passibile più facilmente ad un brute force (dai dai stussa stussa, finchè non trovi quella giusta).

L’altro metodo è quello di fare il parsing di dati inseriti, croppando o sostituendo gli apici, gli uguali o gli spazi, e dando come regola generale del DB che un nome utente non contiene nè apici, nè uaguali e nè spazi. Tutto sommato non è molto restrittivo, considerando la gran parte dei sistemi di login. In questo modo qualsiasi valore inserito andrà bene per la nostra interrogazione.

Cuori impavidi Noi che siamo maliziosi però sappiamo che non può essere finita lì. Se il programmatore è stato bravo allora non risuciremo ad entrare con un semplice trucchetto, però ne abbiamo tanti altri a disposizione, uno di questi è cercare di raccogliere informazioni sul server, come abbiamo fatto per sapere il nome della tabella e del campo userid, abbiamo cercato di mandare in errore il SQL server e di farci vedere informazioni utili al nostro scopo. Abbiamo raccolto soltanto 3 informazioni: nome tabella, nome campo utente, nome campo password, più che abbastanza per poter entrare in altri modi. Nel caso in cui abbiamo una pagina di login, vuol dire che almeno una parte del sito è dinamica, e si può supporre quindi di avere pagine del tipo http://www.sitovittima.ex/prod.asp?id=242 e quindi si può supporre che la pagina sia costruita normalmente con questo SQL:

Select nomeProd, prezzo from prodotti where id = 242 benissimo, se non è stato controllato che valore passare alla pagina tramite l’URL possiamo anche inserire degli or come abbiamo fatto per lo username e la pwd della pagina di login. Prestiamo però attenzione a non passare solo parametri puri così coem stanno, stiamo trattando con delle URL, le URL vanno inserite in unicode, quindi per uno spazio dobbiamo scrivere %20, quindi la stringa da inserire nell’URL è questa:

http://www.sitovittima.ex/prod.asp?id=242%20or%201=1

La pagina processerà la richiesta restituendo al database questa query:

Select nomeProd, prezzo from prodotti where id = 242 or 1=1

otterremo la lista completa dei prodotti con il relativo prezzo, mmm ma non ci doveva interessare questo, a noi interessa entrare nella pagina di amministrazione come abbiamo fatto prima, dobbiamo perciò utilizzare tutto quello che abbiamo a disposizione, un’idea brillante è utilizzare una union, niente ce lo vieta, tanto meno il programmatore (solitamente), abbiamo a disposizione sempre quei 3 dati, la possibilità di fare delle union… mbhè non ci rimane altro che mettere il tutto insieme e provare a far fare una union con la tabella degli utenti. Una delle richieste della funzione union è quella che la prima interrogazione deve avere lo stesso numero di campi della seconda. Allora se noi volessimo unire la pagina prod.asp che ci visualizza descrizione e prezzo con la tabella utenti basta che specifichiamo una seconda query con soltanto due campi … mm fammici pensare, nome utente e passowrd? 🙂 Risulterebbe così:

select nomeProd, prezzo from prodotti where id =242 union select userID, Password from users

A noi basta aggiungere tutto quello che sta dopo l’id del prodotto ricordandoci di sostituire tutti gli spazi con %20, quindi lanciando tutto da URL diventerebbe così:

http://www.sitovittima.ex/prod.asp?id=242%20union%20select%20userID,%20Password%20from%20users

Sulla pagina risultante dalla richiesta otterremo il primo record che è il prodotto 242, ed il rimanente dei record sono la tabella degli utenti in toto, non ci resta che sceglierne uno :-).

Prevenzione Come facciamo ad essere sicuri che il nostro script non sia passibile di queste sql injections? mi verrebbe da rispondere … non possiamo, ma siccome questo articolo è nato come difesa dalle injections e non come un articolo per imparare a farle, proviamo a far di tutto per rendere la vita difficile ai famosi utenti maliziosi. Prima di tutto dobbiamo fare una procedura di Hardening sul server, sia sul DB che sul server Web, inutile dire che le apllicazioni anche se costruite benissimo non possono proteggere il server web. Un’operazione di hardening prevede ad esempio che l’utente che si collega dalle pagine web deve essere limitato ad esempio alla sola  selezione dei record quindi solo operazioni di select, magari crearne un’altro per le operazioni di insert, e basta, drop e alter sono operazioni abbastanza pericolose. Ricordiamo anche del xp_cmdshell o del xp_grantlogin che non devono assolutamente essere eseguite. Poi a livello di applicazione possiamo fare sempre un parsing di quello che viene inserito dall’utente, quindi togliamo l’apostrofo o lo raddoppiamo con funzioni come questa:

parsata = replace(str, “‘”, “””);

e magari cerchiamo di interpretare quello che viene scritto nei campi a disposizione dell’utente. Parole come insert, drop, or, = non dovrebbero essere contemplate, fatto questo dobbiamo anche non dimenticare di controllare i valori passati tramite URL. Prima di tutto controllare che esistano, infatti se non esistono, il nostro server restituirà un errore svelando parte del codice utilizzato per costruire la pagina, nonchè la pagina stessa e magari anche la posizione fisica della pagina all’interno del server. Poi se abbiamo dei valori numerici ricordiamo sempre di controllare che essi siano semrpe numerici con funzioni come questa:

isNumeric(id)

Poi ricordiamo che qualsiasi informazione è utile ad un cattivo intenzionato, quindi anche i parametri passati tramite URL sono delle informazioni passate in chiaro da una pagina all’altra, quindi ove possibile mettiamo delle form con dei postm, magari con i valori hidden, poi nella pagina che riceve i valori postati è sempre bene, ove possibile, controllare che la pagina che ha lanciato quella attuale sia quella giusta, il nome della form sia quella che voglio io, insomma il programma deve avere un grado di flessibilità abbastanza basso.

Nei vari esempi che ho riportato utilizzo SQLServer, perchè ha una sintassi chiara e di facile utilizzo, ma ricordate che anche MYSql o Oracle stesso nonostante l’osannata sicurezza proclamata.

Voglio concludere con una frase che mi piace un sacco, la sicurezza è solo un compromesso tra spento e non.

]]>

Nessun commento “SQL Injections”