Niveau 27

Natas 27

Level Goal

Username: natas27
Password: 55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ
URL: http://natas27.natas.labs.overthewire.org

En se connectant à la page du challenge 27 (curl http://natas27.natas.labs.overthewire.org -u natas27:55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ) on arrive sur un éniième formulaire de connexion :

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

Avec le code source suivant :

<?

// morla / 10111
// database gets cleared every 5 min 


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


function checkCredentials($link,$usr,$pass){
 
    $user=mysql_real_escape_string($usr);
    $password=mysql_real_escape_string($pass);
    
    $query = "SELECT username from users where username='$user' and password='$password' ";
    $res = mysql_query($query, $link);
    if(mysql_num_rows($res) > 0){
        return True;
    }
    return False;
}


function validUser($link,$usr){
    
    $user=mysql_real_escape_string($usr);
    
    $query = "SELECT * from users where username='$user'";
    $res = mysql_query($query, $link);
    if($res) {
        if(mysql_num_rows($res) > 0) {
            return True;
        }
    }
    return False;
}


function dumpData($link,$usr){
    
    $user=mysql_real_escape_string($usr);
    
    $query = "SELECT * from users where username='$user'";
    $res = mysql_query($query, $link);
    if($res) {
        if(mysql_num_rows($res) > 0) {
            while ($row = mysql_fetch_assoc($res)) {
                // thanks to Gobo for reporting this bug!  
                //return print_r($row);
                return print_r($row,true);
            }
        }
    }
    return False;
}


function createUser($link, $usr, $pass){

    $user=mysql_real_escape_string($usr);
    $password=mysql_real_escape_string($pass);
    
    $query = "INSERT INTO users (username,password) values ('$user','$password')";
    $res = mysql_query($query, $link);
    if(mysql_affected_rows() > 0){
        return True;
    }
    return False;
}


if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas27', '<censored>');
    mysql_select_db('natas27', $link);
   

    if(validUser($link,$_REQUEST["username"])) {
        //user exists, check creds
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
            echo "Here is your data:<br>";
            $data=dumpData($link,$_REQUEST["username"]);
            print htmlentities($data);
        }
        else{
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }        
    } 
    else {
        //user doesn't exist
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){ 
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
        }
    }

    mysql_close($link);
} else {
?> 

En quelque mot ce code a le comportement suivant :

Contrairement aux précédentes épreuves portant sur des failles SQL, ici les informations envoyées par le formulaire sont filtrées par la fonction native de PHP mysql_real_escape_string(). Cette fonction a pour effet d'échapper les caractères permettant de réaliser une injection SQL (NULL, \x00, \n, \r, \, ', " et \x1a).

Il n'y a pas de moyen correspondant au scénario de la page permettant d'effectuer une injection SQL en passant à travers la fonction mysql_real_escape_string().

On peut toutefois noter plusieurs éléments intéressants :

Ces 3 éléments nous permettent de supposer qu'il va falloir réussir à créer un utilisateur natas27 pour pouvoir se connecter avec ses identifiants.

En SQL tout les trailing-spaces (espaces à la fin de la valeur) dans un varchar sont automatiquement supprimés avant insertion dans la base de donnée.

Si l'on essaye de se connecter avec les valeurs natas27 et password comme identifiants, on obtient l'erreur suivante :

Wrong password for user: natas27

Cela signifie que l'on n'a pas réussi à tromper la fonction validUser(). Cependant, si l'on regarde le schéma de base de donnée spécifié en commentaire du code source, on a :

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

On peut remarquer que le nom d'utilisateur est stocké dans un varchar de 64 caractères de long, ce qui veut dire que si l'on envoie un nom d'utilisateur plus long, la fonction validUser() ne devrait pas trouver d'occurence dans la base de donnée. Essayons avec une commande curl ou l'on envoie un username de 65 caractères de long :

curl --form username='natas27                                                         a' \
     --form password='password' \
     http://natas27.natas.labs.overthewire.org -u natas27:55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ

On obtient le message suivant :

User natas27                                                         a was created!

On sait cependant que lors de l'insertion d'un varchar, MySQL coupe la chaîne de caractères lorsqu'elle a atteint la limite autorisée.

Ainsi, on a :

Si l'on essaye de se connecter avec le couple natas27 / password, on obtient :

Welcome natas27 !
Here is your data:
Array ( [username] => natas27 [password] => JWwR438wkgTsNKBbcJoowyysdM82YjeF ) 

On a donc le mot de passe pour le niveau suivant : JWwR438wkgTsNKBbcJoowyysdM82YjeF