19-03-2019 / Od 0 do pentestera

Confidence CTF 2019

W tym materiale pokażę rozwiązanie dwóch zadań z CTF-a organizowanego przez team P4 w ramach konferencji Confidence.

Team CTF P4

Zadanie pierwsze to My admin panel.

Otrzymujemy adres, pod którym to w otwartym katalogu znajdują się 2 pliki.

Pierwszy login.php oraz jego kopia z rozszerzeniem .bak.

Zazwyczaj serwer Apache jest skonfigurowany tak, że nie traktuje plików z rozszerzeniem bak jako plików z kodem źródłowym.

Stąd też możliwe jest podglądnięcie zawartości tego zadania.

Prześledźmy teraz wspólnie ten kawałek kodu.

My admin panel

Na początku sprawdzenie, czy istnieje ciasteczko o nazwie otadmin.

Jeżeli nie, wyświetlany jest komunikat Not authenticated.

Jeżeli istnieje - jego treść jest przekazywana do funkcji preg_match, która jest odpowiedzialna za wyrażenia regularne.

Tutaj to sprawdzamy czy treść ciasteczka jest zgodna z danym wyrażeniem.

Jeżeli nie - wyświetlany jest komunikat Cookie tampering.

Wyrażenie regularne sprawdza, czy ciąg zaczyna się od nawiasu a następnie klucza hash.

Potem możliwe jest wiele cyfr, dużych liter oraz cudzysłowów.

Cały ciąg musi się kończyć nawiasem.

Sprawne oko dostrzeże zatem, że jest to składnia JSON.

Możemy zatem w ciasteczku przekazać plik JSON, z jednym kluczem hash oraz odpowiadającą mu wartością.

Potwierdza to kolejna funkcja, json_decode - która w PHP odpowiedzialna jest za dekodowanie plików JSON.

Jeżeli ta funkcja zwraca null - oznacza to, że jej wywołanie się nie powiodło, a podany ciąg znaków nie jest prawidłowym plikiem JSON.

Dalej podany przez nas hash jest porównywany z hashem MD5, który to jest wyliczany ze zmiennej $cfg_pass.

Wartość tej zmiennej jest dla nas zagadką - ponieważ jest ona zdefiniowana w pliku config.php, do którego dostępu nie posiadamy.

Jeżeli wartość podana przez nas jest różna od tej, którą musimy zgadnąć, wyświetlany jest odpowiedni komunikat.

W innym wypadku - funkcja display_admin zwraca nam flagę, potrzebną do rozwiązania zadania.

Na pierwszy rzut oka mamy zatem odgadnąć hash MD5 bez praktycznie żadnej wiedzy o nim.

Pierwsze co mi się w tym przypadku nasunęło, to tak zwane magiczne hasze w PHP.

W skrócie: jeżeli porównujemy ze sobą dwa ciągi znaków, które zaczynają się od 0e i zawierają same cyfry, zostaną zrzutowane na liczbę całkowitą.

Magiczne hashe

Mamy zatem do czynienia z równaniem 0*10^x - gdzie x to dalsza część liczby po ciągu 0e.

A ponieważ 0 razy cokolwiek to nadal 0 - ciągi są sobie równe.

W Internecie można znaleźć przykłady takich ciągów.

Są to zatem wcześniej wyliczone dane, które po wprowadzeniu do funkcji MD5 dadzą nam w rezultacie ciąg rozpoczynający się od 0e a następnie samych cyfr.

Niestety, ta próba była nieudana.

Kolejna rzecz jaka mnie zainteresowała, to wersja PHP jaka została zainstalowana na tym serwerze.

Może jest to jakaś stara, podatna wersja i znajduje się jakiś exploit?

W tym wypadku mamy do czynienia z wersją PHP 7.3.3.

I rzeczywiście, nie tak dawno wprowadzano zmianę w funkcji json_decode.

Dodano do niej flagę, dzięki której funkcja ta będzie rzucać wyjątek, jeżeli coś pójdzie nie tak2.

W tym wypadku ponownie wyjątek to nie jest coś czego nam potrzeba.

Należy zatem szukać dalej.

json_encode

Zainteresowałem się wyrażeniem regularnym nieco bliżej.

Dlaczego oprócz liter i cyfr pozwalają tam na przekazywanie cudzysłowów?

Musimy się bowiem zastanowić jak działają typy danych JSON.

Jeżeli wartość zostanie podana pomiędzy cudzysłowami - zostanie przekonwertowana przez PHP na string.

Jeżeli jednak zostanie podana bez cudzysłowów - mamy do czynienia z liczbą.

W tym wypadku autorzy zadania pozwalają na obie formy.

Wyrażenie regularne

Ale dlaczego pozwalają na przekazywanie liczby, jeżeli wartość tą porównują potem z tekstem?

Krótkie zapytanie do Google pokazuje, że nie jesteśmy jedyną osobą, która o to pyta.

W dokumentacji można znaleźć informację, iż w przypadku porównywania string z int, PHP automatycznie przekonwertuje string na liczbę.

Czyli jeżeli w naszym pliku podamy liczbę - hash MD5 zostanie automatycznie, w tle, przekonwertowany na liczbę.

Ale jak ta konwersja działa? Jak można zamienić litery na cyfry?

PHP używa dość prostego algorytmu.

Rozpatruje dany ciąg od początku - sprawdzając, czy są to cyfry.

Gdy natrafi na pierwszą literę - kończy analizowanie tekstu i zamienia go na liczbę, która ułożyła się z poprzednich znaków.

Popatrzmy na przykład.

Konwersja

Ciąg ABCD nie zaczyna się od żadnych cyfr - będzie wiec zamieniony na 0.

Ciąg 12EFG zaczyna się od 12 - i właśnie na taką liczbę zostanie zamieniony.

W trzecim przykładzie porównujemy tekst z tekstem - tutaj zatem nie dojdzie do automatycznej zamiany typów - wiec ciagi te są różne.

Zatem, jeżeli mamy szczęście, to hash MD5 użyty przez twórców zadania może zaczynać się od jakiejś małej liczby.

Możemy więc sprawdzić pierwsze 1000 kombinacji, za każdym razem w ciasteczku ustawiając liczbę od 0 do 10000.

Proces odnajdowania flagi CTF

I rzeczywiście - po prawie 400 próbach znaleźliśmy prawidłową wartość.

Hasz zaczynał się od liczby 389.

I w taki sposób uzyskaliśmy pierwszą flagę.

Pora na drugie zadanie - WEB 50.

Ponownie otrzymujemy adres URL.

Tym razem jednak nie posiadamy dostępu do kodu źródłowego.

Wita nas formularz na login i hasło.

On to automatycznie tworzy za nas konto w tym systemie.

Po zalogowaniu dostępne są dwie opcje:

Profile oraz Report a bug.

Ta pierwsza to prosty formularz, w którym możemy podać imię, nazwisko, rozmiar buta, sekret oraz avatar.

Formularz zadania WEB 50

Druga opcja to pojedyncze pole tekstowe oraz captcha, którą musimy wypełnić aby przesłać formularz.

Po jego weryfikacji otrzymujemy informację, że pole to przyjmuje tylko adresy z obrębu tej strony internetowej.

Po podaniu właśnie takich danych - komunikat zmienia się.

Tym razem twórcy informują nas, że admin przyjrzy się stronie podanej przez nas w parametrze.

Jest to popularny mechanizm w zadaniach CTF.

Symuluje się tutaj czynnik ludzki.

Czyli wysyłamy jakiś adres URL a następnie robot - symulujący człowieka - klika w ten adres.

Zazwyczaj w zadaniach tego rodzaju chodzi zatem o znalezienie podatności XSS.

Wtedy to administrator wchodząc na podatną stronę wykonuje złośliwy, kontrolowany przez nas kod JS.

Dzięki temu możemy wykonywać jakieś akcje w jego imieniu - czyli chociażby przeglądać treść panelu admina.

Bo to zazwyczaj właśnie tam znajduje się tajna flaga, którą chcemy pozyskać.

Wiemy już, że szukamy błędu XSS.

Pora wrócić do poprzedniego formularza.

Zainteresowało mnie ostatnie pole - czyli możliwość wysłania pliku.

W takich sytuacjach zazwyczaj tworzę plik PHP z jakimś dynamicznym kodem.

W tym wypadku prostą funkcją zwracającą bieżący czas.

Wtedy to kiedy wyślę taki plik a następnie mogę go wyświetlić - jeżeli zobaczę czas - wiem, że mój kod został wykonany.

Jeżeli zobaczę tekst echo time(); - wiem, że nic z tego, gdyż kod się nie wykonał.

Tutaj jednak otrzymałem komunikat iż mój plik nie jest prawidłowym obrazkiem.

Stwórzmy zatem obrazek i spróbujmy ponownie.

Ale strona ta sprawdza dodatkowo jego wielkość.

Ustalanie rozmiaru obrazka

Zmieńmy więc jego wymiary na 100x100px i spróbujmy ponownie.

I rzeczywiście - tym razem formularz został przyjęty, a na naszej stronie, zamiast kaczki - widzimy piękną grafikę.

W kodzie źródłowym widzimy rówież, że ta grafika została skopiowana do losowego katalogu.

Serwer zwraca także prawidłowy nagłówek content-type, informując przeglądarkę, że mamy do czynienia z plikiem graficznym.

Content-type obrazka

Tutaj przypomniała mi się informacja na temat plików polyglots, czyli plików, które oprócz prawidłowego obrazka są równocześnie prawidłowym kodem JS.

Wtedy to administrator wchodząc na taki obrazek, nie wyświetlałby go - lecz wykonywał kod JavaScript.

Tylko, że aby to zadziałało - przeglądarka musiałaby zwracać nagłówek content-type ustawiony na HTML.

Musimy zatem szukać innych opcji.

Ale przecież mamy plik SVG.

Z jednej strony jest to grafika - z drugiej plik, w którym znajduje się kod JS.

Spróbujmy zatem przykładowego kodu ze strony Sekurak3.

Musimy dodać odpowiedni kawałek kodu, tak aby obrazek ten miał odpowiednie wymiary.

Co ciekawe - formularz przyjął ten obrazek i go wyświetlił.

A przechodząc bezpośrednio pod adres pliku SVG naszym oczom ukazało się okienko z alertem.

Atak XSS działa prawidłowo.

Teraz trzeba już tylko odpowiednio zmodyfikować kod JS, tak aby wyświetlać treść panelu administratora i przesyłać go do serwera przez nas kontrolowanego.

Wykorzystujemy do tego celu krótki kawałek kodu.

Najpierw pobieramy treść ze strony profile, a następnie przekazujemy ją do serwera requestbin4.

Serwis Requestbin

Jest to prosta strona, pozwalająca na sprawdzenie żądań HTTP.

Generujemy tutaj swój unikalny adres.

Wchodząc na niego przez przeglądarkę lub też wysyłając do niego dane przy użyciu JavaScript - notuje on wszystkie informacje jakie zostały zawarte w żądaniu, a następnie wyświetla w czytelnej formie.

Jeżeli więc administrator kliknie w nasz link i podatność XSS zadziała, kod JS w niej zawarty prześle zawartość panelu admina do naszego serwera.

Rozwiązanie zadania

I rzeczywiście - wszystko zadziałało prawidłowo.

W treści widzimy żądanie pochodzące od administratora, a w polu sekret flagę, która jest rozwiązaniem zadania.

Tak oto poprzez pliki SVG udało nam się wykonać kod JS.