Top 5 IDOR Vulnerabilities We Found in 2024
Broken object-level authorization keeps topping our findings — here's how we hunt it, and how to kill it for good.
By ProteQon Red Team
IDOR — insecure direct object reference, or BOLA (broken object-level authorization) in API terms — was the single most common high-severity bug we reported in 2024. It's simple to understand, trivial to exploit, and devastating in impact: one user reading or modifying another user's data. Here are the five patterns we ran into most, how we hunt each one, and how to shut it down.
1. Sequential IDs in REST endpoints
The classic. An endpoint like GET /api/v2/invoices/10432 returns your invoice — and 10431 returns someone else’s. Whenever we see incrementing integers in a URL or request body, we capture a legitimate request, then replay it with a neighbouring ID from a second, low-privilege account.
2. Predictable UUIDs and leaked identifiers
Teams swap integers for UUIDs and assume the problem is solved. It isn't. We routinely find those “unguessable” IDs leaked in earlier API responses, email links, exported CSVs, or referrer headers. Once an ID leaks, a UUID is no safer than 1, 2, 3 if the endpoint never checks ownership.
3. Mass assignment on the owner field
Some endpoints do check ownership — but let you change who the owner is. We send a PATCH that includes a field the UI never exposes, like "user_id": <victim>, and the record silently moves to (or from) another account. Always test for fields the front end doesn’t send.
4. IDOR in file and report downloads
Document download routes are a blind spot because they often sit outside the main API authorization layer. Paths like /files/download?path=... or /reports/2024/Q3/acme.pdf frequently serve any file to any authenticated user. We fuzz the path and watch for other tenants’ documents.
5. Object references hidden in GraphQL and batch endpoints
GraphQL node IDs, bulk “fetch these 50 records” endpoints, and nested relationships are fertile ground. Authorization is often enforced on the top-level query but not on every nested resolver, so requesting a related object by ID slips straight through.
How we hunt it
- Always test with at least two accounts at the same privilege level — IDOR is invisible with a single user.
- Capture every request, then replay it swapping IDs, tokens, and tenant identifiers from the other account.
- Watch responses the UI never shows: exports, webhooks, email links, verbose errors.
- Test write paths, not just reads — PATCH / PUT / DELETE IDOR is usually higher impact.
How to kill it for good
There's no clever client-side trick. Authorization has to be centralized and applied per object, server-side, on every request — ideally through a single policy layer that every endpoint is forced to call. Random IDs raise the bar but are not a control. If you remember one thing: never trust an identifier the client sends without re-checking who is allowed to touch it.