Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← Back to Blog
API Security 2026-07-06 ⏱️ 13 min

Deep Dive into IDOR Vulnerabilities in REST APIs

Insecure Direct Object Reference (IDOR) consistently ranks among the most exploited vulnerabilities in modern web applications and APIs. Despite being conceptually simple — the server trusts a user-supplied identifier without verifying ownership — the real-world impact is severe: unauthorized access to personal records, financial data, medical files, and administrative functions. In the 2023 OWASP API Security Top 10, IDOR is classified as API1:2023 Broken Object Level Authorization (BOLA), the single highest-priority API risk. This article dissects every facet of IDOR in REST APIs, walks through attack scenarios with real HTTP requests, compares vulnerable and secure code, and outlines a defense-in-depth strategy.


1. What Is IDOR?

An IDOR vulnerability exists whenever an application uses a client-supplied value — an integer ID, a UUID, a filename, a hash — to retrieve or manipulate a server-side resource, and fails to verify that the authenticated principal is authorized to access that specific resource. The flaw is purely an authorization deficiency, not an authentication one. The user is legitimately logged in; the server simply never checks ownership.

The canonical example: a user logs in as [email protected], navigates to her profile at /api/users/101/profile, and notices the number 101 in the URL. She changes it to 102. If the server returns user 102's profile instead of a 403, you have an IDOR.

OWASP defines BOLA/IDOR as: "The API exposes a direct reference to an internal object (database record, file, etc.) without performing sufficient authorization checks." The attack surface is enormous — every endpoint that accepts an object identifier is a candidate.


2. Why REST APIs Are Particularly Vulnerable

RESTful design intentionally exposes resource identifiers in URLs. A well-structured REST API looks like:

GET  /api/users/{userId}
GET  /api/orders/{orderId}
GET  /api/invoices/{invoiceId}
PUT  /api/documents/{docId}
DELETE /api/messages/{messageId}

These predictable, resource-centric URLs are a core REST principle — and they directly enable IDOR testing. An attacker who has one valid ID needs only to enumerate adjacent values. Sequential integer IDs (1000, 1001, 1002…) are trivially iterable. Even UUID-based systems are vulnerable when IDs can be obtained through other API responses (e.g., a search endpoint leaking other users' IDs in its results).

Additionally, REST APIs are often consumed by mobile apps, third-party integrations, and JavaScript SPAs — all of which expose request structures in client-side code or interceptable network traffic, making parameter discovery straightforward with tools like Burp Suite.


3. Types of IDOR

3.1 Numeric ID IDOR

The most obvious form. Sequential integers make brute-force enumeration trivial.

# Victim's own invoice
GET /api/invoices/1001 HTTP/1.1
Authorization: Bearer <valid_token>

# IDOR: accessing another user's invoice by decrementing
GET /api/invoices/1000 HTTP/1.1
Authorization: Bearer <valid_token>

An attacker with a valid account can iterate from ID 1 to the maximum, harvesting every invoice in the system.

3.2 UUID IDOR

Many developers switch to UUIDs believing randomness provides security through obscurity. UUIDs are not secrets — they appear in email links, support tickets, API responses, and browser history. If any endpoint leaks them (e.g., a transaction list that includes recipient account UUIDs), sequential enumeration becomes unnecessary.

# UUID leaked from GET /api/transactions (other user's accountId)
GET /api/accounts/f47ac10b-58cc-4372-a567-0e02b2c3d479/statement HTTP/1.1
Authorization: Bearer <victim_token>

3.3 Filename IDOR

File download endpoints are a frequent IDOR location. If files are stored with predictable or guessable names:

GET /api/files/2026/alice_tax_return.pdf HTTP/1.1
Authorization: Bearer <valid_token>

# Attempt:
GET /api/files/2026/bob_tax_return.pdf HTTP/1.1
Authorization: Bearer <valid_token>

Even hashed filenames (MD5, SHA1) can be precomputed if the naming convention is known (e.g., MD5(userId + year + "report")).

3.4 Reference in Request Body

IDOR is not limited to URL parameters. Object references embedded in request bodies are equally exploitable.

DELETE /api/messages HTTP/1.1
Authorization: Bearer <valid_token>
Content-Type: application/json

{"messageId": 42}

If the server validates the auth token but not whether messageId 42 belongs to the authenticated user, an attacker can delete any message by simply changing the ID.


4. Proof-of-Concept HTTP Requests

The following demonstrates a classic horizontal privilege escalation via IDOR. User A (id=123) is legitimately authenticated with a valid JWT:

# Step 1 — Legitimate request: User A reads their own profile
GET /api/v1/users/123/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoidXNlciJ9.abc123
Accept: application/json

HTTP/1.1 200 OK
{"id":123,"name":"Alice","email":"[email protected]","plan":"premium"}

# Step 2 — IDOR: User A reads User B's profile (id=124)
GET /api/v1/users/124/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoidXNlciJ9.abc123
Accept: application/json

HTTP/1.1 200 OK                         <-- Should be 403
{"id":124,"name":"Bob","email":"[email protected]","plan":"enterprise","creditCard":"****1234"}

User A's token contains sub: 123, but the server never validates that the requested profile ID matches the token subject. This is the root cause of every IDOR — decoupled authentication and authorization.


5. IDOR in Indirect and Advanced References

5.1 Parameter Pollution

Some servers parse duplicate query parameters in unexpected ways. An attacker might attempt:

GET /api/cart?userId=123&userId=456 HTTP/1.1

Depending on the backend framework, the last, first, or both values may be processed. If a middleware extracts the first parameter for rate-limiting but the handler uses the last for data retrieval, an IDOR emerges.

5.2 JSON Path Confusion

APIs that merge request body data with user context are susceptible to mass assignment variants:

POST /api/profile/update HTTP/1.1
Content-Type: application/json

{
  "user": {
    "id": 123,
    "displayName": "Alice",
    "admin_override_id": 1
  }
}

If the server naively binds the request body to a model, the admin_override_id or even a plain id override can redirect the write operation to a different object.

5.3 GraphQL IDOR

GraphQL's flexible query language introduces IDOR through node ID injection. Since every resource in a GraphQL schema is typically addressable by a global ID:

query {
  node(id: "VXNlcjoxMjQ=") {   # base64 decode: "User:124"
    ... on User {
      email
      creditCard { number }
    }
  }
}

If the server resolves the node without checking that the requesting user owns User:124, full object data is exposed. GraphQL also allows batching queries, enabling high-speed enumeration of IDs within a single request.


6. Real-World IDOR Patterns from Bug Bounty

IDOR bugs dominate HackerOne and Bugcrowd payouts. The most common patterns include:

  • Order and invoice APIs: E-commerce platforms frequently expose /orders/{id} endpoints without ownership checks. Attackers harvest purchase histories, shipping addresses, and payment method metadata.
  • Healthcare record endpoints: Patient portals querying /patients/{id}/records without verifying the requesting physician or patient's relationship to the record — a HIPAA violation with significant legal consequence.
  • Document download endpoints: SaaS platforms serving /documents/{docId}/download without checking the document's owning organization. One well-known bug bounty report described accessing competitor companies' confidential agreements through sequential document IDs.
  • Support ticket systems: Platforms using predictable ticket IDs in URLs, allowing authenticated users of one company to read another company's support conversations.

A particularly impactful pattern is IDOR in administrative endpoints — for example, GET /api/admin/users/{userId}/audit-log accessible to regular authenticated users, revealing all audit activity for any user account in the system.


7. Vulnerable Code vs. Secure Code

The following Node.js/Express examples illustrate the exact difference between a vulnerable and a correctly defended endpoint.

Vulnerable Implementation

// VULNERABLE — No ownership check
// Any authenticated user can read any order by changing the orderId
app.get('/api/orders/:orderId', authMiddleware, async (req, res) => {
  try {
    const order = await Order.findById(req.params.orderId);
    if (!order) return res.status(404).json({ error: 'Order not found' });
    res.json(order); // returned regardless of who owns it
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

The authMiddleware verifies the JWT is valid and populates req.user, but the handler never checks whether order.userId matches req.user.id. Any authenticated user can retrieve any order.

Secure Implementation

// SECURE — Ownership enforced at the database query level
app.get('/api/orders/:orderId', authMiddleware, async (req, res) => {
  try {
    const order = await Order.findOne({
      _id: req.params.orderId,
      userId: req.user.id  // MUST match the authenticated user's ID
    });
    if (!order) return res.status(403).json({ error: 'Access denied' });
    res.json(order);
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

The critical change is in the query: findOne({ _id: ..., userId: req.user.id }). If the orderId doesn't belong to the authenticated user, the query returns null and the endpoint responds with 403. Importantly, the error message is neutral — it doesn't distinguish "not found" from "exists but forbidden," preventing enumeration of valid IDs.

Secure Django/Python Equivalent

# SECURE Django REST Framework view
class OrderDetailView(RetrieveAPIView):
    serializer_class = OrderSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        # Scope the queryset to the authenticated user's orders only
        return Order.objects.filter(user=self.request.user)

    # DRF's get_object() calls get_queryset() then applies lookup_field
    # If the orderId isn't in the user's scope, a 404 is returned

8. Defense-in-Depth Strategy

8.1 Object-Level Authorization on Every Endpoint

Every API handler that accepts an object identifier must perform an ownership verification query before returning data or executing mutations. This check belongs at the data access layer — not as a post-retrieval comparison — so that forbidden records are never loaded into memory.

8.2 Non-Sequential Identifiers

Use UUID v4 (cryptographically random) instead of sequential integers for resource IDs. This eliminates trivial increment-based enumeration. However, UUIDs alone are not a security control — they simply raise the bar for unaided guessing. Object-level authorization checks remain mandatory.

8.3 Indirect Reference Maps

For highly sensitive resources, implement indirect reference maps: the server maintains a session-scoped mapping between opaque tokens and real internal IDs. A user never sees or manipulates internal database IDs; they work exclusively with per-session tokens that expire. This completely eliminates direct reference attacks.

8.4 Principle of Least Privilege in API Scopes

Define granular OAuth 2.0 scopes that encode both the resource type and ownership relationship. A token scoped to orders:read:own can only authorize retrieval of the token owner's orders — the scope itself enforces the authorization boundary.

8.5 Automated Testing in CI/CD

IDOR testing must be automated and integrated into the development pipeline. Practical tooling includes:

  • Burp Suite with the Autorize extension: Records responses as a privileged user, then replays every request with a lower-privileged user's token and compares responses. Significant response similarity flags IDOR candidates.
  • OWASP ZAP: Open-source alternative with authorization testing plugins.
  • Custom integration tests: Write tests that create two user accounts, create resources with user A, and verify that user B cannot access them. These tests should live in the CI/CD pipeline and block deployment on failure.

8.6 Logging and Monitoring

Log every 403 response with the authenticated user's ID, the requested resource identifier, and the resource's actual owner. A spike in 403s from a single user ID is a strong signal of active IDOR probing. Correlate these logs in your SIEM with alerts at configurable thresholds.


Conclusion

IDOR in REST APIs is not a complex cryptographic vulnerability — it is a process failure where authorization checks that should exist simply don't. The remedy is equally straightforward: every data access operation must validate that the authenticated identity owns or is authorized to access the requested object. This check must be enforced at the server, never trusted to the client, and must be present on every endpoint — IDOR vulnerabilities discovered in production are almost always on endpoints that developers assumed were "internal" or "low-risk."

Build IDOR prevention into your API framework as a default behavior, automate ownership-scoped queries at the ORM level, and test authorization independently of authentication on every endpoint in your test suite. The cost of getting this right at design time is a few lines of query logic. The cost of getting it wrong is a data breach affecting every user in your database.

Is Your API Exposed to IDOR?

Our API security specialists perform deep authorization testing across every endpoint — identifying IDOR, BOLA, and broken access control before attackers do.

✉️ Request an API Security Audit

Related Articles