Historia pewnego zadania – SQL Injection i zepsute uwierzytelnianie

By | 13 lutego 2018

Dzisiaj chciałbym opisać rozwiązanie jednego z zadań pokazujących przykład złej implementacji autoryzacji w aplikacji webowej. Zadanie polegało na zalogowaniu się na konto admina w celu zdobycia odpowiedniej flagi.

Po wejściu na stronę naszym oczom ukazuje się prosty formularz logowania z polami do wpisania użytkownika i hasła. Pierwszy krok to analiza kodu źródłowego, gdzie zauważam komentarz wskazujący, że formularz można przetestować logując się na wskazane konto z podanym hasłem. Przeprowadzam test i faktycznie udało się zalogować. Na stronie pojawia się komunikat o poprawnym zalogowaniu, ale tylko do konta zwykłego użytkownika.

Idziemy dalej. Próbuję tym razem wpisać inną (dowolną) nazwę użytkownika. Otrzymuję komunikat: „Użytkownik nie istnieje”. Hmm… . Spróbujmy z kontem „admin” i dowolnym hasłem. Bingo – „Niepoprawne hasło”. Pierwszy podstawowy błąd uwierzytelniania – komunikaty umożliwiające weryfikację, czy konto w ogóle istanieje. Daje to atakującemu możliwość iteracji kont i atakowania tylko tych, które faktycznie istnieją.

Mam jednak inny pomysł. Konta pewnie składowane są w bazie danych, spróbujmy więc testu na wstrzyknięcie polecenia SQL. Z doświadczenia wiem jak mniej więcej powinno wyglądać zapytanie do bazy danych, jeśli miałoby być podatne na atak SQL Injection:

select * from users where username= ' + $username + ' and password=' + $password + ';

Próbujemy, więc taki payload: login.php?username=user'&password=P@ssw0rd . Po nazwie użytkownika dodaję apostrof i … dostaję błąd HTTP 500. Coś się wysypało 😉 Zapewne przedwcześnie zamknięty apostrof w parametrze username zepsuł zapytanie SQL.

Spróbujmy, więc naprawić zapytanie wykomentowując wszystko po warunku dla pola username :
login.php?username=user'#&password=P@ssw0rd . Okazuje się, że teraz udało mi się zalogować na testowe konto. Robię kolejny test i wysyłam inne hasło dla tego użytkownika. Niestety hasło jest nieprawidłowe. Z powyższych czynności wynika, że zapytanie SQL pobiera rekord użytkownika z bazy, a dopiero potem w kodzie sprawdzana jest poprawność hasła.
Wiemy, że możliwe jest wstrzyknięcie kodu SQL i znamy procedurę weryfikacji. W związku z tym możemy sprawić, aby zapytanie zwróciło taki rekord jaki chcemy. Aby to zrobić możemy wykorzystać klauzulę UNION i dodać tam własne zapytanie. Problem w tym, że nie wiemy ile kolumn zwraca pierwotne zapytanie o użytkownika. Można to jednak łatwo przetestować dodając kolejne kolumny w dołączonym zapytaniu.
1. login.php?username=user' UNION select '1';#password=P@ssw0rd daje HTTP 500
2. login.php?username=user' UNION select '1','2';#password=P@ssw0rd daje HTTP 200 i komunikat o poprawnym zalogowaniu. Wynika z tego, że pierwsze zapytanie zwraca dwie kolumny zapewne z nazwą użytkownika i hasłem.

Jesteśmy już blisko. Tworzymy docelowy payload:
login=' union select 'admin','P@ssw0rd';#&password=P@ssw0rd.
Uruchamiam zapytanie i klops – nie działa. Chwila namysłu i jest. Przecież haseł nie należy trzymać w postaci jawnej i tworzy się z nich hash’e, a jaka jest najpopularniejsza funkcja hasująca w PHP? Oczywiście MD5 (sic!). Szybkie obliczenia i tym razem payload wygląda tak:
login=' union select 'admin','098f6bcd4621d373cade4e832627b4f6';#&password=P@ssw0rd

Sukces. Flaga zdobyta.

2 thoughts on “Historia pewnego zadania – SQL Injection i zepsute uwierzytelnianie

  1. Mariusz

    Tak się zastanawiam, bo zdarzyło też mi się wiele razy bawić z takimi zadaniami sql injection na podstawowym poziomie. Są jakieś może narzędzia które to ułatwiają albo wręcz automatyzują? Bo po tym przykładzie mam wrażenie że można by spróbować napisać aplikcję która by potrafiła testować i sprawdzać taki rodzaj błędów.

    Reply
    1. Bartek R Post author

      Istnieją takie narzędzia jak SQLmap, BSQLHacker, SQLNinja, BBQSQL, które znacząco ułatwiają wykorzystanie takiej luki. Jednakże podczas prawdziwych testów najpierw sprawdzasz prostym testem podatność jakiegoś parametru, a następnie uruchamiasz np. SQLmap podając konkretny URL z podatnością. Narzędzie takie potrafi np. wyciągnać całą strukturę bazy danych wraz z danymi.
      Inną sprawą jest to, że wyniki, które sugerują istnienie podatności mogą być różne. Raz będzie to błąd HTTP 500, ale jeśli programista dobrze obsługuje błędy, to dostaniesz HTTP OK 200 i stronę z informacją, że coś poszło nie tak. NIe ma tutaj jakiejś konkretnej reguły.

      Reply

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *