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 :
- lorsque le serveur reçoit des données de connexion, il se connecte à la base de donnée MySQL
- il appelle la fonction
validUser()
et si celle-ci retournetrue
, le serveur va appeler la fonctioncheckCredentials()
puis afficher les données de l'utilisateur si ses identifiants sont valides - si la fonction
validUser()
retournefalse
, le script ajoute un nouvel utilisateur avec les informations de connexion fournies - si l'on se connecte avec des identifiants nouvellement créés on obtient notre nom et notre mot de passe grâce à la fonction
dumpData()
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 :
- tout d'abord, la fonction
createUser()
crée un utilisateur sans vérifier si une autre occurence de celui-ci existe dans la base de donnée. Cela implique que si on envoie la valeurnatas27
à cette fonction, un utilisateur de ce nom sera créé et ce même si il existe dans la base de donnée. - pour prévenir cette faille, la fonction
validUser()
retournefalse
seulement si il n'existe pas d'utilisateur correspondant à celui communiqué dans le formulaire - enfin, la fonction
dumpData()
retourne l'ensemble des lignes correspondant à un nom d'utilisateur dans la base de donnée
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 :
- la fonction
validUser()
qui compare la valeur en base de donnée (natas27
) à celle qu'on lui envoie (natas27 a
) - la fonction
createUser()
va ajouter l'utilisateur spécifié dans la base de donnée. Lea
à l'extrémité de la valeur qu'on envoie pour leusername
va être coupé, car au delà du 64ème caractère. Puisqu'il ne reste plus que des espaces en fin de chaîne, MySQL va les supprimer et enregistrer un utilisateurnatas27
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