back to blog
GuideAccess Control

Why IDOR Is Still the Highest-Paid Bug in 2026

valkant/May 2026

People who do not test for a living assume insecure direct object references are a solved problem. UUIDs replaced sequential IDs years ago. Authorisation libraries are mature. Frameworks have built-in policy primitives. None of that is wrong, and IDOR is still the single most reliable way to walk into a critical-severity finding on a mature target. The class did not disappear. It moved.

The first place it moved is to endpoints nobody thinks of as user-facing. The main resource routes usually have proper authorisation because the team explicitly thought about them. Export endpoints, audit log endpoints, admin-shaped routes that exist for support staff, and webhook replay endpoints often do not. Those are the routes where the developer reasoned about the happy path and never considered that a regular user might call them directly with a different tenant identifier.

The second place it moved is into GraphQL. A REST endpoint forces the developer to write an explicit handler. A GraphQL resolver often inherits authorisation from a field-level decorator that the team wrote once and then forgot to apply consistently. The result is that the top-level query checks permissions but a nested field on the same response does not. You ask for your own object and request a child relation that belongs to someone else. The response comes back. We have seen this pattern in production at companies that take security very seriously.

The third place it moved is into UUIDs that are not actually random. UUIDv1 is timestamp-based and partially predictable. Some libraries default to it. Other systems generate IDs server-side using a counter encoded in base62 and then call the result a UUID-like token. If the entropy is wrong, you do not need a permission bypass. You can guess the next identifier and ask the API for it. The authorisation is sometimes correct and the predictability of the ID is what defeats it.

The fourth place it moved is into batch and bulk endpoints. A single resource fetch checks permission on the one identifier the user supplied. A batch endpoint takes a list. Some implementations check permission on the first element and assume the rest are fine. Others check permission only on the authenticated user and then iterate through the list with whatever identifiers were provided. Mix in a generous rate limit, and you have an enumeration tool that returns data instead of error codes.

The reason IDOR pays well is that it is almost always tied directly to customer data. There is no argument about exploitability. You hand the triage team a request with a different user's identifier and a response containing that user's record. The proof writes itself. If you are testing a target, the highest-value thing you can do in the first hour is map every endpoint that accepts an identifier, and then try the identifier of an account you do not own on every single one.