Narzędzia osobiste
Start > Aktualności > Security blog > Topics > sql server

W kategorii: sql server

Blind SQL Injection

22.04
W kategorii: ,

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:

  1. 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.
  2. 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’
  3. 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ń.
  4. 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).
  5. 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):

  1. czy użytkownik, który wywołuje zapytanie jest „dbo”
  2. czy liczba tabel w bazie danych jest większa od [10,5,7,8]
  3. czy pierwsza litera nazwy pierwszej tabeli  przedstawiona jako mała litera to [m,g,d,b]
  4. czy druga litera nazwy pierwszej tabeli …
  5. czy liczba kolumn w wybranej tabeli jest większa od…
  6. 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.

 AND ascii(lower(substring((SELECT TOP 1 name FROM sysobjects WHERE xtype='U'),1,1)))>96

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.

rusk

SQLi- mit bezpiecznych procedur

Czy Ty jesteś bezpieczny? Przed czym należy się bronić?

O SQL Injection można znaleźć w Internecie wiele materiałów, zakładam więc, że czytelnik ma już podstawową wiedzę z tego zakresu, a mianowicie wie:

- co znaczy zwrot SQL Injection

- jak manipulować danymi w bazie danych za pomocą zapytań SQL przemycanych w przekazywanych danych wejściowych.

Jeżeli terminologia nie jest jeszcze znana, polecam zapoznanie się z tematyką: http://www.owasp.org/index.php/SQL_injection, http://www.owasp.org/index.php/Guide_to_SQL_Injection, http://www.governmentsecurity.org/articles/SQLinjectionBasicTutorial.php).
Szczególnie polecam strony OWASP, ponieważ nie powielają one mitu, o którym będę pisał poniżej, a który jest powielany  na wielu forach, blogach itp.

Mit, którym chciałbym się zająć, jest o tyle niebezpieczny, że daje pozorne poczucie bezpieczeństwa, a jednocześnie jest ukrytą miną, na którą ktoś wcześniej, czy później trafi. Tyle, że w tym przypadku ofiarą będzie umieszczający tę minę.

Mit ten brzmi: „korzystanie ze Stored Procedures zabezpiecza przed SQL Injection”.

Dla wielu programistów jest oczywiste, że najlepszym zabezpieczeniem przed SQL Injection jest skorzystanie z używanych w większości serwerów bazodanowych procedur przechowywanych w bazie danych, wywoływanych z parametrami, przekazywanymi przez klienta wywołującego procedurę. Jest to prawda w 50% (a więc nie jest to prawda), ponieważ błędnie napisana procedura może się okazać równie niebezpieczna, co konkatenowany skrypt SQL zaszyty w kodzie aplikacji. Może nawet bardziej niebezpieczna, ponieważ zapytanie generowane dynamicznie w kodzie, w przeciwieństwie do procedury, nie sprawia wrażenia bezpiecznego.

W przykładzie posłużę się procedurami składowanymi (stored procedures) oferowanymi przez SQL Server.

Załóżmy, że mamy w bazie danych tabelę AppUsers:

 

 

 

Tabela przechowuje dane użytkowników naszej aplikacji. Jedno z pól (pole IsAdmin) informuje, czy użytkownik jest administratorem. Na podstawie flagi ustawionej w tym polu aplikacja daje dostęp do panelu administracyjnego, w którym zalogowany użytkownik może zarządzać aplikacją, użytkownikami, uprawnieniami itp.

W części publicznej aplikacji mamy jakąś listę użytkowników, którą można przeszukiwać. Dla uproszczenia przykładu przyjmijmy, że będzie to wyszukiwanie według loginu użytkownika. W sytuacji, kiedy w filtrze wyszukiwania nie zostaną wprowadzone żadne dane, wówczas prezentowana jest lista wszystkich użytkowników. W przeciwnym razie w bazie będą wyszukiwani użytkownicy, których login zawiera litery podane w zapytaniu. Programista w tej sytuacji postanawia skorzystać z procedury SQL, ponieważ jest przekonany, że to zabezpieczy go przed problemem SQL Injection. Umieszcza więc w bazie danych procedurę:

CREATE PROCEDURE [dbo].[USP_UsersSearchSafe]

@username varchar(400) = NULL

AS

DECLARE @sql nvarchar(4000)

SET @sql = ' SELECT Id, Username, IsAdmin, LastLogon ' +

              ' FROM AppUsers '

IF @username IS NOT NULL

   SET @sql = @sql + ' Where Username LIKE '''+ @username +''''

Exec (@sql)


Wydaje się, że wszystko jest bezpieczne, ponieważ skorzystał z procedury, natomiast teraz wyobraźmy sobie, że w polu tekstowym użytkownik o loginie „user1” wprowadzi poniższy kod SQL:

';update AppUsers set IsAdmin=1 where Username='user1';--

Po przesłaniu powyższego zapytania do mechanizmu wyszukiwawczego, nasz mechanizm wyszukiwawczy „w promocji” dorzuca użytkownikowi „user1” uprawnienia administratora. Gdyby zapytanie było wykonywane 6 grudnia, można by zrezygnować z klauzuli „where” i wówczas wszyscy użytkownicy w prezencie mikołajkowym dostaliby administracyjne uprawnienia.

Nie trzeba wyrafinowanej analizy, żeby odkryć, co jest przyczyną podatności na SQL Injection, mimo zastosowania procedury SQL.

W powyższym przykładzie procedura została napisana dokładnie w taki sam sposób, w jaki byłaby ona zapisana, gdyby programista zaszył ją wewnątrz kodu aplikacji, tzn. zapytanie SQL jest konkatenowane i ewaluowane , natomiast sam przekazany parametr nie jest odseparowany od logiki zapytania. Zapytanie to nie różni się zbytnio od zapytań typu:

„Select * from products where Id=” + Input[„id”]

Przy korzystaniu z procedur SQL ważne jest, żeby uświadomić sobie, że przez zagrożeniami nie chroni sam fakt skorzystania z procedury. Przed wszelkiego rodzaju problemami chroni tylko DOBRZE NAPISANY KOD, niezależnie od tego, czy znajduje się on w aplikacji, czy też w bazie danych.

Przejdźmy więc do tego mitycznego stworzenia o nazwie DOBRZE NAPISANY KOD. Jak powinien wyglądać w naszym przykładzie, żeby wyeliminować podatność na SQL Injection.

CREATE PROCEDURE [dbo].[USP_UsersSearchReallySafe]

@username varchar(400) = NULL AS

DECLARE @sql nvarchar(4000)

SET @sql = ' SELECT Id, Username, IsAdmin, LastLogon ' +

              ' FROM AppUsers '

IF @username IS NOT NULL

   SET @sql = @sql + ' Where Username LIKE @username '

Exec sp_executesql @sql, N'@username varchar(400)',@username

 

Na czym więc polega różnica. Otóż w tym przypadku skorzystaliśmy możliwości oferowanych przez zapytania parametryzowane, dzięki czemu parametry przekazywane do zapytania SQL (dane wejściowe) zostały całkowicie odseparowane od składni zapytania. Jedyną niedogodnością jest, że w tym przypadku wywołanie przygotowanego zapytania SQL jest trochę bardziej skomplikowane, ponieważ musimy skorzystać ze specjalnej procedury sp_executesql, a także musimy jeszcze raz przekazać parametry do tej procedury. W tym przypadku jednak dużo bardziej przejrzyste jest przygotowanie samego zapytania SQL, dlatego w sumie wychodzimy na remis. Z tą różnicą, że w tym drugim przypadku mamy naprawdę bezpieczną procedurę.

Jaką konkluzję chciałbym więc zawrzeć w podsumowaniu? Chyba taką, że nawet „the best practice” może być przyczyną problemów, jeżeli będzie zastosowana bezrefleksyjnie...

I że ŹLE NAPISANY KOD zawsze jest zagrożeniem, niezależnie od tego, gdzie został umieszczony.

rusk


Produkty
  • eProcurement
    (PDF 438 kb)

    Elektroniczny Obieg Wniosków Zakupowych
  • more:portal
    (PDF 0,5 MB)

    Kompetencje oraz doświadczenia WebService w zakresie wdrażania portali korporacyjnych.
  • more:arena
    (PDF 1,7 MB)

    Aplikacja do prowadzenia zaawansowanych negocjacji z klientem oraz do planowania sprzedaży.
  • Portal Absolwent
    (PDF 1,87 MB)

    System dla Biur Karier, przeznaczony do komunikacji pomiędzy Uczelniami, Pracodawcami oraz Studentami i Absolwentami.