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=123https://example.com/documents/12345https://example.com/api/orders/5678Using predictable filenames or paths that can be easily guessed:
https://example.com/receipts/2023-03-25-order-123.pdfhttps://example.com/users/alice/profile_photo.jpgRelying 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