Bug bounty platforms are full of writeups about researchers earning several hundred dollars for a single, almost simple finding, for example, changing a number in a URL, and suddenly you are looking at someone else’s data. This is an IDOR vulnerability, one of the most common, most underestimated, and most consistently rewarded vulnerabilities in web application security.
Vulnerability Explained
IDOR stands for Insecure Direct Object Reference. It happens when an application exposes a direct reference to an internal object, a database row, a file, or an account, and lets a user access that object simply by changing the reference, without properly checking whether they are actually allowed to see it.
The “object” in question could be almost anything: an invoice ID, a user profile number, a file path, an order confirmation, a chat message ID, or an API resource identifier. The “reference” is whatever value the application uses to point at that object, typically visible in a URL, a query parameter, a form field, or a JSON body sent to an API.
The problem is not that the reference exists; applications need some way to identify records. The problem is that the server trusts the reference blindly. It fetches whatever object the ID points to and hands it back, without asking “does the person requesting this actually own it, or have permission to view it?” That missing question is the entire vulnerability. It’s not a flaw in cryptography or a memory corruption bug. It is a missing authorization check, which is exactly why it’s so easy to introduce and so easy to overlook in code review.
Protect your application from the Clickjacking Vulnerability. We help you detect and secure your website before attackers do. Email Us to secure your website today
This is what separates IDOR from authentication problems. The user in an IDOR scenario is usually logged in legitimately, using their own valid session. The failure is one of authorization, not authentication. The system correctly knows who you are, but fails to verify what you’re allowed to touch.
How IDOR Works
At a technical level, IDOR emerges from a predictable chain of decisions made during development:
- A resource is stored with an identifier, often a sequential integer, but sometimes a predictable string or an easily-guessable UUID.
- That identifier is exposed to the client, usually in a URL path, query string, hidden form field, or API payload.
- When a request comes in, the server looks up the resource by that identifier.
- The server does not verify that the currently authenticated user has permission to access that specific resource. It verifies that the user is logged in, or performs no check whatsoever.
The vulnerability lives in step 4. Everything up to that point is completely normal, functional application design. Nothing about steps 1 through 3 is inherently insecure; using IDs to reference database rows is unavoidable. The insecurity is purely the absence of an ownership or permission check at the moment of retrieval.
This is why IDOR is often called a “logic flaw” rather than a technical exploit; there’s no malformed input, no injection payload, no buffer overflow. The attacker’s request looks exactly like a legitimate one; only the ID has changed.
Workflow
A typical IDOR discovery and exploitation workflow looks like this:
- Create two test accounts: Most IDOR testing requires comparing what Account A can see versus what Account B can see.
- Map out the application: Identify every place an object reference appears on profile pages, order histories, document downloads, message threads, admin panels, and API endpoints.
- Capture a legitimate request: Using a proxy tool like Burp Suite or OWASP ZAP, intercept a request made by Account A to view one of its own resources (e.g.,
GET /api/invoices/1042). - Modify the identifier: Replace
1042with a different value, ideally one belonging to Account B, or simply an adjacent number like1041or1043. - Replay the request: Using Account A’s session/cookie/token, keeping A’s authentication intact while requesting B’s data.
- Observe the response: If the server returns Account B’s data (an invoice, a private message, a profile with an email address) instead of an authorization error, you have confirmed an IDOR.
- Assess impact: Determine whether the flaw is read-only (viewing data) or also allows write/delete actions (modifying or deleting someone else’s resource); this is far more severe.
It is important to note that the more you deep dive in the application you will get more chances for IDOR testing.
Common Use Cases and Risks
IDOR shows up across nearly every category of web application, which is part of why it remains so prevalent:
User profile and account data, changing a user_id parameter to view another person’s name, email, address, or phone number.
Financial records by accessing another customer’s invoices, bank statements, or payment history, or by altering an order or transaction ID.
File and document access leads to direct file paths or download tokens that let one user retrieve another’s uploaded documents, medical records, or contracts.
Messaging and support tickets reading private conversations or support tickets belonging to other users.
Administrative functions, which are the most severe cases, where an IDOR in an admin-facing endpoint allows a regular user to edit, delete, or impersonate other accounts.
API endpoints in mobile and single-page applications frequently expose REST or GraphQL APIs where object IDs are passed directly, and these are tested far less often than the visible web UI.
The risk profile ranges widely. A read-only IDOR exposing a mailing address might be a low-severity finding. An IDOR that lets any authenticated user delete another user’s data, escalate privileges, or access another company’s records in a multi-tenant SaaS product can be a critical vulnerability, which is why bounty payouts for IDOR range from modest sums to five and six figures depending on data sensitivity and exploitability at scale.
Proof of Concept
A PoC for IDOR is usually just two HTTP requests placed side by side. Consider an endpoint that returns order details:
GET /api/v1/orders/8841 HTTP/1.1 Host: shop.example.com Cookie: session=abc123_belongs_to_userA
This returns Account A’s own order. Now the tester changes only the ID:
GET /api/v1/orders/8842 HTTP/1.1 Host: shop.example.com Cookie: session=abc123_belongs_to_userA
If 8842 belongs to a different customer, and the server returns that customer’s order details, name, and shipping address. Items purchased using Account A’s own session cookie confirm the IDOR. No token was forged, no password was cracked; the request is syntactically identical to a normal one.
A simple server-side snippet illustrating the flaw might look like this in a Node.js application and Express-style pseudocode:
// Vulnerable: no ownership check
app.get('/api/v1/orders/:id', requireLogin, (req, res) => {
const order = db.getOrderById(req.params.id);
res.json(order); // returned regardless of who owns it
});
The fix requires one additional condition:
// Fixed: verifies ownership before returning data
app.get('/api/v1/orders/:id', requireLogin, (req, res) => {
const order = db.getOrderById(req.params.id);
if (!order || order.userId !== req.user.id) {
return res.status(404).json({ error: 'Not found' });
}
res.json(order);
});
That single if statement is, quite literally, the difference between a secure endpoint and a vulnerability report.
Mitigation and Defense
Fixing and preventing IDOR is conceptually simple, even if it requires discipline across an entire codebase:
- Enforce object-level authorization on every request. Never assume that a valid session alone is sufficient. Every lookup of a user-owned resource should check that the resource actually belongs to the requester.
- Use indirect references where practical. Instead of exposing raw database primary keys, use per-user, per-session, or randomly generated reference tokens that are meaningless outside the context of the requesting user.
- Adopt a centralized authorization layer. Rather than ownership checks throughout individual route handlers, use middleware, policy objects, or an access-control framework so the check can’t be accidentally omitted in new code.
- Authorization logic should fail closed; if there’s any chance of permission, the safe default is to reject the request, not serve the data.
- Avoid predictable, sequential identifiers where feasible. While obscurity is not a substitute for real authorization checks, non-sequential IDs (UUIDs) reduce the ease of enumeration.
- Test with multiple accounts as a standard QA practice. Automated and manual testing should routinely attempt cross-account access as part of the development lifecycle, not just during a pentest.
- Log and monitor unusual access patterns, such as one account rapidly requesting sequential resource IDs, which can indicate active IDOR probing.
The lesson behind is that IDOR is not technically sophisticated; it is a reminder that authentication answers “who are you?” while authorization must separately answer “what are you allowed to see?” Every endpoint that skips the second question is a candidate for the next bug bounty report.
If you find this proof of concept useful, check out our cyber news on the Claude Mythos, an artificial intelligence cybersecurity researcher who found thousands of old zero-day flaws.