← Back to All Vulnerabilities

Insecure Direct Object References (IDOR) Vulnerability

Insecure Direct Object References occur when an application provides direct access to objects based on user-supplied input. As a result of this vulnerability, attackers can bypass authorization and access resources directly by modifying the resource reference in the request.

IDOR Demonstration

User Profile Viewer

Enter a user ID to view their profile:

https://example.com/profile.php?id=123

IDOR Vulnerability:

This application has an IDOR vulnerability because it:

  1. Accepts a user ID directly in the URL (id=123)
  2. Does not verify if the logged-in user has permission to view the requested profile
  3. Shows sensitive information that should only be visible to the profile owner

Try It: Change the user ID to 456 or 789 to access other users' profiles and view their sensitive information!

User Profile Viewer (Secure)

Enter a user ID to view their profile:

https://example.com/secure_profile.php?id=123

Security Implementation:

This application protects against IDOR by:

  1. Checking if the requested user ID matches the logged-in user's ID
  2. Only allowing admins to view other users' profiles
  3. Returning an "Access Denied" error for unauthorized requests
// SECURE: Add authorization check
$is_admin = ($current_user['role'] === 'admin');
$is_own_profile = ($profile_id === $current_user['id']);

// Only allow viewing if it's the user's own profile or if the user is an admin
if (!$is_own_profile && !$is_admin) {
    echo "Access denied. You can only view your own profile.";
    exit;
}
                    

User Profile Viewer (Indirect References)

Enter a user reference to view their profile:

https://example.com/indirect_profile.php?ref=usr_a7f3bc

Indirect Reference Implementation:

This application uses indirect object references by:

  1. Using non-sequential, unpredictable reference values instead of database IDs
  2. Maintaining a server-side mapping of references to actual user IDs
  3. Still implementing proper authorization checks as an extra layer of security
// Create a mapping of public IDs to actual user IDs
$reference_map = [
    'usr_a7f3bc' => 123,  // Alice
    'usr_9e2d5f' => 456,  // Bob
    'usr_c4e8d1' => 789   // Admin
];

// Map the reference to an actual user ID
if (!isset($reference_map[$profile_ref])) {
    echo "User not found";
    exit;
}

$profile_id = $reference_map[$profile_ref];
                    

Note: Even if an attacker tries ref=usr_9e2d5f, the application will still check if they have permission to view that profile.

Common IDOR Vulnerabilities

1. Direct References in URLs

Using database IDs or other sequential identifiers directly in URLs:

2. Predictable Resource Locators

Using predictable filenames or paths that can be easily guessed:

3. Client-Side Access Control

Relying on client-side JavaScript to hide or show sensitive information:

// Vulnerable: Client-side only access control
<script>
    if (currentUser.role === 'admin') {
        document.getElementById('adminPanel').style.display = 'block';
    }
</script>
        

4. Insecure API Endpoints

API endpoints that don't validate authorization for requested resources:

// API endpoint that returns data based on ID without checking permissions
app.get('/api/user/:id', (req, res) => {
    const userData = getUserById(req.params.id);
    res.json(userData);
});
        

5. Horizontal and Vertical Privilege Escalation

Real-World Impact:

How to Prevent IDOR Vulnerabilities

1. Implement Proper Access Controls

Always verify that the current user has permission to access the requested resource:

// Check if user has permission to view the resource
function canAccessResource($userId, $resourceId) {
    $resource = getResource($resourceId);
    
    // Check if the resource belongs to the user or if user is admin
    return $resource['owner_id'] === $userId || isAdmin($userId);
}

// Use in controllers/handlers
if (!canAccessResource($currentUserId, $requestedResourceId)) {
    return accessDenied();
}
        

2. Use Indirect Object References

Replace direct database IDs with temporary, user-specific tokens:

// Generate mapping when user logs in
$userResourceMap = [];
foreach ($userResources as $resource) {
    $token = generateRandomToken();
    $userResourceMap[$token] = $resource['id'];
    $_SESSION['resource_map'] = $userResourceMap;
}

// Later, when user requests a resource by token
$resourceId = $_SESSION['resource_map'][$token] ?? null;
if ($resourceId) {
    // Resource token is valid for this user's session
    return getResource($resourceId);
}
        

3. Implement Role-Based Access Control (RBAC)

Define access rules based on user roles and resource types:

// Define access control rules
$accessRules = [
    'user' => [
        'profile' => ['read_own', 'update_own'],
        'document' => ['read_own', 'create', 'update_own', 'delete_own'],
        'comment' => ['read', 'create', 'update_own', 'delete_own']
    ],
    'admin' => [
        'profile' => ['read', 'update', 'delete'],
        'document' => ['read', 'create', 'update', 'delete'],
        'comment' => ['read', 'create', 'update', 'delete']
    ]
];

// Check if action is allowed
function canPerformAction($userRole, $resourceType, $action, $resourceOwnerId = null) {
    global $accessRules;
    
    if (!isset($accessRules[$userRole][$resourceType])) {
        return false;
    }
    
    $allowedActions = $accessRules[$userRole][$resourceType];
    
    // Check for specific actions
    if (in_array($action, $allowedActions)) {
        return true;
    }
    
    // Check for owner-specific actions
    if (in_array($action . '_own', $allowedActions) && $resourceOwnerId === getCurrentUserId()) {
        return true;
    }
    
    return false;
}
        

4. Use UUID Instead of Sequential IDs

Replace easily guessable sequential IDs with UUIDs in database and URLs:

// Database table with UUID instead of auto-increment ID
CREATE TABLE users (
    id CHAR(36) PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    /* other columns */
);

// Generate UUID for new user
function createUser($userData) {
    $userData['id'] = generateUUID();
    // Insert into database
    return $userData['id'];
}
        

5. Implement API Security Best Practices

6. Use Server-Side Sessions

Don't rely on client-side data for access control decisions:

// Use server-side session data to track user permissions
$_SESSION['user_id'] = $authenticatedUser['id'];
$_SESSION['user_role'] = $authenticatedUser['role'];

// Don't send role information to the client for access control decisions
// Instead, check on the server for each request