HTB: Cap

Easy box, and it mostly plays fair. Except the name is a setup for the privesc and I didn't clock it until root popped. Quick version: an IDOR in a fake "security dashboard" leaks an FTP password sitting in plaintext inside a packet capture (the app's whole job is capturing packets, so at least it's consistent), and root comes from a Linux capability nobody bothered to strip off python3.8.

recon

$ sudo nmap -sC -sV -T5 -Pn -p- -v 10.129.48.97 -oN scan.txt

nmap scan showing ports 21 (vsftpd 3.0.3), 22 (OpenSSH 8.2p1), and 80 (gunicorn, "Security Dashboard")

Three ports, nothing weird. FTP (vsftpd 3.0.3), SSH, and a gunicorn app on 80 calling itself "Security Dashboard." A box that names its own web app after the thing you're trying to break is basically taunting you.

ftp enumeration

Anonymous login's off, so that's the lazy route gone:

ftp anonymous login denied

And before anyone gets hopeful, this isn't the 2.3.4 build with the smiley-face backdoor everyone still brings up. 3.0.3 is clean, the only CVE against it is a 2023 DoS, and knocking over someone's FTP daemon isn't the goal here:

CVEdetails showing vsftpd 3.0.3 has no code execution CVEs, only a 2023 DoS

Dead end without creds. On to the web app.

the "security dashboard" has a hole in it

The dashboard is playing dress-up as a real SOC tool. Stat tiles for security events, failed logins, port scans, all trending in vaguely alarming directions, none of it clickable, none of it meaning anything. I'd bet money the numbers are Math.random() wearing a trench coat:

Security Dashboard homepage with fake stat tiles

The one feature that actually does something is "Security Snapshot (5 Second PCAP + Analysis)", which lives at a URL like /data/<n>. <n> is just a plain integer. No session check, no ownership check, nothing stopping me from typing a different number in:

/data/6 endpoint showing packet stats and a Download button, with the id visible in the URL

That's an IDOR, plain and simple. Walk <n> down to /data/0 and it hands over a capture that belongs to someone else entirely. Open it in Wireshark and there's an FTP login going by in plaintext:

Wireshark showing FTP USER nathan and PASS Buck3tH4TF0RM3! in cleartext

USER nathan
PASS Buck3tH4TF0RM3!

ftp login and the user flag

$ ftp 10.129.48.97
Name: nathan
Password: ********
230 Login successful.

user.txt is locked down to -r--------, readable only by nathan. Turns out that's me now:

FTP session as nathan listing home directory and downloading user.txt

e7f5c434665f925ba7b58e6bbd6e488c

User flag's done. Root's next.

privesc: cap catches a cap

SSH'd in as nathan. Ran SUID3NUM to sweep for SUID binaries and cross-check them against GTFOBins automatically instead of doing it by hand, and it came back empty. Checked capabilities next, since half the time people lock down SUID bits and completely forget capabilities are a separate thing:

$ getcap -r / 2>/dev/null
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip

getcap output showing python3.8 with cap_setuid,cap_net_bind_service+eip

There it is. cap_setuid+eip on the Python interpreter means it can hand itself root without ever needing the SUID bit. GTFOBins has the exact one-liner memorized for this:

$ /usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'

root shell obtained via python3.8 setuid(0), confirmed with id

# id
uid=0(root) gid=1001(nathan) groups=1001(nathan)

Well. That was easy. Should've seen the pun coming from a box called Cap getting rooted by a capability, but I didn't, and I'm choosing to be delighted about it instead of annoyed.

root flag

# cat /root/root.txt
1f6cc0e868c53b98d48f155c7a643379

root.txt contents after cd /root

takeaway

Three small mistakes, one full compromise. A dashboard that hands out other people's data to anyone who can count, a password that never should've touched the wire in plaintext, and a capability that made the "no SUID binaries found" check meaningless. getcap -r / takes five seconds and it should be as automatic as the SUID sweep at this point. SUID3NUM won't catch capabilities since that's not what it's built for.

← back to writeups