Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book
Podatność LDAP injection – definicje, przykłady ataku, metody ochrony
Czym jest LDAP?
Lightweight Directory Access Protocol (LDAP) to protokół pozwalający na wymianę informacji wykorzystując protokół TCP/IP. Przeznaczony jest on do korzystania z usług katalogowych, czyli obiektowych baz danych reprezentujących użytkowników sieci i zasoby. LDAP jest powszechnie stosowany w wielu usługach, z których zapewne najbardziej znaną jest Active Directory firmy Microsoft.
LDAP oparty jest na modelu klient-serwer. Jeden lub więcej serwerów LDAP posiadających dane grupuje je w tak zwane ‘drzewo katalogów LDAP’. Klient LDAP łączy się z serwerem i wysyła żądanie. Serwer odpowiada zasobem lub wskazuje klientowi gdzie może znaleźć więcej informacji (inny serwer LDAP). Bez znaczenia jest, z jakim serwerem połączy się klient, zawsze widzi on tą samą strukturę katalogów.
Informacje w katalogach przechowywane są w postaci wpisów. Każdy wpis jest obiektem jednej lub wielu klas, a każda klasa składa się z jednego lub wielu atrybutów.
Wpisy zorganizowane są w postaci struktury wspomnianego wcześniej już “drzewa katalogów”. Są one identyfikowane jednoznacznie – przez wyznaczone za pomocą atrybutów – DN (ang. Distinguished Name) o którym możemy w uproszczeniu pomyśleć jak o ścieżce do pliku (np. /home/Desktop/myfile.txt).
LDAP Injection
LDAP Injection, to atak zbliżony formą do ataku SQL Injection, dlatego te same techniki eksploitowania mogą zostać podobnie wykorzystane. LDAP Injection ma wykorzystać w aplikacji www konstruującej wyrażenia LDAP, możliwość wprowadzenia danych przez użytkownika, w celu nieuprawnionego uzyskania danych z bazy, ich modyfikacji lub powiększenia uprawnień. Na atak narażone są aplikacje nie filtrujące odpowiednio danych, a głębsze zrozumienie osiągniemy po przeanalizowaniu poniższych przykładów.
Przykład 1
W tym przykładzie, zobaczymy w jaki sposób wykorzystując LDAP Injection, możemy ominąć mechanizm uwierzytelnienia. Poświęćmy najpierw kilka chwil, aby zrozumieć składnię zapytania LDAP. Pobieramy z bazy rekordy użytkownika po jego atrybucie CN (ang. Common Name), czyli jednym z wymaganych atrybutów klasy ‘osoba’. W tym celu użyjemy, zapytania (cn=[NAZWA]). Aby „dokleić” następny warunek, musimy posłużyć się operatorami logicznymi OR ‘|’ lub AND ‘&’. Zapytania LDAP konstruowane są w tzw. notacji polskiej, co oznacza że mają operator logiczny na początku. Zapytanie pobierające z bazy rekordy, których nazwa użytkownika jest równa ‘Marek’ lub ‘Jarek’, wygląda więc następująco:
(|(cn=Marek)(cn=Jarek))
W celu sprawdzenia, czy podana nazwa ‘Marek’ i odpowiadające mu hasło ‘marek123’ są poprawne, posłużymy się zapytaniem:
(&(cn=Marek)(userPassword=marek123))
LDAP używa także wieloznacznika ( * ), znajdującego wszystkie możliwe wartości. Możemy manewrować nim tak, by otrzymać łańcuch znaków spełniający nasze wymagania, np:
(cn=Mar*) znajdzie wszystkie rekordy których cn zaczyna się od ‘Mar’.
(cn=*are*) znajdzie wszystkie rekordy których cn ma w sobie ‘are’.
Gdy znamy już podstawy, możemy zająć się naszą web aplikacją połączoną z serwerem LDAP. Aplikacja przyjmuje dane uwierzytelniające od użytkownika, konstruuje zapytanie i wysyła je do serwera.
Jeżeli dane nie są filtrowane, czy powinno wystarczyć jeśli wpiszemy * w pola ‘login’ i ‘hasło’? Aż tak łatwo nie będzie, bo chociaż LDAP wspiera przetrzymywanie haseł w zwykłym tekście, to możemy się domyślać, że już nikt nie przechowuje ich w takim formacie. Mamy więc do dyspozycji tylko jedno pole i możemy podejrzewać, że zapytanie wysyłane do serwera wygląda następująco:
(&(cn=[login])(userPassword=MD5[haslo]))
Spróbujmy zatem pomanewrować parametrem login. Zapytanie rozpoczyna się od operatora AND, więc żeby było poprawne musimy zapewnić mu co najmniej dwa warunki. Chcemy przekazać mu dwa warunki zawsze prawdziwe. Zaczniemy od zakończenia tego już rozpoczętego używając *), a następnie wstawimy drugi, bezwzględnie prawdziwy. Mógłby on być identyczny jak poprzedni tzn (cn=*), ale dla urozmaicenia wstawimy symbol (&) – prawdę absolutną. Teraz zamkniemy jeszcze pierwszy fragment zapytania ), a z drugiego zrobimy osobny filtr danych (|(cn = *. Serwer LDAP będzie interpretował takie oto zapytanie:
(&(cn=*)(&))(|(cn =*)(userPassword=MD5[haslo]))
Zamiast jednego, mamy teraz dwa filtry: (&(cn=*)(&)) oraz (|(cn=*)(userPassword=MD5[haslo]))
Tak naprawdę, nie ma najmniejszego znaczenia co znajduje się w drugim fragmencie, zostanie on zignorowany, a serwer interpretując jedynie (&(cn=*)(&)) pozytywnie nas uwierzytelni.
Takiego zachowania nie zaobserwujemy jednak zawsze, ponieważ część usług LDAP, w tym Microsoft ADAM (Active Directory Application Mode) nie akceptuje zapytań z dwoma filtrami. Dlatego też, zaraz po zakończeniu pierwszego umieścimy NULL BYTE (%00 w kodowaniu URL).
(&(cn=*)(&))%00)(userPassword=MD5[haslo]))
W ten sposób pozbywamy się z zapytania drugiego filtru i zostajemy uwierzytelnieni.
Przykład 2
Tym razem, nasza web aplikacja wypisuje imię i nazwisko studenta odpowiadające wpisanemu przez nas indeksowi. Aplikacja nie filtruje wprowadzanych przez użytkownika danych, dlatego możemy na podstawie zwracanych odpowiedzi (prawda lub fałsz) odgadnąć dane do których nie powinniśmy mieć dostępu. Ten rodzaj ataku nazywany jest ‘ślepym’ (ang. Blind LDAP Injection).
Zapytanie wysyłane przez aplikację wygląda następująco:
(&(index=[NR_INDEKSU])(ou=Students))
Wiemy jednak, że oprócz imienia i nazwiska, w bazie znajduje się również numer telefonu studenta i postaramy się ten numer “zgadnąć”. W tym celu, tworzymy trzeci warunek dla operatora AND (&) wprowadzając jako nr indeksu 2766810)(telephonenumber=*. Zapytanie “(&(index=2766810)(telephonenumber=*)(ou=Students))” nie zwraca błędu (zwraca dokładnie te same dane), więc wiemy, że atrybut ‘telephonenumber’ jest poprawny. Teraz postaramy się wydobyć wartość tego atrybutu za pomocą mechanizmu o nazwie ‘booleanization’.
Na podstawie odpowiedzi, będziemy sukcesywnie poznawać numer studenta. Sprawdzamy najpierw, czy nie zaczyna się on na cyfrę 1.
Aplikacja zwraca nam błąd, wiemy że warunek (telephonenumber=1*) nie jest spełniony i próbujemy dalej z kolejnymi cyframi. W końcu, przy wpisaniu 2766810)(telephonenumber=5*, otrzymujemy poprawną odpowiedź, oznacza to że pierwsza cyfra numeru telefonu studenta to 5.
Teraz iterujemy drugą cyfrę, do momentu otrzymania prawidłowej odpowiedzi, 2766810)(telephonenumber=50*, i tak dalej. Odgadywanie możemy przeprowadzać na różne sposoby, np poprzez zawężanie zestawu znaków: 2766810)(telephonenumber=*3* – sprawdzamy czy 3 jest w numerze telefonu. „Ręczne” zgadywanie numerów wszystkich studentów byłoby czasochłonne, jednak proces ten łatwo możemy zautomatyzować.
Metody ochrony
Podstawową ochroną przed LDAP injection, jest walidacja danych wprowadzanych przez użytkownika. Muszą one zostać oczyszczone z wszystkich znaków, które mogłyby zostać wykorzystane do zainicjowania ataku. Najlepiej zastosować się do zasady białej listy i filtrować dane w oparciu o regular expression, pozwalając na wprowadzanie tylko poszczególnych znaków.
Dodatkową linią obrony, jest walidacja danych zwracanych do użytkownika oraz ograniczenie ich ilości w pojedynczej odpowiedzi.
Aby zminimalizować potencjalne szkody warto zastosować się do zasady Least Privilege, czyli przydzielać web aplikacji najmniejsze uprawnienia, jakie są konieczne.
Administrator LDAP’a powinien kontrolować dostęp do zasobów. Ustalając uprawnienia do poszczególnych obiektów, powinien rozważyć, które z nich mogą być modyfikowane, a do których użytkownik nie może mieć dostępu. Jest to ważne tym bardziej, gdy usługa korzysta z coraz bardziej powszechnego rozwiązania – single sign-on.
Podsumowanie
LDAP injection, to atak wykorzystujący w aplikacjach www konstruujących wyrażenia LDAP, możliwość wprowadzenia danych przez użytkownika. Jego wadą w porównaniu do SQL Injection jest – wynikający ze składni – fakt, że złośliwy kod jest wstrzykiwany po określeniu operatora logicznego. LDAP injection pozwala na ominięcie mechanizmów autoryzacji i uwierzytelnienia. Atakujący może także uzyskać wrażliwe dane, lecz jest to trudniejsze do zrealizowania.
–Michał Ogorzałek
Może jakieś zdania do rozwal.to dodać. Bo nie widzę dedykowanych pod LDAP injection.
Lista TODO uzupełniona ;)
Dziękuję za bardzo interesujący artykuł, po raz pierwszy czytam o tego typu podatności (ale jak się już o tym pomyśli, to staje się to oczywistą sprawą), sam mam w firmie 2 proste aplikacje korzystające z LDAP, postaram się je przejrzeć i poprawić, bo na pewno nie ma tam w kodzie żadnego zabezpieczenia przez tego typu obejściem.
Full disclosure – tylko napisz gdzie pracujesz