IDOR - Insecure Direct Object Reference
Insecure Direct Object Reference (IDOR) is an access control vulnerability in which an application uses direct references to internal objects (user IDs, file paths, database keys) without performing authorization checks. Attackers manipulate these references to gain unauthorized access to data. IDOR is part of OWASP A01:2021 (Broken Access Control) and is one of the most common critical vulnerabilities in web applications and APIs.
IDOR is deceptively simple—and has been rewarded many times in multi-million-euro bug bounty programs. Changing a user ID parameter from ?user=123 to ?user=124 to gain access to someone else’s account data: that is IDOR in its purest form. Although it sounds simple, IDOR regularly causes massive data breaches. Through IDOR, millions of customer records, credit card details, and private messages have been accessed without authorization at various companies.
Types of IDOR and Examples
Most common IDOR manifestations:
1. Numeric IDs in URLs:
# Attacker is logged in as User 123:
GET /api/invoices/456 → views invoice for User 456!
GET /profile/789 → views User 789’s profile!
GET /download/file/1001 → downloads another user’s file!
2. IDs in Request Body:
POST /api/update-profile
{"userId": 456, "email": "angreifer@evil.com"} ← another user’s ID!
3. IDs in HTTP headers:
GET /api/dashboard
X-User-ID: 456 ← manipulation!
4. IDOR in file downloads:
GET /download?file=../users/456/private-docs/contract.pdf
GET /invoice?id=INV-2026-0042 → different invoice number!
5. IDOR in API keys/tokens:
GET /api/webhooks/webhook_abc123 → third-party webhook configuration!
DELETE /api/sessions/sess_xyz789 → terminate third-party session!
6. IDOR with Hash/UUID (but no rate limit):
GET /share/a3f5c8e2-1234-5678-abcd-ef0123456789
→ UUIDs are not unrateable if generation is predictable!
→ Or: Endpoint is internal and UUID is in the source code!
7. Mass Assignment + IDOR:
PUT /api/user/123
{"role": "admin"} → Privilege escalation via IDOR!
{"balance": 99999} → Manipulate account balance!
8. IDOR in GraphQL:
query {
user(id: "456") { ← different user ID!
email, phone, address
}
}
# If no object-level authorization: third-party data visible!
Real-world examples (bug bounties):
Uber (2014): IDOR allowed access to all driver profiles
Tesco (2020): Order IDOR - every order number accessible
Instagram: Mass IDOR via Follower API → millions of profiles
PayPal: IDOR in invoice system → third-party invoices viewable
IDOR Detection
Testing methodology in penetration testing:
Step 1: Identify parameters
→ Find all object references in requests:
- Numeric IDs: /api/order/123
- Alphanumeric: /doc/AB123CD
- Hashes: /file/md5hash
- Base64-encoded IDs: /profile/dXNlcl8xMjM=
- Sequential IDs: INV-2026-0001, INV-2026-0002
- UUIDs: /webhook/550e8400-e29b-41d4-a716-446655440000
Step 2: Create two accounts (User A + User B)
User A: account_a@test.com / Resource ID: 1001
User B: account_b@test.com / Resource ID: 1002
# Logged in as User B:
GET /api/resource/1001 → Does this retrieve User A’s resource?
Step 3: Vertical IDOR (different role)
Regular user attempts admin endpoints:
GET /api/admin/users/123 → using regular user token
Step 4: Analyze API documentation
→ Swagger/OpenAPI docs: what parameters exist?
→ JavaScript files: internal API endpoints?
→ Backup files: /api.md, /api.txt, /swagger.json
Burp Suite Intruder for IDOR:
# Automatically iterate parameter "id":
GET /api/user/§1§
# Payloads: 1, 2, 3, ... 1000
# → Filter: response length != error response
IDOR hidden in API responses:
# Sometimes IDs are hidden in responses:
GET /profile
{
"user": "alice",
"_links": {
"invoice": "/api/invoices?userId=123" ← ID in link!
}
}
Secure Design Against IDOR
Countermeasure - Authorization at the Object Level:
1. Object-Level Authorization (Best Practice):
# NOT:
def get_invoice(invoice_id: int):
return Invoice.get(invoice_id) # No owner check!
# GOOD:
def get_invoice(invoice_id: int, current_user: User):
invoice = Invoice.get(invoice_id)
if invoice.user_id != current_user.id:
raise PermissionDenied("Not authorized")
return invoice
2. Indirect Object References (Mapping):
# Instead of direct DB IDs: session-specific references
# On login: Create map
session['resource_map'] = {
'doc1': actual_db_id_1234,
'doc2': actual_db_id_5678
}
# URL: /download?ref=doc1 (not /download?id=1234!)
# Server resolves 'doc1' → 1234 (session-specific!)
3. Cryptographic tokens for resources:
# HMAC-signed resource tokens:
import hmac, hashlib
def generate_resource_token(user_id: int, resource_id: int, secret: str) -> str:
message = f"{user_id}:{resource_id}".encode()
return hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
# Validation:
def validate_resource_token(user_id, resource_id, token, secret):
expected = generate_resource_token(user_id, resource_id, secret)
return hmac.compare_digest(token, expected) # Timing-safe!
4. UUIDs as an alternative (use with caution!):
# UUID v4 is random (not guessable) - but:
# → URL/log exposure problem!
# → Security through obscurity - not true AuthZ!
# UUIDs DO NOT REPLACE Authorization - they supplement it!
5. Middleware/Policy-based AuthZ:
# CASL (JavaScript):
ability.can('read', Invoice, { userId: currentUser.id })
# OPA (Open Policy Agent):
allow {
input.user.id == input.resource.owner_id
}
# Django Rest Framework:
class IsOwner(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.user == request.user
Automated IDOR testing in CI/CD:
# Tool: pytm (Threat Modeling) + Tests
# Tool: Semgrep Rules for IDOR patterns
# GitHub Action: DAST scan on staging
IDOR vs. related vulnerabilities
IDOR vs. other Broken Access Control types:
IDOR (Horizontal Privilege Escalation):
→ Access to resources of OTHER users (same role)
→ Example: User A views User B’s invoices
Vertical Privilege Escalation:
→ User accesses admin functions
→ Example: Regular user activates admin dashboard
Mass Assignment:
→ Setting fields that are not permitted
→ Example: {"role": "admin"} in user update
→ Often combined with IDOR!
Forced Browsing:
→ Direct URL input to protected resources
→ Example: /admin/users without login
API-Level IDOR (BOLA - Broken Object Level Authorization):
→ OWASP API Security Top 10 #1 (API1:2023)
→ Same concept as IDOR, but in an API context
→ Particularly dangerous with REST APIs (CRUD operations!)
IDOR vs. BOLA:
IDOR: older term, originally for web apps
BOLA: OWASP API Security term, means the same thing
In practice: both terms are used interchangeably