Recon
Three ports came back open — FTP, SSH, and an HTTP service running gunicorn. The web app on port 80 identified itself as a "Security Dashboard," which was immediately the most interesting thing on the box.
$ nmap -sC -sV -oN nmap/initial 10.10.10.245 21/tcp open ftp vsftpd 3.0.3 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 80/tcp open http gunicorn # Service info: gunicorn confirms a Python web app behind it
FTP on 21 is worth keeping in mind — vsftpd without explicit TLS configured means any captured auth is plaintext. I noted that and moved to the web app first.
Enumeration
The web app presents a network security dashboard after login. The most interesting feature is "Security Snapshot (5 Second)" — it runs a packet capture for five seconds and then shows you stats and a download link. The download URL pattern is /data/1, which is my capture ID.
I changed the ID to /data/0. The server handed me a PCAP without any access check — classic IDOR. The file at ID 0 is a capture taken when the server first started up, which means it contains whatever traffic hit the box at boot time.
/data/<id> endpoint performs no ownership check — decrementing the ID to 0 returns a PCAP captured at server startup belonging to a different session.
Analyzing the PCAP
I opened the file in Wireshark and filtered on ftp. Two packets stood out immediately:
# Wireshark display filter: ftp Request: USER nathan Request: PASS Buck3tH4TF0RM3! # Plaintext FTP authentication captured at server startup
The credentials were right there — nathan / Buck3tH4TF0RM3!. FTP sends everything in the clear, so any packet capture that includes an authentication sequence will expose the password verbatim.
Foothold
I tried the captured credentials against SSH rather than FTP — SSH is far more useful for an interactive shell, and people reuse passwords across services constantly.
$ ssh nathan@10.10.10.245 nathan@10.10.10.245's password: Buck3tH4TF0RM3! nathan@cap:~$ # Shell as nathan — password reuse from FTP to SSH
Direct shell as nathan. User flag is sitting in /home/nathan/user.txt.
Privilege Escalation
Standard Linux privesc checklist: sudo rights, SUID binaries, cron jobs, writable paths, and — often skipped — Linux capabilities. I ran getcap first since it's quick and frequently rewarding on HTB boxes.
$ getcap -r / 2>/dev/null /usr/bin/python3.8 = cap_setuid+ep
That's the entire privesc right there. cap_setuid lets the process call setuid(0) to set its effective UID to root without needing the SUID bit set on the binary. The ep flag means the capability is both permitted and effective — it applies immediately on execution.
Linux Capability Abuse — cap_setuid
Unlike SUID, Linux capabilities are fine-grained privileges attached to a binary. cap_setuid+ep on the Python interpreter means I can call os.setuid(0) from Python and the kernel will allow it, because the process has the required capability. This gives me the same result as a SUID Python binary — arbitrary UID switching to root.
$ python3 -c "import os; os.setuid(0); os.system('/bin/bash')" root@cap:~# # os.setuid(0) succeeds because of cap_setuid+ep — root shell
Root shell. One line. /root/root.txt is readable.
Flags
Lessons Learned
- IDOR on file download endpoints is a first-order finding: always try decrementing or incrementing IDs to access data belonging to other users or sessions.
- FTP transmits credentials in plaintext — any network capture, including startup captures, can expose FTP authentication. Use SFTP or FTPS instead.
- Linux capabilities (
getcap -r /) are a frequently missed privesc vector and should be on every privesc checklist alongside SUID and sudo. cap_setuid+epgrants arbitrary UID switching without the SUID bit — functionally equivalent to a SUID Python binary and just as dangerous.
getcap -r / regularly. cap_setuid on any interpreter (Python, Perl, Ruby) is essentially a root backdoor. Remove the capability with setcap -r /usr/bin/python3.8 and enforce FTP over TLS or migrate to SFTP entirely.