The first real bug most people find is an IDOR vulnerability, and they usually find it by changing one digit. You open your own order at /orders/4417, type 4418 out of curiosity, and a stranger's delivery address loads. No payload, no encoding trick, no exploit code. Just a number the server trusted. If you want to feel that moment before you read another word about it, open HackerDNA's IDOR Explorer lab and start swapping IDs.
IDOR sits inside Broken Access Control, the number one entry on the OWASP Top 10, and it is the single most common finding on bug bounty programs for a reason: it takes ten seconds to test and developers keep shipping it. This guide covers what an IDOR actually is, how to hunt one properly, the "fixes" that do not fix anything, and how to close the hole for good.
TL;DR: An IDOR vulnerability (Insecure Direct Object Reference) happens when an application takes an identifier from the user, fetches the matching object, and never checks whether that user is allowed to have it. The reference is not the bug. The missing ownership check is. You find IDORs by holding two accounts side by side and replaying account A's requests with account B's IDs, and you fix them with a server-side check tied to the session, not by making the IDs harder to guess.
What Is an IDOR Vulnerability?
IDOR stands for Insecure Direct Object Reference. It is an access control flaw where an application exposes a reference to an internal object (a database row, a file, a document) and lets whoever is logged in supply that reference without verifying they own the object. Change the reference, receive a different object.
Split the name in half and it explains itself. A direct object reference is normal and unavoidable: every application needs some way to say "give me record 4417". The insecure part is the missing authorization check. Your app is not broken because 4417 appears in the URL. It is broken because the server never asked whether the person requesting 4417 has any business seeing it.
Underneath, this maps to CWE-639: Authorization Bypass Through User-Controlled Key, which MITRE rates as having a high likelihood of exploitation. MITRE lists "Insecure Direct Object Reference (IDOR)" and "Broken Object Level Authorization (BOLA)" as alternate names for the same weakness, so if you meet all three terms in a report, they describe the same failure.
The short version every beginner should memorize: authentication asks who you are, authorization asks what you are allowed to touch. An IDOR is an app that nailed the first question and skipped the second.
How an IDOR Attack Works, Step by Step
How does an IDOR attack work? You log in as a legitimate user, find a request that carries an identifier you control, change that identifier to one belonging to someone else, and send it. If the server returns their data instead of an error, the access control is broken. There is no bypass of the login, because you never needed one.
Here is the exchange in full. You are logged into a support portal as a normal customer and you open one of your own tickets:
GET /api/tickets/4417 HTTP/1.1
Host: helpdesk.acme.example
Cookie: session=a9f31c0b7e2d...
HTTP/1.1 200 OK
{"id":4417,"owner":"[email protected]","subject":"Refund request","body":"..."}
You change one character and send it again:
GET /api/tickets/4418 HTTP/1.1
Host: helpdesk.acme.example
Cookie: session=a9f31c0b7e2d...
HTTP/1.1 200 OK
{"id":4418,"owner":"[email protected]","subject":"Card declined","body":"..."}
The server checked your cookie, decided you were logged in, and then read ticket 4418 out of the database because you asked for it. Nothing in that code path ever compared owner to your session. That gap is the whole vulnerability.
- Get an authenticated session. Register or log in as an ordinary user. You are not attacking the login, you are abusing what a normal account can already reach.
- Find a reference you control. Watch the traffic and note every ID, username, filename, and reference number the browser sends. Each one is a candidate.
- Change it and read the response carefully. A
200with someone else's data is a confirmed IDOR. A403means that endpoint checks. A404often means the check exists and the app is hiding whether the record does, which is good practice. - Prove impact, then stop. One record belonging to a second account you control is enough for the report. Pulling ten thousand records is a data breach with your name on it.
Read, Write, and Blind: The Three Shapes of an IDOR
Most articles stop at "you can read other people's data". Bug hunters ask a second question: can you also write? That is the difference between information disclosure and account takeover.
Read IDOR (disclosure)
A GET request returns an object you do not own. Invoices, medical records, private messages, uploaded files, support tickets. This is the most common form and the easiest to prove. Severity depends entirely on what the object contains: another user's public display name is noise, their scanned passport is a critical.
Write IDOR (tampering and takeover)
A POST, PUT, or PATCH modifies an object you do not own. The classic is a profile update endpoint that accepts a user_id in the body: change it to someone else's, set their email address to yours, then run a password reset. That chain turns a "medium" into full account takeover, which is why you should always test the write path even when the read path is properly locked down.
Blind IDOR (no data comes back)
Some endpoints return an empty 200 or a bare success message no matter whose object you touched. That does not mean it failed. If POST /api/invites/4418/resend answers {"status":"sent"}, check whether an email actually landed in the second account's inbox. In practice this is where beginners give up too early, because the response body looks boring and the side effect happens somewhere they were not watching.
Where IDORs Hide in Modern Applications
Everyone checks the URL bar, which is why URL-bar IDORs are mostly gone from mature applications and the interesting ones live elsewhere. These four spots produce the most findings, roughly in order of yield.
- JSON request bodies of internal APIs. Single-page applications send far more parameters than the URL shows. An endpoint like
PATCH /api/v2/profilecarrying{"user_id":4417,"email":"..."}is the highest-value target on most sites, because the front end always sends the correct ID and nobody tested what happens when it does not. - Export, download, and report endpoints. Frequently bolted on later by a different team, and frequently skipping the authorization middleware the main app runs.
GET /reports/download?ref=Q3-4417.pdfis worth ten minutes of anyone's time. - Custom headers the client controls. Some APIs pass tenancy or identity in headers such as
X-Account-Id. If the server reads that header instead of deriving the account from the session, you have an IDOR that never appears in the URL or the body. - Nested and secondary IDs. A request like
GET /api/teams/12/members/4417often validates that you belong to team 12 and then trusts the member ID completely. Changing the outer ID gets a clean403; changing the inner one gets the record. Test every identifier in a path, not just the last.
GraphQL deserves its own mention. A single node(id: "...") query can expose objects the REST API guards properly, because authorization was written per-endpoint and GraphQL collapsed a hundred endpoints into one. If a target runs GraphQL, start there.
How to Test for IDOR Vulnerabilities
How do you test for an IDOR vulnerability? Create two accounts, capture the requests account A makes to its own resources, then replay each request using A's session cookie and B's identifiers. Any response that returns B's data or changes B's state is an IDOR. Two accounts is the whole methodology, and it is the part scanners cannot do for you.
- Register two accounts and keep them separate. Use two browsers or two containers so the sessions never mix. Create one distinctive object in each (a ticket titled "canary-B", say) so you know instantly whose data you are looking at.
- Proxy everything and build a map. Browse the whole application through an intercepting proxy as account A, then read the history and write down every parameter that looks like an identifier. Our Burp Suite tutorial covers getting the proxy and browser talking if you have not set one up before.
- Swap in Repeater, one request at a time. Replace A's object ID with B's and compare responses. Then repeat the exercise with no session cookie at all, which occasionally turns up unauthenticated access nobody expected.
- Automate the boring part. Burp's free Autorize extension replays every request you make through a second account's session and flags the ones that should have been blocked. It turns an afternoon of manual swapping into a list you review in twenty minutes.
- Check the write and delete paths too. A locked-down
GETtells you nothing about the matchingPUT. Different developers wrote them on different days.
One habit worth building early: log the exact request pairs as you go. A report showing request A (your account, your record, 200) beside request B (your session, their record, 200) gets triaged fast. A report that says "I could see other users' data" gets closed as "need more info".
Fixes That Are Not Fixes
When a team gets an IDOR report, the first instinct is usually to make the identifier harder to guess. That instinct is wrong, and knowing why is what separates someone who understands the bug from someone who memorized it. Unguessable is not the same as unauthorized.
- Switching to UUIDs. Random identifiers raise the cost of enumeration, which is genuinely useful, but they are not a permission check. UUIDs leak constantly: through shared links, referrer headers, CSV exports, support screenshots, and the app's own search results. Once one leaks, the endpoint that never verified ownership still does not verify ownership.
- Encoding the ID. A parameter of
NDQxNw==looks opaque for about four seconds. That is base64 for4417. Hashing is barely better when the hash is unsalted: an ID ofb1d5781111d84f7b3fe45a0852e59758cd7a87e5is just the SHA-1 of a small integer, and anyone can precompute the first million. - Checking on the client. If the browser decides whether to show the edit button, the server still has to decide whether to honor the request. Attackers do not press buttons, they send requests.
- Blocking one HTTP method. Denying
POST /admin/records/4418whilePUTandDELETEsail through is a real pattern in real code, usually caused by a route-level rule that listed methods by hand.
A second category is the filter a small change slips past. id=4418 may be blocked while id[]=4418 arrives as an array and takes a different code path, and sending both id=4417&id=4418 picks different winners in different frameworks. These are not clever attacks. They are evidence the application is validating a shape instead of checking a permission.
What Real IDOR Breaches Have Cost
IDOR gets treated as a beginner bug, which undersells it badly. In July 2023, CISA, the NSA, and the Australian Cyber Security Centre published a joint advisory titled Preventing Web Application Access Control Abuse specifically because of the damage this one class of flaw kept causing. Three of the incidents it documents:
- 2019: over 800 million personal financial files exposed at a US financial services organization.
- 2021: hundreds of thousands of mobile devices exposed through stalkerware applications containing IDOR flaws.
- 2012: personal data of more than 100,000 mobile device owners taken from a communications sector website.
The 2019 case is the one every web tester should know. First American Financial Corporation, a Fortune 500 title insurer, ran a document-sharing system that numbered closing documents sequentially. Anyone holding a link to one could edit the number and read the next, and the exposed set reached back to 2003: Social Security numbers, bank details, driver's license scans. Per the SEC's June 2021 enforcement action, the company's own security team had documented the flaw during a manual penetration test months before it became public and it was never remediated in line with policy. First American paid $487,616 to settle the disclosure-controls charge.
The useful part is that sequence. The bug was found internally, by a person, doing exactly the two-account exercise described above. Then it sat there. The technical flaw was one missing check; the expensive failure was organizational.
The class has not gone anywhere. Broken Access Control is A01 in the OWASP Top 10 2025, built from 40 mapped CWEs, roughly 1.84 million recorded occurrences, and 32,654 associated CVEs. OWASP ranks the API form of it first in the API Security Top 10, rating its prevalence widespread and its exploitability easy.
How to Fix an IDOR Vulnerability
How do you fix an IDOR? Look up the object, then verify that the authenticated session is allowed to have that specific object, before you return anything. The check belongs on the server, in the same place the data is fetched, and it must use the identity from the session rather than any value the client sent.
Concretely, this is the difference between vulnerable and fixed:
# Vulnerable: the ID decides everything
ticket = Ticket.get(request.params["id"])
return ticket.to_json()
# Fixed: ownership is part of the lookup
ticket = Ticket.get(request.params["id"])
if ticket is None or ticket.owner_id != session.user_id:
return http_404()
return ticket.to_json()
Returning 404 rather than 403 on a failed check is a small touch worth adopting. A 403 confirms that record 4418 exists, which hands an attacker a working existence oracle even when the data stays hidden.
- Deny by default. Resources start closed and open only through an explicit rule. A new endpoint someone forgot to protect should fail shut, not fall open.
- Put the check where the data is. A gateway that confirms a valid session is not authorization. If the only check lives at the edge, every internal service call runs unguarded.
- Centralize the policy. One authorization layer that every request passes through beats per-endpoint checks copied by hand, because copies drift and one of them will be wrong.
- Alert on the pattern, not the request. One
403is a typo. Four hundred of them walking consecutive IDs from one session is enumeration in progress, and it should page someone.
Add a regression test while you are in there. A test that logs in as user B and asserts a 404 on user A's object costs about six lines and stops the flaw returning the next time someone refactors the controller.
IDOR vs BOLA vs Broken Access Control
Three terms, one underlying failure, different scopes. Getting them straight makes reports and interviews go smoother.
Broken Access Control is the umbrella category. It covers every authorization failure: reaching an admin page you were never granted, escalating a role, tampering with a request method, and IDOR. Our broken access control guide maps the full set.
IDOR is one specific type inside that umbrella: an object reference the user controls, fetched without an ownership check. Every IDOR is a broken access control flaw. Most broken access control flaws are not IDORs.
BOLA (Broken Object Level Authorization) is what OWASP calls IDOR in its API Security Top 10, where it ranks first. Same bug, API-flavored name. If a triager reclassifies your IDOR report as BOLA, nothing changed except vocabulary.
Legal and Ethical Considerations
Critical reminder: Always get explicit written authorization before testing any application for IDOR. Changing an identifier to read someone else's record on a system you do not own is unauthorized access under the Computer Fraud and Abuse Act (US), the Computer Misuse Act (UK), and equivalent laws worldwide. The fact that the server answered is not permission, and "I only changed a number" has never worked as a defense.
- Test only on systems you own, on purpose-built labs, or inside the written scope of a bug bounty program or engagement.
- Use two accounts you control. Confirming an IDOR against a real stranger's record means you have accessed a real person's data, even by accident.
- Stop at proof of impact. One record, screenshotted, is a report. Iterating through the ID range is a breach, and program rules say so explicitly.
- If a response contains personal data you did not expect, do not save it. Note the request, the status code, and the field names, then say so in the report and give the team time to fix it before writing anything public.
Frequently Asked Questions
What does IDOR stand for?
IDOR stands for Insecure Direct Object Reference. It describes an application that accepts a reference to an object (an ID, filename, or key) from the user and returns the matching object without checking whether that user is authorized to access it. MITRE tracks it as CWE-639, and OWASP's API Security Top 10 calls the same weakness Broken Object Level Authorization.
Is IDOR still common in 2026?
Yes. Broken Access Control, the category IDOR belongs to, is ranked first in the OWASP Top 10 2025, drawn from about 1.84 million recorded occurrences and 32,654 CVEs. Single-page applications and mobile back ends made it more common, not less, because authorization now has to be enforced across hundreds of API endpoints instead of a handful of server-rendered pages.
Can a vulnerability scanner find IDOR?
Mostly not. A scanner can see that a request returned 200, but it has no way to know that the record should have belonged to someone else, because ownership is business logic. Tools that replay your traffic through a second account's session, such as Burp's Autorize extension, get much closer, because you supply the second identity that gives the comparison meaning.
Do UUIDs prevent IDOR vulnerabilities?
No. Random identifiers make enumeration slower, which is worth doing, but they are not an authorization check. UUIDs leak through shared links, referrer headers, exports, and screenshots. If the server still does not verify that the session owns the object, the flaw is intact the moment one identifier escapes.
Is IDOR the same as CSRF?
No. In a CSRF attack, a victim's browser is tricked into sending a request the victim is authorized to make. In an IDOR, you send a request from your own authenticated session that you were never authorized to make. CSRF is defended with anti-forgery tokens and SameSite cookies; IDOR is defended with server-side ownership checks.
How severe is an IDOR vulnerability?
Severity depends on the object and the action. Reading a stranger's display name is low. Reading their identity documents is critical. If the flaw also allows writing, an attacker can often change a victim's email address and trigger a password reset, turning the finding into full account takeover.
Part of the OWASP Top 10 series
Related articles:
- Broken Access Control Explained
- IDOR Vulnerability Guide
- OWASP API Security Top 10
- SQL Injection Tutorial
- SSRF Attack Explained
Your Next Steps
An IDOR vulnerability is the cheapest lesson in application security you will ever get: one changed identifier, one unchecked request, and the difference between authentication and authorization stops being a definition you half-remember. Reading about it does very little. Changing 4417 to 4418 and watching a stranger's record load does all of it at once. Start free, no credit card, in the IDOR Explorer lab, then take the API version in API Breaker, where the identifiers hide in JSON instead of the URL. For how this fits alongside injection, XSS, and the rest of the OWASP Top 10, the Web Attacks course walks each one through guided browser labs. Learn to find the missing check, then go write the one that stops it.