Blind SQL Injection
Ogólne rozważania o technice, czyli jak wykonać testy na Blind SQL Injection.
Ostatnim razem zająłem się mitem bezpiecznych procedur. Tym
razem chciałbym się zająć kolejnym mitem, czy może lepiej, niebezpiecznym
przeświadczeniem, które równie często osłabia naszą czujność. Być może czytając poprzedni tekst na temat
niebezpiecznych procedur SQLi- mit bezpiecznych procedur zadałeś/łaś sobie pytanie: „No i co z tego, że
aplikacja jest podatna na SQL Injection, skoro włamywacz nie będzie znał
struktury tabel, kolumn itp. Jeżeli aplikacja nie będzie wysyłała błędów składniowych bazy danych na output
aplikacji, to nigdy nie wymyśli on zapytania, którym mógłby zaszkodzić moim danym”.
I znów to tylko niebezpieczne pozory.
Tak jak człowiekowi niewidomemu trudniej dotrzeć do celu niż człowiekowi ze sprawnym wzrokiem, tak włamywaczowi, który nie będzie miał drogowskazów będzie trudniej namieszać w naszej bazie danych. W obu przypadkach „trudniej”, nie znaczy „niemożliwe”. Dlatego technika wykrywania podatności na SQL Injection w sytuacji, kiedy błędy serwera są ukrywane przed klientem, została nazwana Blind SQL Injection.
Na czym polega ta technika? Mówiąc najprościej, polega ona na takim spreparowaniu zapytań, które da nam PEWNOŚĆ, że kod SQL dołączony do przekazanego parametru się wykonuje i zwraca rezultaty, jakich oczekujemy. (http://www.owasp.org/index.php/Blind_SQL_Injection , http://www.cgisecurity.com/questions/blindsql.shtml, http://www.spidynamics.com/assets/documents/Blind_SQLInjection.pdf)
Przedstawię to na przykładzie. Wyobraźmy sobie, że mamy aplikację, która przekazuje parametr „ID” w url-u i na podstawie tego parametru wyświetlane są rozszerzone informacje o użytkowniku, np.:
http://app-server/userInfo.aspx?id=25
Mając wiedzę o SQL Injection, możliwe jest sprawdzenie, czy aplikacja jest podatna na tego typu atak. Jeżeli jednak aplikacja ma prawidłowo zaimplementowaną obsługę błędów, to w sytuacji, kiedy w url-u zostanie przekazana wartość id: „25;select * from jakastabela”, zapewne wyświetlona zostanie strona z komunikatem: „Przykro nam, ale wystąpił błąd”. Można sobie zadać pytanie, czy błąd został wygenerowany na serwerze bazy danych, czy w aplikacji? W tej sytuacji komunikaty o błędach niewiele pomogą.
Jeżeli jednak potencjalny intruz wie, jak korzystać z techniki Blind SQL Injection, łatwo stwierdzi, czy serwer jest podatny na atak. Można to wykonać na kilka sposobów, jednak cel jest jeden: takie przygotowanie zapytania SQL, żeby rekord zwracany przez aplikację był taki sam, jak rekord, który zostanie wyświetlony po zaaplikowaniu „zatrutej strzały”.
Oto kilka metod wykonywania testów na Blind SQL Injection:
- Metoda „na działanie arytmetyczne”. Polega ona na przekazaniu takiego ciągu znaków, który w wyniku przetworzenia przez serwer bazy danych wykona działanie arytmetyczne, które w wyniku da wartość przekazywanego identyfikatora, a więc w naszym przypadku może to być: id=24+1, Id=26-1 lub dla prawdziwych twardzieli ;-) Id=5*5.
- Metoda „na oczywistość”. Polega ona na wprowadzeniu do zapytania elementu, które zawsze zwraca „true”, wskutek czego warunki filtrowania danych nie ulegną zmianie. W naszym przypadku może to być: Id=25 AND 1=1, lub Id=25 AND ‘biale’=’biale’ AND ‘czarne’=’czarne’
- Metoda „czekajcie, a będzie wam dane”. Polega ona na wysłaniu do serwera takiego zapytania, żeby serwer bazy danych sam zasygnalizował, że zapytanie zostało wykonane. Najprościej zrobić to kombinując z wywoływaniem funkcji opóźniających. Przykładowo w SQL Serverze do naszego parametru można dodać Id=25 WAITFOR DELAY '0:0:5'. Jeżeli strona, która wyświetlona była bez dodatkowych „wspomagaczy” wyświetlała się o 5 sekund krócej, niż po ich zastosowaniu, oznacza to, że dodatkowe parametry zapytania zostały przyjęte bez zastrzeżeń, a więc istnieje możliwość wykonania kodu SQL dołączonego do zapytań.
- Metoda „Kto tam?”. Służy ona odpytaniu bazy danych, jakiego serwera bazodanowego (chodzi o rozpoznanie czy to MySQL, SQL Server, Postgresql, Oracle itp.) używa aplikacja. Metoda ta polega na dołączeniu do zapytania kodu SQL specyficznego dla wybranych serwerów. Jeżeli dołączony kod wygeneruje błąd, wówczas można wykluczyć tę wersję serwera. Jest to bardzo istotny element Blind SQL, ponieważ wydobywanie informacji na temat obiektów systemowych jest inne dla każdej bazy danych. W większości tekstów poświęconych bezpieczeństwu nazywana jest ona fingerprinting. (http://bernardodamele.blogspot.com/2007/07/more-on-database-management-system.html).
- Metoda „Jeżeli”. Metoda służy do sprawdzenia, czy zapytanie wykonuje się w sytuacji, kiedy nie da się w prosty sposób stwierdzić, że zapytanie się wykonało i zwróciło oczekiwane rezultaty, ponieważ wynik zapytania SQL nie jest wyświetlany na ekran. Np. w sytuacji, kiedy użytkownik się loguje na stronę, a po poprawnym wykonaniu zapytania jest weryfikowany i przekierowywany do innego zasobu. Jeżeli intruz chce sprawdzić, czy zapytanie SQL przekazane w parametrze się wykonuje, wówczas wystarczy „wstrzyknąć” zapytanie, które w zależności od spełnienia warunku wykona jakąś czynność, łatwą do zweryfikowania, np. opóźnienie. A zatem kawałek kodu dodany do zapytania przeznaczonego do SQL Servera wyglądałby następująco:
if ((select user) = 'dbo') WAITFOR DELAY '0:0:5'
Jeżeli po przesłaniu zapytania do bazy danych chwila oczekiwania na wykonanie akcji będzie o 5 sekund dłuższa niż normalnie, oznacza to, że baza danych to SQL Server, a ponadto użytkownik, z którego prawami wykonywane jest zapytanie to „dbo”.
Powyższe przykłady stanowią tylko część możliwości. Więcej można poczytać pod adresem http://ferruh.mavituna.com/sql-injection-cheatsheet-oku/. Pod tym adresem można znaleźć bardzo dużo wskazówek na temat SQL Injection.
Po stwierdzeniu faktu występowania podatności na włamanie, po raz kolejny można sobie zadać pytanie, do czego włamywacz może ją wykorzystać, skoro nigdzie nie może wyświetlić rezultatów zapytania (co nie zawsze jest prawdą, ponieważ przy pomocy odpowiedniego spreparowania instrukcji wyciągnięcie danych nie stanowi problemu, ale o tym innym razem)? Otóż nawet nie mogąc wyświetlić informacji na output można mozolnie, ale skutecznie przepytać serwer i wyciągnąć od niego wiele cennych informacji.
Z samej podatności serwera bowiem jeszcze nic nie wynika. Cały czas mamy w tyle głowy pytanie: „I co nam może zrobić włamywacz, skoro nie zna struktury bazy danych”. Zwłaszcza, jeżeli projektant przewidując ewentualne próby włamań i zgodnie z zasadą dobrych praktyk unikał standardowych nazw obiektów bazy danych, ich właściwości itp. Zamiast nazywać obiekty standardowo, np. tabela „Users”, z polami „username”, „password”, „id”, pododawał prefiksy, dzięki którym dużo trudniej wymyślić zapytanie. Tabeli nadał nazwę „AppUsers”, a polom w tabeli „AUName”, „AUPass”, „AUId”. Zabezpieczenie takie jest o tyle dobre, że od razu odfiltruje nam wszystkich scripts kiddies. Jednak jeżeli osoba próbująca się dostać do naszego serwera zna trochę zaawansowane funkcjonalności bazy danych, wówczas nawet mimo najbardziej wyrafinowanych nazw włamanie nie będzie stanowiło problemu właśnie z powodu Blind SQL Injection.
Zanim pójdziemy dalej, żeby przekonać się jak to jest możliwe, spróbujmy się przyjrzeć regułom formułowania zapytań tego typu. Przeanalizujmy sobie jak wygląda nasze zapytanie SQL przed i po zaaplikowaniu dodatkowego kodu SQL.
Jako przykład znów użyjemy naszą tabelę z użytkownikami użytą w poprzednim wpisie[link]. W parametrze GET przekazywana jest wartość ID i po jej otrzymaniu od serwera wykonujemy następujący kod SQL:
string sql = "select Id, Username, LastLogon from AppUsers where Id=" + Request.Params["id"];
Jeżeli więc jako parametr ID przekażemy
1 AND 1=1
Wówczas na serwerze bazodanowym wykona się zapytanie
select Id, Username, LastLogon from AppUsers where Id=1 AND 1=1
Przeanalizujmy sobie nasze zapytanie i zastanówmy się, jak podzielone jest to zapytanie. Pierwsza część jest standardowym zapytaniem, które zwraca oczekiwany rekord. Natomiast druga część mówi, czy warunek umieszczony po „AND” jest prawdziwy. Żeby się upewnić, że druga część zapytania nie jest ignorowana, ale na pewno się wykonuje, trzeba dodać warunek, który zawsze jest fałszywy, czyli np.:
AND 1=2
W tej sytuacji aplikacja zwróci błąd mówiący, że nie znaleziono podanego użytkownika.
Jak widać pierwsza część zapytania jest niezmienna i jest ona gwarantem, że zapytanie umieszczone w drugiej części zawsze zwraca „True”. Co można wyciągnąć z takiej wiedzy? Nadspodziewanie dużo, a jeżeli użytkownik, z którego prawami jest uruchamiana aplikacja kliencka, ma odpowiednie uprawnienia, to jeszcze więcej.
Włamywacz ma więc do dyspozycji tak tylko drugą część zapytania i do dyspozycji bardzo lakoniczne odpowiedzi serwera (TAK/NIE). Co dalej?
Przypomnijmy sobie zabawę z dzieciństwa w 25 pytań, kiedy jedna osoba wymyślała jakiś przedmiot, a druga musiała odgadnąć jaki to przedmiot, mając do dyspozycji 25 pytań i odpowiedzi tej pierwszej tylko „TAK” lub „NIE”. Podobnie włamywacz może odpytać serwer podatny na atak typu Blind SQL Injection.
Zastanówmy się, jakie pytania może on zadać serwerowi, żeby mogli dowiedzieć się, jaką strukturę ma tabela, do której kierowane są zapytania ze skryptu.
Może zadać następujące pytania (oczywiście po przeformułowaniu ich na język SQL):
- czy użytkownik, który wywołuje zapytanie jest „dbo”
- czy liczba tabel w bazie danych jest większa od [10,5,7,8]
- czy pierwsza litera nazwy pierwszej tabeli przedstawiona jako mała litera to [m,g,d,b]
- czy druga litera nazwy pierwszej tabeli …
- czy liczba kolumn w wybranej tabeli jest większa od…
- czy pierwsza litera nazwy pierwszej kolumny wybranej tabeli to…
Wszystkie te informacje są możliwe do wyciągnięcia, jeżeli odpowiednio zostaną spreparowane odpowiednie zapytania do tabel systemowych, wywołania wbudowanych funkcji systemowych itp. Właściwie w ilości danych, jakie można wyciągnąć z niezabezpieczonej aplikacji intruza ogranicza tylko czas, wyobraźnia i potrzeby. Z takich szczątkowych informacji, jest w stanie zbudować sobie potężną bazę wiedzy o tabelach bazy danych używanych przez aplikację. Oczywiście opisane przeze mnie kroki, nie muszą być wykonywane ręcznie. Przygotowanie skryptu, który wykona za niego całą pracę nie zajmie mu wiele czasu (tym łatwiej, jeżeli będzie miał już rozpoznaną wersję serwera bazy danych), poza tym w sieci można ściągnąć wiele aplikacji wykonujących te zadania.
Ze względu na to, że celem tego artykułu nie jest przygotowanie instrukcji, jak wyciągać poufne dane z serwerów, a jedynie ostrzeżenie przed niebezpieczeństwem, nie będę tutaj pisał tutoriala, jakie kolejne kroki należy wykonać żeby je wyciągnąć z serwera bazodanowego, natomiast żeby udowodnić, że zagrożenie rzeczywiście istnieje, przytoczę kilka przykładów, które zachęcą, mam nadzieję, do lepszego zabezpieczania aplikacji przed SQL Injection we wszystkich postaciach.
Wszystkie przykłady są odpowiednie dla MS SQL Server.
Odp. 1: Czy user jest dbo.
AND USER_NAME()='dbo'
Odp 2.
AND (select count(name) from sysobjects where type='U')>10
Odp. 3.
Mam nadzieję, że zachęciłem do zwrócenia uwagi, na to, czy mimowolnie nie dodaliśmy do naszej aplikacji funkcjonalności konsoli SQL dla zaawansowanych użytkowników. Warto o tym pomyśleć, kiedy piszemy warstwę obsługi dostępu do danych w naszych aplikacjach.AND ascii(lower(substring((SELECT TOP 1 name FROM sysobjects WHERE xtype='U'),1,1)))>96
