Mastering IDOR: Testing Broken Access Control — ioSENTRIX blog hero with two user records and an open padlock.
TABLE Of CONTENTS

Mastering IDOR: Finding Broken Access Control Behind Predictable IDs and UUIDs

Salman Khan
2026-09-10
9
min read
About these case studies. The techniques below are drawn from real assessment and research work, fully anonymized. Every screenshot is illustrative and mocked — the hostnames (target.com), identifiers, emails, and any record fields shown are AI-generated and fabricated for demonstration; they do not represent any real user, record, or client system. Where a technique depends on framework-specific behavior, that behavior is described from public documentation, not from any customer’s configuration.

INSECURE DIRECT OBJECT REFERENCE (IDOR)

Insecure Direct Object Reference (IDOR) is an access control vulnerability that occurs when an application allows users to access resources directly using identifiers such as user IDs, order IDs, or document IDs without properly verifying ownership. For example, if a user can access their profile using user_id=212, an attacker may modify the value to user_id=213 and gain unauthorized access to another user's information if proper authorization checks are missing.

TYPES OF IDENTIFIERS USED IN APPLICATIONS

When testing for IDOR vulnerabilities, it is important to understand the type of identifiers used by the application. Some applications use simple, predictable identifiers such as sequential numeric IDs (121, 122, 123), which are easy to enumerate by modifying the parameter value and observing the response. In such cases, discovering unauthorized resources is often straightforward. However, many modern applications use complex, unpredictable identifiers such as UUIDs, random strings, or tokens (for example, 1sdse32ew3wedsws or 550e8400-e29b-41d4-a716-446655440000). These identifiers are difficult to guess through brute force or manual testing. While such identifiers reduce the risk of enumeration, they do not prevent IDOR vulnerabilities. If an attacker can obtain another valid identifier through application functionality, API responses, shared resources, logs, exports, or other information disclosure issues, they may still be able to access unauthorized data if proper authorization checks are not enforced. Therefore, the security of an application should never rely solely on making identifiers difficult to guess. Every request must be validated on the server side to ensure that the authenticated user is authorized to access the requested resource.

Predictable sequential IDs versus unpredictable UUIDs and tokens used to reference application objects.

1. IDOR WITH PREDICTABLE IDENTIFIERS

One of the most common forms of IDOR occurs when an application uses predictable identifiers to reference resources. These identifiers are typically sequential numbers such as 101, 102, 103, or similar values that can be easily be guessed by an attacker. If the application fails to perform proper server-side authorization checks, an attacker can simply modify these identifiers to access resources belonging to other users. Depending on the affected functionality, this may result in unauthorized access to sensitive information, modification of another user's data, or deletion of protected resources.

For example:

GET /profile?user_id=212        →  your profile
GET /profile?user_id=213        →  someone else’s profile   ← IDOR

TESTING METHODOLOGY

Although IDOR vulnerabilities involving predictable identifiers are relatively simple to discover, they are frequently overlooked because researchers fail to test every functionality within the application. A thorough assessment requires reviewing all areas of the application and analyzing every request that interacts with user-controlled data. Researchers should inspect requests sent through URLs, API endpoints, POST bodies, JSON parameters, cookies, hidden fields, and HTTP headers, as object identifiers can appear anywhere within the request. Always perform testing with at least two different user accounts. Capture requests using Burp Suite Proxy, replay them using Repeater, replace the object identifiers with those belonging to another account, and verify whether the application enforces authorization. Using multiple accounts helps eliminate false positives and confirms whether the application properly restricts access between users.

IDOR testing methodology for predictable identifiers: two accounts, intercept, swap the ID, observe, confirm.

Case study #1 — A serialized payload hiding the real identifier

During the reconnaissance and application mapping phase, I noticed that the application relied heavily on a single backend endpoint (/_serverFn/) to perform almost every operation. Modern frameworks commonly serialize request data into a single payload that is processed by the backend. The request headers gave the framework away immediately (x-tsr-serverfn: true, x-tss-serialized: true, and Accept: application/x-tss-frame. This is TanStack Start, whose server functions serialize their arguments into a single structured payload (via the seroval serializer) rather than exposing conventional named parameters.

GET /_serverFn/...?...payload=%7b...%7d HTTP/1.1

Host: target.com

At first glance, the payload appeared encoded and difficult to understand. After decoding it, the underlying structure became visible. The payload was a nested object where each node is tagged by type and index rather than by readable field names:

{

   "t": {

           "t": 10,

           "i": 0,

           "p": {

                  "k": ["data"],

                  "v": [{

                            "t": 10,

                             "i": 1,

                            "p": {

                                    "k": ["portalUserId"],

                                    "v": [{

                                            "t": 0,

                                             "s": 11

                                            }]

                                     }

                             }]

                     }

            }

}


The request structure was framework-specific, so I analyzed the serialized format to understand how values were passed to the backend. During this analysis, I identified the portalUserId field and determined that the value "s":11 represented the authenticated user's identifier.

After modifying this value and replaying the request, the server returned another user's portal information. Further testing showed that numerous backend functions accepted user-controlled identifiers without verifying ownership. By following the same methodology across different functionalities, multiple identifiers were found to be vulnerable, resulting in a critical site-wide IDOR affecting numerous user resources.

A TanStack Start server-function request whose seroval-serialized payload hides a user-controlled portalUserId (illustrative, mocked data).

Takeaway: Never skip encoded or serialized requests. Modern frameworks TanStack Start, Next.js server actions, tRPC, GraphQL  routinely pack identifiers into a single opaque-looking payload. Decode it, find the object reference, and test it exactly like a URL parameter, because to the authorization layer it is one.

Case study #2 — Predictable order IDs behind a “send my invoice” feature

While testing an e-commerce application, I noticed a feature that allowed users to send purchase invoices to their registered email address. The intercepted request contained an order identifier similar to: orderId=COMP-21234

Initially, I modified the identifier manually, but random values did not return any results. I moved on to testing other areas of the application. Several weeks later, while reviewing my notes, I revisited the same functionality. I realized that although the identifier contained a prefix, only the numeric portion changed between orders. The request was sent to Burp Intruder, where the numeric sequence was enumerated. After several minutes, invoices belonging to other customers began arriving in my email. The application generated invoices based solely on the supplied order identifier and failed to verify whether the requested invoice belonged to the authenticated user.This vulnerability resulted in unauthorized disclosure of customer invoices and was rewarded with a bug bounty.

Enumerating the numeric portion of a predictable order ID in Burp Intruder to retrieve other users' invoices (illustrative, mocked data).

Takeaway: Sometimes the vulnerability is not obvious during the first assessment. Revisiting previously tested functionality often reveals attack paths that were initially overlooked.

2. IDOR WITH UNPREDICTABLE IDENTIFIERS (UUIDS AND TOKENS)

Unlike predictable numeric identifiers, many modern applications use UUIDs, random strings, hashes, or tokens to reference resources. At first glance, these identifiers appear secure because brute-forcing them would require an enormous amount of time and computational effort. However, Unpredictable identifiers do not eliminate IDOR. They change the attacker’s job from guessing the identifier to discovering it. The entire game becomes: where does the application hand out a valid identifier belonging to someone else?

Instead of userId=212 an application may use: userId=8f4c9a7d-4e7b-4c13-9f1d-5f7a0f3d6b12 or resourceId=1sdse32ew3wedsws

Unpredictable identifiers such as UUIDs and tokens hide resources but do not authorize requests.

A. SINGLE-ROLE APPLICATIONS

When every user has the same role and the same functionality, there is no higher-privileged view leaking lower-privileged objects, making it harder to obtain identifiers belonging to other users. In these situations, researchers should look for identifiers exposed through other sources, including:

  • Historical and archived URLs
  • Publicly accessible or shared resources and links
  • Search-engine caches
  • API responses and exported files
  • Email notifications
  • Client-side JavaScript and source maps

One effective technique is reviewing archived URLs: waybackurls target.com

Historical records sometimes expose resource identifiers that remain valid even after the URLs are removed from the application.

CASE #1 – OBTAINING UUIDS FROM HISTORICAL RECORDS

During one assessment, all resource identifiers consisted of long random strings that were practically impossible to guess. Instead of attempting brute force, historical URLs and archived API responses were reviewed. Several archived URLs contained valid resource identifiers. Supplying those identifiers to the application's API allowed access to resources belonging to other users because authorization checks were missing. This demonstrates that UUIDs do not prevent IDOR. They simply change the attack from guessing identifiers to discovering them elsewhere.

waybackurls output exposing historical URLs that still contain valid resource identifiers (illustrative, mocked data).

B. MULTIPLE-ROLE APPLICATIONS

Applications supporting multiple roles such as Administrator, Manager, Employee, Merchant, Vendor, Customer Support, or Organization Owner often provide the best opportunities for discovering IDOR vulnerabilities involving UUIDs. Different roles naturally exchange information with one another. During these interactions, identifiers that cannot be guessed are frequently disclosed through legitimate application workflows. Common places where identifiers are exposed include:

• User management

• Organization members

• Audit logs

• Reports

• Notifications

• Approval workflows

• Administrative dashboards

• Shared API responses

Testing should always involve multiple accounts with different privilege levels while observing how identifiers move between roles.

CASE #1 – OBTAINING A COMPLEX USER IDENTIFIER THROUGH ANOTHER ENDPOINT

During an assessment, the user verification functionality accepted a parameter named selfUserId, which contained a 24-character random identifier. Initially, the identifier appeared secure because brute-forcing it was unrealistic. However, while mapping the application, another endpoint used to retrieve public project details exposed the project owner's selfUserId. After obtaining another user's identifier, the verification request was replayed with the modified value.

Because the application trusted the client-supplied identifier and failed to verify ownership, another user's verification details including name, phone number, address, country, and uploaded identification documents could be modified.

A public endpoint leaking another user's selfUserId, replayed against a verification endpoint that never checks ownership (illustrative, mocked data).

Key Takeaway: Complex identifiers should never be treated as authorization. If another endpoint exposes them, the application remains vulnerable.

CASE #2 – PRIVILEGE ESCALATION via a leaked admin identifier (BFLA)

During another assessment, two accounts were available:

• Administrator

• View-Only User

The administrator account could delete users using a request that accepted a complex user identifier.Although the identifier appeared impossible to guess, further testing revealed that the View-Only profile response exposed the administrator's internal identifier through the createdBy field. After replacing the identifier in the delete request with the exposed administrator identifier, the request was replayed using the View-Only account. Because the server failed to validate permissions, the low-privileged account successfully performed an administrator-only action. This demonstrates a common attack chain in modern applications: one endpoint leaks a privileged identifier, while another endpoint trusts that identifier without enforcing authorization.

A view-only user performing an admin-only delete by substituting a leaked administrator UUID: BOLA chained with BFLA (illustrative, mocked data).

Key Takeaway: UUIDs hide resources; they never replace authorization. When you find a leaked privileged identifier, check whether the sensitive function is also reachable by the wrong role. The chain is usually worse than either link alone.

What a real fix looks like

Every case above has the same root cause and, therefore, the same class of fix: authorize on the server, on every request, against the authenticated session  never against a value the client supplied. That is the control we test for, and it is worth being specific about what “good” means, because a paper policy that says “we check authorization” is not the same as a control that holds under a replayed request.

  • Enforce object-level ownership server-side, per request. Derive the acting user from the session or validated token, then confirm that user is authorized for the specific object and the specific operation before doing any work. Never infer authorization from the mere presence of an identifier in the request.
  • Default to deny. New endpoints and new object types should require an explicit access decision, not inherit implicit access. Most IDORs we find are on endpoints nobody remembered to gate.
  • Don’t rely on unpredictability as a control. Unguessable IDs (and avoiding sequential public keys) are good defense-in-depth against enumeration, per the OWASP IDOR Prevention Cheat Sheet  but they are a supplement to authorization, never a substitute.
  • Add rate limiting and monitoring on object-referencing endpoints so that even a partial gap can’t be harvested at scale, and so enumeration attempts are visible.
  • Test it, don’t assert it. Regression-test authorization with a second, lower-privileged account in CI, and validate the control adversarially, the way an attacker actually exercises it not by reading the code and trusting it.

That last point is the ioSENTRIX position, and it is the whole reason IDOR keeps reaching production: a control that exists in the codebase is not the same as a control that holds when a real request tries to bypass it. The only way to know which one you have is to prove it.

Frequently asked questions

What is an IDOR vulnerability?
Insecure Direct Object Reference is an access-control flaw where an application exposes a reference to an internal object — a user ID, order ID, or document ID — and fails to verify that the requesting user is authorized to access that object. Changing the identifier grants access to another user’s resource. It is a form of Broken Access Control (OWASP A01) and, at the API layer, of Broken Object Level Authorization (BOLA, API1:2023).

Do UUIDs prevent IDOR?
No. UUIDs make a resource harder to discover by brute force, but they do not authorize requests. If an attacker obtains a valid UUID from an API response, a shared link, an archived URL, or a leak from another endpoint, the resource is exposed unless the server independently verifies ownership on every request.

How do you test for IDOR?
Map every place the application references an object — URLs, path segments, POST bodies, JSON and GraphQL fields, cookies, headers, and serialized payloads. Using at least two accounts at different privilege levels, capture requests from one account and replay them with the other account’s identifiers, checking whether the server enforces ownership. Burp Suite’s Repeater, Intruder, and the Autorize extension are the core toolset.

What is the difference between BOLA and BFLA?
BOLA (Broken Object Level Authorization) is accessing an object you shouldn’t — another user’s record via its identifier. BFLA (Broken Function Level Authorization) is invoking a function you shouldn’t — a low-privilege user calling an admin-only operation. They frequently chain: one endpoint leaks a privileged identifier (BOLA) and another lets the wrong role use it (BFLA).

What is the correct fix for IDOR?
Enforce authorization on the server for every request, derived from the authenticated session rather than from client-supplied values, checking both the specific object and the specific operation. Default to deny, add rate limiting and monitoring, and treat unguessable identifiers as defense-in-depth against enumeration — not as an access-control boundary.

ioSENTRIX Can Help

ioSENTRIX is a CREST-accredited, ISO/IEC 27001 certified penetration testing firm. Broken access control is the most common serious finding we report, and IDOR is its most persistent form precisely because it hides behind identifiers that look safe. Our web and API penetration testing exercises every object reference and every privileged function with the same discipline shown above — multiple accounts, adversarial replay, and evidence — so you learn whether your authorization actually holds, not just whether the code says it should. If you’re shipping APIs, multi-tenant features, or role-based access, we can help you prove those controls work before an attacker tests them for you.

Keep reading

  • API Security Testing: Beyond the Scanner
  • Broken Access Control: The Bug Scanners Miss
  • Why Authorization Belongs in Your CI Pipeline
  • Threat Modeling Multi-Tenant Applications
  • Prove, Don’t Assert: What Adversarial Testing Actually Buys You
#
OWASPTop10
#
Penetration Testing
Contact us

Similar Blogs

View All