Back to Learning Center
highA01:2021CWE-639CWE-284CWE-862

Insecure Direct Object Reference (IDOR)

Insecure Direct Object Reference (IDOR) is a type of access control vulnerability that occurs when an application uses user-supplied input to directly access objects. Attackers can manipulate these references to access unauthorized data—viewing other users' files, modifying their profiles, or accessing sensitive information.

What is IDOR?

IDOR vulnerabilities arise when applications expose internal implementation objects (database records, files, directories) through user-controllable parameters and fail to verify authorization. It's ranked under OWASP A01:2021 Broken Access Control.

Common locations where IDOR occurs:

  • User profile URLs: /user/123/profile
  • File downloads: /download?file=invoice_123.pdf
  • API endpoints: /api/orders/456
  • Form parameters: <input name="user_id" value="789">

Types of IDOR Attacks

Horizontal Privilege Escalation

Accessing resources belonging to other users at the same privilege level:

text
# User 123 is logged in and views their own order
GET /api/orders/1001

# Attacker changes the order ID to view another user's order
GET /api/orders/1002  → Returns order belonging to User 456!

# The server doesn't check if User 123 owns Order 1002

Vertical Privilege Escalation

Accessing resources meant for higher-privileged users:

text
# Regular user views their profile
GET /api/users/123?role=user

# Attacker modifies parameter to access admin functionality
GET /api/users/1?role=admin  → Returns admin user data!

# Or accessing admin endpoints directly
GET /admin/users/list  → No authorization check!

Vulnerable Code Examples

Vulnerable API Endpoint

javascript
// VULNERABLE: No ownership verification
app.get('/api/documents/:id', async (req, res) => {
  const documentId = req.params.id;
  
  // Fetches document without checking who owns it!
  const document = await db.documents.findById(documentId);
  
  if (!document) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  // Returns document to ANY authenticated user
  res.json(document);
});

// Attacker can enumerate: /api/documents/1, /api/documents/2, ...

Vulnerable File Download

javascript
// VULNERABLE: User-controlled filename
app.get('/download', (req, res) => {
  const filename = req.query.file;
  
  // No check if user is allowed to access this file!
  const filepath = path.join('/uploads', filename);
  res.download(filepath);
});

// Attacker requests:
// /download?file=user456_bank_statement.pdf
// /download?file=../../../etc/passwd (Path traversal combo!)

Vulnerable Form Processing

javascript
// VULNERABLE: Trusting hidden form field
app.post('/profile/update', (req, res) => {
  const userId = req.body.user_id; // From hidden form field!
  const newEmail = req.body.email;
  
  // Updates whatever user_id is provided
  db.users.update(userId, { email: newEmail });
  res.send('Profile updated');
});

// Attacker modifies hidden field in browser DevTools:
// <input type="hidden" name="user_id" value="1"> // Admin!

Prevention Strategies

1. Always Verify Ownership

javascript
// SECURE: Check ownership before access
app.get('/api/documents/:id', async (req, res) => {
  const documentId = req.params.id;
  const userId = req.session.userId; // From authenticated session
  
  const document = await db.documents.findById(documentId);
  
  if (!document) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  // Verify the logged-in user owns this document
  if (document.ownerId !== userId) {
    return res.status(403).json({ error: 'Access denied' });
  }
  
  res.json(document);
});

2. Use Session-Based User ID

javascript
// SECURE: Get user ID from session, not request
app.post('/profile/update', (req, res) => {
  // NEVER trust user-supplied user ID
  const userId = req.session.userId; // From server-side session!
  const newEmail = req.body.email;
  
  db.users.update(userId, { email: newEmail });
  res.send('Profile updated');
});

// Even if attacker modifies request, they can only update their own profile

3. Query with User Context

javascript
// SECURE: Include user context in database query
app.get('/api/orders/:id', async (req, res) => {
  const orderId = req.params.id;
  const userId = req.session.userId;
  
  // Query includes both order ID AND user ID
  const order = await db.orders.findOne({
    _id: orderId,
    userId: userId  // Can only find orders belonging to this user
  });
  
  if (!order) {
    // Returns 404 whether order doesn't exist OR belongs to someone else
    // This prevents information disclosure about valid order IDs
    return res.status(404).json({ error: 'Order not found' });
  }
  
  res.json(order);
});

4. Use Indirect References

javascript
// SECURE: Map user-specific index to actual ID
app.get('/api/documents/:index', async (req, res) => {
  const index = parseInt(req.params.index);
  const userId = req.session.userId;
  
  // Get user's documents and access by index
  const userDocs = await db.documents.find({ ownerId: userId });
  
  if (index < 0 || index >= userDocs.length) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  // User can only access their own documents via index
  res.json(userDocs[index]);
});

// User sees /api/documents/0, /api/documents/1 (their docs)
// Can't guess other users' document IDs

5. Use UUIDs Instead of Sequential IDs

javascript
import { randomUUID } from 'crypto';

// Defense in depth: Use unpredictable IDs
const document = await db.documents.create({
  _id: randomUUID(), // e.g., '550e8400-e29b-41d4-a716-446655440000'
  ownerId: userId,
  content: content
});

// Still implement access control! UUIDs alone aren't sufficient
// They just make enumeration harder

Testing for IDOR

IDOR can't be found by automated scanners. Manual testing is required:

  1. Create two test accounts (User A and User B)
  2. As User A, create resources (orders, documents, etc.)
  3. Note the IDs/references used in URLs and parameters
  4. Log in as User B and try accessing User A's resources
  5. If successful, IDOR vulnerability confirmed

Security Checklist

  • Implement server-side access control for every resource
  • Get user identity from server session, not client input
  • Include user context in database queries
  • Use indirect object references where possible
  • Use UUIDs instead of sequential IDs (defense in depth)
  • Never expose user IDs in URLs if avoidable
  • Return consistent error responses (don't leak info)
  • Perform manual penetration testing for IDOR

Practice Challenges

View all