Application Security IDOR

IDOR in Practice: How Broken Access Control Happens in Real Applications

Author
Dulanjana Fernando
Jul 26, 2026  •  12 min read  •  80 views
IDOR in Practice: How Broken Access Control Happens in Real Applications

Insecure Direct Object Reference(IDOR) vulnerability arises when the application allows users to access or modify objects that they are not authorized to do so. To exploit the IDOR vulnerability, it does not require an experienced hacker with custom malware or a terminal wizard, it usually just needs a curious user changing a single digit in a URL parameter in their web browser's address bar.

1. What is IDOR? A Bug Hiding in Plain Sight

Insecure Direct Object Reference(IDOR) is an access control vulnerability that arises when applications do not check for the user's permission to view or modify the object that is defined/referenced using a URL parameter.

Imagine you are logged into your bank's web application, and you are viewing your bank account balance from the URL https://mybank.com/dashboard/123345643. You are authenticated, and the application knows who you are. But, out of pure curiosity, you change the account number in the URL https://mybank.com/dashboard/123345644 and press the enter button.
And page loads and displays another person's dashboard with their account balance.

No injection, no malware, no scripts, no complicated payloads used. Just a small URL parameter change.
This is the simplicity of an Insecure Direct Object Reference (IDOR) vulnerability. This vulnerability is dangerous because the knowledge barrier required to exploit this vulnerability is extremely low, and it can often be used for horizontal privilege escalation.

2. The Core Root Cause (Confusing AuthN with AuthZ)

Authentication(AuthN) is not the same as Authorization(AuthZ). The most common IDOR vulnerabilities exist because of confusion between these two concepts.

Authentication:- The process of verifying the identity of the user. (Who are you?)
Authorization:- The process of determining if the user has permission to perform a specific action.

The application should never assume that the user has access automatically because the user has successfully logged in.

Frontend Validation is NOT a Security Boundary

One of the most common mistakes in application security is to delegate input validation to the frontend. Hiding something from view or validating a user input using JavaScript is good User Experience(UX), but not good Application Security. The browser is controlled by the user, user do not need to see the button to submit a malicious request. Just the endpoint and the variables that are accepted are enough.

# Frontend Validation using "if (user.isAdmin)" with DELETE endpoint code visible
if (user.isAdmin) { 
    showDeleteButton(); 
}

function showDeleteButton() {
    const deleteButton = document.createElement("button");
    deleteButton.textContent = "Delete Invoice";

    deleteButton.onclick = () => {
        // Sends a request to delete an invoice
        fetch("/api/invoices/202", {
            method: "DELETE",
            headers: {
                "Authorization": "Bearer [access_token]"
            }
        });
    };

    document.body.appendChild(deleteButton);
}

User can manipulate user.isAdmin value using Developer Tools of the browser and successfully submit a DELETE request. Since the endpoint URL is exposed to the user, an attacker can bypass the JavaScript validation completely and submit the delete request to the server. If the server does not check the logged-in user's permissions, the attacker will be able to delete an invoice that does not belong to the current user by successfully exploiting the IDOR vulnerability.

Secure applications should not use the frontend validation for security and should hide both the UI elements and the code that gets executed.

# Backend validation before displaying UI elements and related code
// The Delete button-related code is displayed only to the admins
// Even the JavaScript code is not visible to ordinary users because the PHP is run on the server 
// the server removes the DELETE button-related HTML and JavaScript code when the page is served to the client

// No endpoint exposure to unauthorized users
{% if user.isAdmin %}
    showDeleteButton(); 

function showDeleteButton() {
    const deleteButton = document.createElement("button");
    deleteButton.textContent = "Delete Invoice";

    deleteButton.onclick = () => {
        // Sends a request to delete an invoice
        fetch("/api/invoices/202", {
            method: "DELETE",
            headers: {
                "Authorization": "Bearer [access_token]"
            }
        });
    };

    document.body.appendChild(deleteButton);
}
{% endif %}

Never Trust Client-Supplied Ownership

A secure application should be careful when accepting ownership information from the client-side. When the user is authenticated, the server already knows about the user who has logged in. Then, the application should derive the user's identity from the session rather than trusting the information received by the client-side.

3. Code Breakdown - Vulnerable vs. Hardened

Most of the time, eliminating the IDOR vulnerability is a matter of one code line, one authorization check.

# Vulnerable Code
// Checks authentication, but ignores authorization
$invoiceId = $_GET['id'] ?? null;

// checking if the user is authenticated and logged in
if (!$authService->isAuthenticated()) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// Fetching the invoice using the URL parameter
// Vulnerable because the application does not check if the invoice belongs to the logged-in user
// User can manipulate the 'id' URL parameter and access any invoice in the database
$stmt = $pdo->prepare('SELECT * FROM invoices WHERE id = :id');
$stmt->execute(['id' => $invoiceId]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$invoice) {
    http_response_code(404);
    echo json_encode(['error' => 'Invoice not found']);
    exit;
}

echo json_encode($invoice);

The above vulnerable code does not check the requested invoice against the logged-in user. An attacker can exploit this IDOR vulnerability to access all the invoices in the database regardless of the invoice ownership.

# Hardened Code
// Checks authentication
$invoiceId = $_GET['id'] ?? null;
$currentUserId = $authService->getUserId();

if (!$authService->isAuthenticated()) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// Invoice ownership is checked against the currently logged-in user
$stmt = $pdo->prepare('SELECT * FROM invoices WHERE id = :id AND user_id = :user_id');
$stmt->execute([
    'id'      => $invoiceId,
    'user_id' => $currentUserId
]);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$invoice) {
    // returns 404 error instead of 403 error
    http_response_code(404);
    echo json_encode(['error' => 'Invoice not found']);
    exit;
}

echo json_encode($invoice);

Returning 404 vs. 403

Another important application security design decision is what response the application should return when authorization fails. Many developers' automatic resolution is to return 403 Forbidden error response. While this response is technically correct, it can lead to object availability disclosure.

In an attacker's context:
GET /api/invoices/1001 returns 200 response --> Invoice Exists
GET /api/invoices/9001 returns 404 response --> Invoice Does Not Exists
GET /api/invoices/2001 returns 403 response --> Invoice Not Accessible but Exists

However, there is no universal rule that an endpoint should return a 404 error for every situation. Whether the endpoint should return a 404 response or a 403 response completely depends on the application's security requirements and design.

4. The UUID Myth - Why Obscurity Isn't Security

Replacing sequential numeric IDs with UUIDs is an improvement, but not a valid fix for IDOR vulnerability. Yes, guessing UUIDs using a script for a brute-force attack is statistically not possible. But a UUID is not a security token. It is an identifier.
There are many ways that a UUID can be leaked. Application logs, Screenshots, frontend JavaScript, email responses, third-party analytics tools, shared link or a clickable link in a document are some of the possibilities.

If the backend does not check for authorization, the resource can still be accessed.

5. The Hidden Danger - IDOR on State-Changing Actions

When discussing IDOR vulnerability, GET requests gets 99% of the attention. Reading someone else's invoice data, downloading another user's PDF files, accessing another user's private data, etc.

While read-only IDOR vulnerabilities are a major privacy violation, IDOR on state-changing operations (POST, PUT, PATCH, DELETE) is far more dangerous. It can transform a data-leak into a full-blown data-destruction or an account takeover.

Golden Rule to eliminate IDOR vulnerability is that the client-side should never decide who owns the resource. The ownership should always be derived from the authenticated session in the server.

6. How to Audit & Prevent IDOR

Knowing what an IDOR vulnerability is and how to eliminate it is easy, but actively eliminating it in a fast-growing code base with many developers working in parallel is a struggle. One common cause of IDOR is the lack of centralized authorization logic. One developer might remember to authorize the requests to the endpoint, while one might forget.

Centralize Authorization Logic

Instead of scattering the ownership authorization logic over multiple controllers, isolating the ownership logic in a single reusable class can help to eliminate the IDOR vulnerability. By abstracting the policy logic, any changes to how the ownership authorization works can be managed from one place rather than hundreds of individual controller functions.

# Centralized and reusable ownership logic
class InvoicePolicy 
{
    public function view(User $user, Invoice $invoice): bool 
    {
        // ownership logic check for invoices
        return $user->id === $invoice->user_id;
    }
}



// In controller or script
$invoice = $invoiceRepository->find($invoiceId);

if (!$policy->view($currentUser, $invoice)) {
    http_response_code(404);
    echo json_encode(['error' => 'Invoice not found']);
    exit;
}

Filter by Ownership at the Database Layer

Whenever possible, it is a good practice to retrieve the resources from the database based on the current user. This approach prevents the unauthorized objects from being retrieved from the database in the first place. It also reduces the risk of the developer forgetting to perform authorization checks before returning results to the frontend.

Instead of SELECT * FROM invoices WHERE id=?; use SELECT * FROM invoices WHERE id=? AND user_id=?; when possible.

7. Final Thoughts - Every Request is a Authorization Check

IDOR (Insecure Direct Object Reference) is a type of Broken Access Control vulnerability and falls under the Broken Access Control category in the OWASP Top 10, one of the most critical and commonly exploited web application security risks. It resides in the OWASP Top 10 not because it's difficult to understand, but because it's easy to overlook.

When the applications grow, new endpoints and functionalities get added, features gets relased under tight deadlines. All it takes is one forgotten ownership check for a user to get access to data or functionality that was never meant for them.

The best defence is not to rely on hidden URLs, UUIDs, JavaScript validation or hoping attackers will not notice, it is to always treat every request as a authorization decision.

Tags:
IDOR

You might also like...

Top-Down Web Framework Code Review: A Step-by-Step Security Audit Methodology
Application Security Code Review
Top-Down Web Framework Code Review: A Step-by-Step Security Audit Methodology

When auditing a modern web application, jumping straight into reading the contro...

Read More
File Uploads in Practice: How an Innocent Image Becomes Remote Code Execution
Application Security RCE
File Uploads in Practice: How an Innocent Image Becomes Remote Code Execution

File upload features such as profile pictures, document submissions and image ga...

Read More

Stay Updated

Get notified when new walkthroughs and security articles are published.