November 3, 2026
The Three-Request IDOR Method: How I Find Broken Access Control Before Breakfast
Two accounts, three requests and one matrix โ the routine that still pays my bounties in 2026

By The XSS Rat
7 min read
The bug I still find before my coffee is cold
I have been hunting bugs and teaching people to hunt them for the better part of a decade, and the vulnerability that pays most reliably has not changed: broken access control. Not a clever race condition, not a novel deserialisation chain. Somebody wrote GET /api/invoices/8812 and never checked that you were allowed to see invoice 8812.
OWASP agrees. Broken Access Control moved to the number one spot in the OWASP Top 10 in 2021 and it kept that spot in the 2025 edition. IDOR โ Insecure Direct Object Reference โ is the most common shape it takes: an object reference you control (an ID, a filename, a username) and a server that trusts it.
Here is the thing nobody tells beginners: you do not find IDORs by being smart. You find them by being systematic. I run the same three requests against every endpoint that touches an object, every single time, and I log the result in a matrix. It is boring. It is also how I have found more bounties than any other single technique, and it is the first method I teach every student in my coaching programme.
This article is that routine, written down. It is the same routine I had my pentest students run against my own lab last month, and it is the routine the CMWAP certification exam quietly expects you to know. If you only take one habit from The XSS Rat, take this one.
The three-request method
You need two accounts and one endpoint. That is the whole setup. Register Account A and Account B on the target. Log A's object reference (its invoice ID, its user ID, whatever the endpoint hands back) and keep B's session token ready. Then run three requests against the same endpoint and compare the answers.
The three requests and what each outcome tells you.
- Request 1 โ the baseline. As Account A, request A's own object:
GET /api/invoices/8812with A's token. This should succeed. You are recording what a legitimate, authorised response looks like โ status code, body shape, size. Everything else is measured against this. - Request 2 โ the cross-account test. As Account B, request A's object:
GET /api/invoices/8812with B's token. B has no business seeing A's invoice. If the server returns A's data, that is a textbook IDOR. If it returns 403 or 404, the access control held โ for now. - Request 3 โ the no-auth test. Request A's object with no token at all. A surprising number of APIs authenticate the route but not the object, or leak data to unauthenticated callers on a parallel endpoint. This request catches the missing-auth cases the two-account test misses.
The discipline is in the comparison, not the cleverness. You are asking one question three ways: does the server check who is asking before it hands over this object? Most of the time it does. When it does not, the three-request pattern shows you exactly which layer failed โ and that detail is what makes your report land as a valid finding instead of a shrug.
Where the object reference actually hides
Beginners look for IDs in the URL path and stop there. The reference you can tamper with lives in a lot more places, and the easy-mode bugs moved into the harder-to-spot spots years ago. Check the path, the query string, the request body, custom headers, JWT and token claims, and GraphQL node IDs on every endpoint.
One request, six places a tamperable object reference can live.
Two habits pay off here. First, decode everything that looks opaque: base64, hex, a hash that is really a sequential ID with lipstick on it. GraphQL global IDs are the classic โ they decode to Type:integer and the integer is guessable. Second, watch the exports and bulk endpoints. GET /api/report?account=8812&format=pdf is often wired straight to a rendering service that never re-checks ownership, because 'it is just a report'. Reports are where the juicy multi-record IDORs live.
The access-control matrix (the QA habit that makes this repeatable)
Here is where my day job leaks into my hunting. I spend my working hours in QA and test automation, and the single most useful artefact in testing is a coverage matrix: rows are things you can do, columns are states you can be in, cells are the expected result. Access control is exactly that shape.
Build one matrix per sensitive object. Roles across the top, actions down the side, and in every cell the result you expect versus the result you got. The moment 'got' diverges from 'expect', you have a finding โ and you have it written down with the exact request that produced it.
Expected vs. got for one object, across every role. Fill the 'got' value as you test.
A cell where B gets 200 on Read is a horizontal privilege escalation. A cell where no-auth gets 200 anywhere is worse. A cell where B can Delete is often a higher severity than Read, because destruction is harder to argue down in triage. The matrix does three things at once: it stops you testing the same thing twice, it shows you the gaps you have not covered yet, and it becomes the evidence table you paste straight into the report. This is the method I drill in coaching, and it is the exact mindset CMWAP grades โ scope adherence and systematic coverage over raw exploit count.
From a leaked field to a critical: the escalation ladder
A single read-only IDOR on a low-value object is a medium at best. The money is in chaining. Before you write the report, walk the object up the ladder and ask how far the same broken check reaches.
One broken object check, walked from a minor read up to account takeover.
- Read one field. You can see another user's email on their invoice. Low impact on its own.
- Read the whole object. The same endpoint returns the full record โ address, phone, tax ID. Now it is a privacy finding with real PII.
- Enumerate. The ID is sequential, so you can walk 8812 โ 8813 โ 8814 and pull every record. A single IDOR just became a full-database read.
- Write, not just read. The Update cell in your matrix was also broken. You can change another user's object, not only view it.
- Escalate the role. The broken check also covers a
roleoris_adminfield you can PUT. Horizontal just became vertical. - Take over the account. You can change another user's email or reset token through the same unguarded endpoint. That is account takeover โ critical.
You do not need to reach rung six for a good report. But you must try each rung, because the severity โ and the bounty โ is set by the highest rung you can prove. Triagers pay for demonstrated impact, not theoretical impact. If you can show the enumeration and one write, show it.
Write the report so triage rates it right
A real finding gets downgraded or closed every day because the report was lazy. The matrix already did most of the work; now present it so a tired triager on the other side can reproduce it in under two minutes.
- Lead with impact, in one sentence. "An authenticated user can read, modify and delete any other user's invoices by changing a sequential ID." Not "I found an IDOR."
- Give the two accounts explicitly. Spell out Account A and Account B, which token is which, and which object belongs to whom. The triager must not have to guess your setup.
- Paste the three requests verbatim. Baseline, cross-account, no-auth โ full request and the response status plus a snippet of the leaked body. The contrast between request 1 and request 2 is the proof.
- Show the enumeration if you have it. Three sequential IDs returning three different users' data kills any "works as intended" rebuttal.
- State severity with a CVSS vector and your reasoning. Do not just claim "high"; show the vector and name the rung you reached on the ladder.
- Keep it to what you proved. Resist padding. Demonstrated impact only โ a clean, reproducible medium beats a hand-wavy "critical" every time.
The golden rule: a report is a reproduction script with a severity attached. If the triager can copy your two requests and watch the server hand over data it should not, you have already won the argument.
Practice this on something you are allowed to break
Reading about IDOR teaches you nothing. Running the three requests until they are muscle memory teaches you everything. Do not practice on live targets you have no permission for โ that is the fast route to a legal problem, not a bounty.
I built a lab for exactly this. RatPack Park is a deliberately vulnerable multi-tenant app โ multiple accounts, multiple tenants, and a pile of broken access-control checks waiting for you to run the matrix against them. Spin up two accounts, build the matrix from this article, and see how many rungs of the ladder you can climb.
When you want the method graded properly, that is what CMWAP โ the Certified Modern Web App Pentester is for. It is a practical, scope-first certification: the exam rewards systematic coverage and a clean report over raw exploit count, which is exactly the discipline this article is about. It is built for beginners too, and it comes with three months of RatCTF to keep your hands warm.
About the author. I am Wesley Thijs โ The XSS Rat. I have taught over 200,000 students, I run a bug bounty coaching programme and the Rat Pack community, and I still hunt. If this method found you a bug, tell me about it โ I read every reply. Follow along on YouTube and X for more, and come say hi to the Rat Pack.
Hunt like a rat: patient, systematic, and always checking the one door everybody assumes is locked.