← all walkthroughs

Cap

Linux· Easy
owned
2026-06-28
time to own
1m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I discovered a network-monitoring web application that stored past packet captures accessible by a sequential numeric ID. By guessing ID zero, I downloaded the very first capture on the system, which contained a live FTP session and the user nathan's password in cleartext. Those same credentials unlocked an SSH session, giving me an interactive shell.

From that shell, a single recursive capability scan revealed that the system Python interpreter had been granted the cap_setuid Linux privilege, which lets any process it spawns assume root identity. One Python one-liner later, I had a root shell and full control of the host.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"

Attack path — how the box was taken

1ReconnaissanceNetwork port and service version scanning (Nmap)
Mapped open services with a version scan
I sent a targeted Nmap probe to three common ports and found an FTP server (vsftpd 3.0.3), an OpenSSH daemon, and an HTTP service fronted by the Gunicorn Python web server. An anonymous FTP login attempt failed, so attention moved to the web application.
Nmap output: 21/tcp vsftpd 3.0.3, 22/tcp OpenSSH 8.2p1 Ubuntu, 80/tcp Gunicorn
Exact commands 2
Identify open ports and service banners without relying on ICMP ping responses.
nmap -Pn -p21,22,80 -sV --version-light $TARGET
Tested anonymous FTP login -- failed, ruling out that entry point.
ftp $TARGET
2Web EnumerationWeb application enumeration / URL pattern analysis
Identified a packet-capture dashboard and its predictable URL structure
The HTTP service hosted a custom network security dashboard that lets logged-in users start a live capture and download the resulting PCAP file. Each capture was stored at a URL following the pattern /download/<integer-id>. The current session's capture had an ID in the single digits, revealing that IDs were assigned sequentially from zero. I inferred that capture ID 0 -- the very first one ever recorded -- likely existed and predated any user sessions.
Exact commands 1
Confirm whether capture record ID 0 returns a 200 rather than a 404 or 403.
curl -s -o /dev/null -w "%{http_code}" http://$TARGET/data/0
3Credential AccessInsecure Direct Object Reference (IDOR) -- CWE-639
Stole a PCAP belonging to another session via insecure direct object reference
The /download/<id> endpoint served any capture file by ID number without checking whether the requesting user owned it. I fetched /download/0 without any authentication for that record. The file was a packet capture of an FTP session the server had held with itself during initial configuration, and it contained the account name 'nathan' and his password in cleartext inside the FTP control-channel data.
PCAP downloaded from http://$TARGET/download/0 contained FTP USER nathan / PASS [REDACTED: recovered credential]
Exact commands 3
Download the first-ever capture by manipulating the numeric ID to 0.
curl -s http://$TARGET/download/0 -o capture_0.pcap
Filter the PCAP to FTP control traffic and print command + argument pairs -- reveals USER and PASS lines.
tshark -r capture_0.pcap -Y ftp -T fields -e ftp.request.command -e ftp.request.arg 2>/dev/null
Alternative one-liner if tshark is unavailable.
tcpdump -r capture_0.pcap -A 2>/dev/null | grep -E 'USER|PASS'
FixEnforce ownership checks on the packet-capture download endpointCritical
WeaknessThe web application's /data/<id> and /download/<id> endpoints returned any capture file to any requester based solely on the numeric ID in the URL, with no check that the requester owned that capture. Sequential integers made every capture trivially guessable.
FixRecord the authenticated user's session ID alongside every capture at creation time and reject requests where the session does not match the record's owner (return HTTP 403). Replace the sequential integer ID with a cryptographically random UUID so records cannot be guessed even if the authorization check were bypassed. Require authentication for all capture endpoints -- unauthenticated requests should receive a 401 and be redirected to the login page. Verify the fix with an automated authorization test: create a capture as user A, then attempt to download it as user B and confirm a 403 is returned.
4Credential ExposureCleartext protocol credential exposure (FTP) -- CWE-319
FTP transmitted nathan's password in cleartext, making the PCAP a credential dump
FTP sends authentication data as unencrypted ASCII text over the control channel. Because the earlier packet capture happened to contain an FTP login by nathan, reading the PCAP was enough to recover a working password without any cracking. Had SFTP or FTPS been used, the authentication exchange would have been encrypted and unreadable even from a valid PCAP.
Tshark output shows 'USER nathan' and 'PASS [REDACTED: recovered credential]' in plaintext
Exact commands 1
Open in Wireshark GUI; apply display filter 'ftp' and inspect the USER/PASS frames directly.
wireshark capture_0.pcap
FixReplace FTP with an encrypted file-transfer protocolHigh
WeaknessThe server used FTP, which transmits usernames and passwords as unencrypted ASCII over the network. Any packet capture taken during a login -- however captured -- immediately yields working credentials.
FixDisable the vsftpd FTP service and replace it with SFTP (the SSH file-transfer subsystem, enabled by adding a Subsystem sftp line to sshd_config and restricting the user with ChrootDirectory) or FTPS (FTP over TLS, configured in vsftpd with ssl_enable=YES and a valid certificate). If FTP is required by a third party, isolate it to a dedicated VLAN and enforce FTPS. After migration, verify with a packet capture that no authentication data is visible in plaintext.
5Initial AccessCredential reuse / valid-account SSH authentication -- MITRE ATT&CK T1078
Logged in as nathan over SSH using the FTP password
The password recovered from the FTP capture worked unchanged for SSH. I connected as nathan, received a shell, and captured the user flag. A single credential served two different services, so compromising one service immediately unlocked the other.
SSH session established as nathan@$TARGET; user.txt captured.
Exact commands 3
Authenticate with password [REDACTED: recovered credential] when prompted.
ssh nathan@$TARGET
Capture user flag: <user.txt>
cat /home/nathan/user.txt
Confirm context: uid=1001(nathan) gid=1001(nathan).
id
FixEnforce unique passwords for each service account and disable SSH password authenticationHigh
WeaknessThe account 'nathan' used the same password for both FTP and SSH. Compromising the credential for one service immediately granted access to the other without any additional effort.
FixAssign a distinct, randomly generated password to every service account on every system. For SSH specifically, disable password authentication entirely in sshd_config (PasswordAuthentication no) and require public-key authentication. Manage SSH keys through a centralized secrets manager or your configuration management platform. Audit all service accounts now for shared passwords and rotate any that are reused before taking other remediation actions.
6Post-Exploitation EnumerationLinux capability enumeration (getcap)
Found a dangerous Linux capability granted to the Python interpreter
From the nathan shell, I ran a recursive capability scan. The scan revealed that /usr/bin/python3.8 held the cap_setuid+ep capability -- meaning any Python process is allowed to change its own user ID to any value, including 0 (root). This is functionally identical to a SUID root binary. A sudo check returned no useful entries for nathan.
Getcap -r / output: /usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
Exact commands 2
Recursively list all binaries that hold Linux capabilities; the dangerous line is python3.8 = cap_setuid,cap_net_bind_service+eip.
getcap -r / 2>/dev/null
Check for sudo entries -- returned no useful privileges for nathan.
sudo -l
7Privilege EscalationLinux capability abuse (cap_setuid) via Python -- MITRE ATT&CK T1548
Called setuid(0) through Python to spawn a root shell
Because the Python 3 binary could change its own UID to any value, I used a Python one-liner to invoke os.setuid(0) -- switching the process to root -- and then launched /bin/bash. No kernel exploit, no password, and no further enumeration were needed. The capability alone was the vulnerability.
Root shell obtained after running python3 one-liner; id returned uid=0(root); root.txt captured.
Exact commands 3
Invoke setuid(0) through the cap_setuid-enabled binary to inherit root identity, then launch an interactive shell.
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
Verify escalation: uid=0(root) gid=0(root) groups=0(root).
id
Capture root flag: <root.txt>
cat /root/root.txt
FixRemove the cap_setuid capability from the Python interpreterCritical
WeaknessThe system Python 3 binary (/usr/bin/python3.8) was granted the cap_setuid Linux capability, allowing any user who can run Python to change the process's user ID to root. This is equivalent to leaving a SUID root shell on the system.
FixRemove the capability immediately by running: sudo setcap -r /usr/bin/python3.8. Then audit every binary on the host: getcap -r / 2>/dev/null. For any binary that holds a capability, confirm it is documented in your hardening baseline and operationally required. Remove all capabilities that have no documented justification. Incorporate this check into your regular configuration compliance scans (using tools such as Lynis, OpenSCAP, or Ansible's command module) so accidental capability assignments are caught promptly.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more