Trochę inne OR 1=1 —
Punkt A1 Injection nie dotyczy tylko błędów typu SQL Injection. Coraz częściej dostęp do danych jest realizowany za pośrednictwem jakiejś warstwy abstrakcji (np. ORM). Użycie tego typu rozwiązań nie gwarantuje odporności na błędy typu injection, np. CWE-564: SQL Injection: Hibernate.
Jest jednak pewna różnica między możliwością wstrzyknięcia kodu, a osiągnięciem w ten sposób jakiegoś sensownego rezultatu. Bardzo często jest tak, że język zapytań wspomnianej warstwy pośredniej jest ograniczony, atakujący może dużo mniej, niż w przypadku błędu SQL Injection. Jeśli ktoś jest przyzwyczajony do tego, że injection daje pełen dostęp do bazy danych (co najmniej w trybie do odczytu), a czasami i więcej, takie ograniczone skutki podatności mogą trochę rozczarowywać. Czasami tym, co chcemy osiągnąć, jest uzyskanie dostępu do danych innych użytkowników. Pozytywne (dla atakującego) jest to, że jesteśmy w stanie to osiągnąć. Przynajmniej w większości wypadków.
Zacznijmy od pewnego powtarzalnego wzorca, który się pojawia w zapytaniach, niezależnie od tego, na czym to zapytanie operuje (SQL, XML, ...). W tym wzorcu parametry zapytania podzielone jest często na dwie części. Pierwsza część ogranicza wyszukiwanie do tych danych, do których użytkownik ma dostęp, w drugiej części zapytania przekazywane są natomiast parametry wyszukiwania, te właściwe. Wygląda to mniej więcej tak (umówmy się, że to jest sekcja WHERE zapytania):
(OwnerID = 1 AND SomeOtherField = 2) AND ((BodyText LIKE '%25test%25' OR Subject LIKE '%25test%25') AND Type = 1)
W praktyce może być tak, że OwnerID zawiera identyfikator użytkownika, do którego dany rekord należy, a pole SomeOtherField jest jakimś innym “ogranicznikiem”. Może to być na przykład poziom dostępu konkretnego użytkownika do danych.
Dla lepszego zrozumienia możemy przyjąć, że OwnerID jest identyfikatorem klienta w jakiejś aplikacji “chmurowej”, natomiast SomeOtherField jest poziomem dostępu aktualnie zalogowanego użytkownika tego klienta.
Załóżmy, że mamy injection w parametrze, który jest wstawiany do LIKE. Jak uzyskać dostęp do danych innych użytkowników?
W normalnej sytuacji byłoby to dość proste. Wystarczyłoby przekazać payload następującej postaci:
')) OR 1=1 —
Wówczas sekcja WHERE zapytania przyjęłaby mniej więcej taką postać:
(OwnerID = 1 AND SomeOtherField = 2) AND ((BodyText LIKE '%25')) OR 1=1 — %25' OR Subject LIKE '%25 ')) OR 1=1 — %25') AND Type = 1)
Efektywnie zapytanie byłoby jednak krótsze, bo jego część zostaje wykomentowana:
(OwnerID = 1 AND SomeOtherField = 2) AND ((BodyText LIKE '%25')) OR 1=1
Jaki jest rezultat tego zapytania? Zwracane są wszystkie rekordy z danej tabeli, bo decydujące znaczenia ma tutaj warunek 1=1, który jest tautologią.
A teraz niespodzianka. Nie zawsze można po prostu wykomentować pozostałą część zapytania, bo na przykład język wykorzystywanej warstwy abstrakcji po prostu nie wspiera komentarzy (patrz: Does JPQL have single- and/or multi-line comments?). Co zrobić?
Oczywiście nie pozostaje nic innego, jak wymyślić inny payload, który zmodyfikuje warunki wyszukiwania w tautologię, ale bez konieczności wykorzystywania komentarza. Dla ułatwienia zacznijmy od czegoś takiego:
(OwnerID = 1 AND SomeOtherField = 2) AND ((BodyText LIKE '%25$injection%25' OR Subject LIKE '%25$injection%25') AND Type = 1)
Musimy uciec z podwójnych nawiasów, a potem do nich wrócić. Musimy też stworzyć “wolną” sekcję OR, która zwraca zawsze true niezależnie od pozostałych części zapytania. Przykład:
')) OR 1=1 OR (('%25'='
Co dzieje się po przekazaniu takiego payloadu (dla ułatwienia – połamane zapytanie):
(OwnerID = 1 AND SomeOtherField = 2) AND ((BodyText LIKE '%25')) OR 1=1 OR (('%25'='%25' OR Subject LIKE '%25')) OR 1=1 OR (('%25'='%25') AND Type = 1)
Przede wszystkim trzeba sprawdzić, czy nawiasy się zgadzają. W pierwszej linii mamy jeden otwierający, jeden zamykający. Jest więc OK. W drugiej linii są dwa nawiasy otwierające, dwa zamykające. Wygląda to brzydko, ale składniowo jest poprawne. Taka sama sytuacja jest w czwartej i szóstej linii. W linii trzeciej i piątek mamy natomiast nasze tautologie. Cały warunek będzie więc prawdziwy, a w rezultacie dostaniemy dostęp do wszystkich danych z danej tabeli.
Prawda, że oczywiste? Trzeba jednak przyznać, że strzelanie “w ciemno” z zapytaniem może być skomplikowane. Jeśli nie wiemy jak skonstruowane jest zapytanie, to wstrzelenie się z payloadem, który po wstrzyknięciu da prawidłowe zapytanie, nie jest zadaniem trywialnym.
Oryginał tego wpisu dostępny jest pod adresem Trochę inne OR 1=1 —
Autor: Paweł Goleń