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

Analiza

Pozyskanym na poprzednim poziomie hasłem logujemy się do poziomu 14. Tym razem dany jest formularz logowania. Mamy również wgląd w kod źródłowy:

<?
if (array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas14', '<censored>');
    mysql_select_db('natas14', $link);
    
    $query = "SELECT * from users where username=\"" . $_REQUEST["username"] . "\" and password=\"" . $_REQUEST["password"] . "\"";
    if (array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }
    
    if (mysql_num_rows(mysql_query($query, $link)) > 0) {
        echo "Successful login! The password for natas15 is <censored><br>";
    } else {
        echo "Access denied!<br>";
    }
    mysql_close($link);
} else {
?>

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

Pod warunkiem wysłania formularza kod wykonuje zapytanie do bazy danych. Odpytuje tablicę users w poszukiwaniu rekordów o zadanych loginie i haśle. Jeśli takie znajdzie, poznamy hasło do poziomu piętnastego. Oczywiście problem polega na tym, że nie znamy prawidłowego loginu i hasła. Należy jednak zauważyć, w jaki sposób wykonywane jest zapytanie:

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

Rozwiązanie

Jest to pierwszy poziom dotyczący baz danych, można wyciągnąć wniosek, że właśnie w tej sferze należy szukać podatności. Podatność tego poziomu polega na braku kontroli danych wpisanych przez użytkownika. Przyjmijmy założenie, że użytkownik wpisał następujące dane:

Login: admin
Hasło: null" OR "1"="1

Jak będzie wyglądało zapytanie $query dla takich danych? Następująco:

SELECT * from users where username="admin" and password="null" OR "1"="1"

Dzięki temu baza zwraca wszystkie rekordy tablicy user. Dzieje się tak, ponieważ dla każdego wpisu prawdziwy jest warunek "1"="1". Skrypt sprawdza ilość rekordów uzyskanych w wyniku zapytania, mamy więc pewność że warunek if (mysql_num_rows(mysql_query($query, $link)) > 0) zostanie spełniony. Logowanie z użyciem takich danych ujawnia hasło:

Successful login! The password for natas15 is AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

Podatność, którą wykorzystujemy nazywa się wstrzyknięciem kodu SQL (SQL Injection). Jest to niestety często spotykane uchybienie, które bardzo często pozwala na wydobycie newralgicznych danych z systemu przez osoby niepowołane. Więcej informacji o SQL Injection znaleźć możemy na stronie OWASP.

Rozwiązanie poziomu 15 jest już dostępne.