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.
Enter a user ID to view their profile:
This application has an IDOR vulnerability because it:
id=123
)Try It: Change the user ID to 456
or 789
to access other users' profiles and view their sensitive information!
Enter a user ID to view their profile:
This application protects against IDOR by:
// 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; }
Enter a user reference to view their profile:
This application uses indirect object references by:
// 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.
Using database IDs or other sequential identifiers directly in URLs:
https://example.com/profile?id=123
https://example.com/documents/12345
https://example.com/api/orders/5678
Using predictable filenames or paths that can be easily guessed:
https://example.com/receipts/2023-03-25-order-123.pdf
https://example.com/users/alice/profile_photo.jpg
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>
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); });
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(); }
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); }
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; }
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']; }
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