Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Second Order SQL Injection
Podczas pracy pentestera aplikacji internetowych można spotkać się z wieloma ciekawymi przypadkami błędów popełnianych przez twórców oprogramowania. Kategorie wykrywanych błędów są bardzo różne, począwszy od brakujących nagłówków bezpieczeństwa, aż po dramatyczne dla właściciela usługi zdalne wykonanie kodu. Pośrodku tego wszystkiego znajduje się grupa błędów o nazwie Injection. Napastnik, który znalazł błąd tej klasy wstrzykuje niebezpieczny kod w struktury strony lub bazy danych w celu uzyskania dostępu do wrażliwych informacji. W specjalnych warunkach możliwe będzie również dokonanie modyfikacji danych.
Najpopularniejszym wstrzyknięciem według zestawienia OWASP TOP 10 jest nadal SQL Injection, polegające na dodawaniu kodu składni SQL do istniejącego zapytania. Możliwe jest to ze względu na złą weryfikację zmiennych używanych w zapytaniu. Napastnik wykorzystujący wyżej wymienioną podatność może przekazać silnikowi bazy danych odpowiednio zmodyfikowaną kwerendę. W odpowiedzi na przesłane zapytanie zamiast domyślnie przekazywanych danych, zgodnych z założeniami programisty – pojawiają te mniej przez niego pożądane, jak loginy czy hasła administratorów (całe szczęście większość z nich nie jest w dzisiejszych czasach przechowywana tekstem jawnym).
Omówione wstrzyknięcie zostało szerzej opisane w jednym z naszych wcześniejszych artykułów.
Wróćmy jednak do tytułowej podatności, Second Order SQL Injection, która jest na tyle specyficzna, że dla początkujących pracowników działu bezpieczeństwa może okazać się trudna do zlokalizowania.
W ramach naszego opracowania użyjemy przygotowanej na tę okazję aplikacji.
Aplikacja posiadać będzie sekcję artykułów oraz możliwość ich komentowania, wraz z uwzględnieniem cytatu (innego komentarza).
Dostępne dla każdego użytkownika pola komentarza to:
- Tytuł
- Imię
- Treść
- Cytat
Pole cytat posiadać będzie następującą mechanikę:
- Wybrany z listy tytuł komentarza jest odszukiwany po jego nazwie zapisanej w bazie danych (nazwa ta została wcześniej przefiltrowana podczas tworzenia pierwotnego komentarza).
- Następnie po odnalezieniu komentarza będzie on prezentowany w postaci: imię autora, tytuł oraz treść.
Dane przekazywane w formularzu są filtrowane pod kątem XSS oraz SQL Injection, co też potwierdziły niezależne skanery, nie znajdując żadnych podatności (całe szczęście aplikacja jest testowana przez pentestera, a nie automat).
Naszym zadaniem będzie wykonanie kodu SQL w systemie komentarzy, pomimo iż przekazywane bezpośrednio do aplikacji payloady SQL nie dawały żadnego efektu.
Poniżej ilustracja omawianej strony z formularzem komentarzy.
Na kolejnej ilustracji zobaczymy, że cytowany komentarz można wybrać z listy.
Powyższy formularz przesyła do aplikacji dane metodą POST, a zawartość przekazywana jest jako seria danych w postaci:
- Ciąg znaków (String) – Imię
- Ciąg znaków (String) – Tytuł
- Ciąg znaków (String) – Komentarz
- Wartość numeryczna (Integer) – Identyfikator komentarza dla cytatu
Przetestowaliśmy aplikację wstrzykując najpopularniejsze wyrażenia:
- Znak apostrofu – ‘
- Wyrażenia arytmetyczne – 33-11
- Znak cudzysłowu – ”
- Komentarze – # — /*
Spróbowaliśmy także wrzucić całe zapytanie HTTP do popularnego programu „sqlmap”.
Efektem naszych prób było pojawienie się niezmienionej treści jako wartości wybranych pól, bez widocznych błędów aplikacji czy spowolnienia jej działania.
Na czym zatem polega sztuczka Second Order SQL Injection, jeżeli przekazując we wszystkie wskazane wyżej pola najpopularniejsze payload’y dla tej klasy podatności nie widzimy żadnego efektu, a treść po usunięciu niebezpiecznych tagów renderuje się prawidłowo?
W celu przeprowadzenia ataku w polu tytuł należy wprowadzić ponownie klasyczny ciąg znaków odpowiadających za pobranie informacji o wersji bazy danych. Tym razem jednak nie zakończymy testów na tym etapie.
Ilustracja poniżej przedstawia dodany przez nas komentarz z tytułem zawierającym gotowy dla tego systemu payload (’ AND 1=2 UNION SELECT 1,2,@@version), który powinien zwrócić wersję bazy danych – jednak na tym etapie payload, zamiast się wykonać, wyświetla się w niezmienionej formie.
Kolejnym krokiem będzie ponowne dodanie komentarza o dowolnej treści, tym razem jednak używając w nim cytatu z przygotowanym wcześniej payload’em.
W przypadku testowej aplikacji, dokładnie znamy jej schemat działania.
Aplikacja w pierwszej kolejności pobiera wszystkie komentarze i ładuje je do tablicy w celu oszczędzenia ilości zapytań SQL. Następnie w przypadku dodania nowego komentarza weryfikuje, czy komentarz zawiera cytat. Jeśli wymagany jest cytat, aplikacja na podstawie przesłanego identyfikatora pobiera z załadowanej wcześniej tablicy tytuł, a następnie na jego podstawie już nie filtrując go ponownie wyszukuje w bazie danych oryginalną treści komentarza.
SELECT name, title, comment FROM comments WHERE title = '' AND 1=2 UNION SELECT 1,2,@@version
Takie działanie aplikacji jest zdecydowanie błędne, dzięki czemu możemy wykorzystać ten fakt w celu wykonania udanego ataku SQL Injection poprzez załadowanie payload’u SQL już bezpośrednio z tytułu innego komentarza.
Kolejna ilustracja pokazuje efekt po dodaniu tego teoretycznie niewinnego komentarza.
Możemy zauważyć, że błąd został popełniony na poziomie architektury aplikacji. Programiści założyli, że dane przekazane i raz przefiltrowane mogą być traktowane jako bezpieczne, a ich ponowne użycie nie stanowi żadnego zagrożenia. Ta mylna niestety teza – doprowadziła nas do wykonania udanego ataku SQL Injection.
Pamiętajmy, że dane pochodzące od użytkownika nigdy nie mogą być traktowane jako bezpieczne i każde ich użycie musi być związane z wcześniejszym filtrowaniem.
Na sam koniec osoby, które są ciekawe jak wygląda tego typu atak w przypadku prawdziwych aplikacji odsyłamy tutaj czy tutaj.
— Robert Kruczek, hakuje w Securitum
Ten, jak i wcześniejsze artykuły koncentrują się na pojęciu „filtrowanie wejścia”, a nie bardziej właściwym, polegającym na zamianie parametru na postać wymaganą przez format. Kwestia bezpieczeństwa jest ważna, ale wtórna. Istotą jest odpowiednie zakodowanie ciągu, aby pozbyć się błędnych interpretacji i błędów parsowania. Błędne podejście do zagadnienia prowadzi do kuriozalnych sytuacji, w których zdawałoby się poważne strony (bankowe) nie dopuszczają niektórych znaków w opisie transakcji. To dotyczy XML, HTML, JavaScript, wyrażeń regularnych. W SQL mamy dostępną parametryzację, ale pułapka polega na tym, że programiści nie odróżniają parametryzacji wartości pól od parametryzacji literałów (na przykład nazw kolumn w ORDER BY). Nacisk musi polegać na zapoznaniu z rozmaitymi funkcjami z quote, encode i escape w nazwie, ich działaniem i ograniczeniami, a nie filtrowaniu.
Słuszna uwaga, w dobie dzisiejszych frameworków z łatwością można rzutować zmienne oraz parametryzować zapytania. Nie mniej jednak odpowiednia filtracja danych nie zaszkodzi ;) tym bardziej, że nie każdy korzysta z gotowych rozwiązań jak framework czy orm.
Myślę, że ideą artykułu było przedstawienie ataku – a nie zabezpieczeń przed nim, stąd „filtrowanie danych” to jedynie skrót myślowy autora. Zwłaszcza, pisząc „już nie filtrując go ponownie” – bo filtracji danych przy pierwszym zapytaniu nie ma. Jest sanityzacja (brzydkie słowo, ale najbardziej uniwersalne) / parametryzacja. Przy filtrowaniu, znaki/payloady testowane pod kątem sqli zostałyby – przefiltrowane – czyli usunięte, nie mogłyby więc pojawić się w kolejnym zapytaniu (i o 2nd order nie mielibyśmy mowy).
Co do testów automatycznych, chociaż 2nd order wygląda skomplikowanie – da się go wykryć również automatami. Myślę, że zarówno Burp jak i nawet sqlmap (flaga -second-order) z opisanym przypadkiem dałyby radę.
Uściślijmy – SQLi nie jest najpopularniejszym błędem na liście OWASP TOP10 – najpopularniejsza jest cała grupa błędów związanych ze wstrzyknięciami ;)
Co robi apostrof po słowie „payload”?
Wystarczy poszukać ;) https://dobryslownik.pl/kompendium/regula/381/