Axe:ploitAxe:ploit
Broken Access Control & IDOR: The API Vulnerability Hiding in Plain Sight

Broken Access Control & IDOR: The API Vulnerability Hiding in Plain Sight

Jason

Jason

@Jason

In modern API ecosystems, Insecure Direct Object Reference (IDOR) remains one of the most consistently exploited patterns of Broken Access Control. While seemingly simple, it poses a significant threat—especially in stateless REST APIs where object references (like user_id, invoice_id, or file_id) are often exposed as part of the resource path or payload.

Despite widespread awareness, IDOR continues to slip past QA, static analysis, and even DAST tools because it's not a bug in code—it's a failure in authorization logic.


Understanding IDOR: A Subtle Flaw in Trust Assumptions

An IDOR vulnerability occurs when an application allows users to access objects—such as database records or files—based solely on user-supplied identifiers, without verifying whether the requesting user actually has permission to access them.

While authentication confirms who a user is, IDOR bypasses the critical question: are they allowed to access this object?


Real-World Attack Vector: IDOR in a Banking API

Let’s break down a real attack scenario involving a vulnerable financial endpoint:

GET /api/accounts/{account_id}/transactions
Authorization: Bearer <user_token>

At first glance, this endpoint appears secure. However, if the backend does not explicitly validate whether the token holder owns the account referenced by account_id, the system becomes trivially exploitable.

Exploit Flow

sequenceDiagram participant Attacker participant API participant DB Note over Attacker,DB: Attacker (User ID: 1002) owns account 5002 Note over API,DB: No access control enforcement for account ownership Attacker->>API: GET /api/accounts/5001/transactions Attacker->>API: Authorization: Bearer token_1002 API->>DB: SELECT * FROM transactions WHERE account_id = 5001 DB->>API: Return data for account 5001 API->>Attacker: [Transaction list for account 5001]

The attacker enumerates account IDs, accessing data they do not own. This is a textbook IDOR exploit: no privilege escalation required, just insufficient authorization enforcement.


How This Should Work: Enforcing Ownership

sequenceDiagram participant Attacker participant API participant DB Attacker->>API: GET /api/accounts/5001/transactions Attacker->>API: Authorization: Bearer token_1002 API->>DB: SELECT * FROM accounts WHERE id = 5001 AND user_id = 1002 DB->>API: No results (access denied) API->>Attacker: 403 Forbidden

Access is denied because ownership validation is correctly enforced at the query or service layer—not just assumed from a valid token.


API Misuse Scenario: User Profile Enumeration

GET /api/user/1234
Authorization: Bearer <valid_token>

This endpoint appears standard—but if there's no server-side check confirming the requester is user 1234, then any authenticated user could access any other profile just by manipulating the ID.

Exploit:

curl -H "Authorization: Bearer attacker_token" \
     https://example.com/api/user/1235

Unless backend logic enforces:

SELECT * FROM users WHERE id = 1235 AND id = <token_owner_id>

...this endpoint leaks sensitive profile data.


Another Example: Downloading Invoices Without Authorization Checks

# Legitimate request
curl -H "Authorization: Bearer token123" \
     https://example.com/api/invoice/6789/download
 
# Malicious attempt
curl -H "Authorization: Bearer token123" \
     https://example.com/api/invoice/6790/download

If invoice 6790 belongs to another customer and no check is in place to validate ownership, the attacker can exfiltrate private financial documents.


Where IDOR Commonly Occurs

IDOR vulnerabilities often emerge in:

  • Path parameters: /api/user/{id}, /file/{doc_id}/download
  • Query strings: ?account_id=5543
  • POST/PUT bodies: { "transaction_id": "xyz" }
  • Hidden form fields and cookies

The underlying issue is always the same: object references are trusted implicitly, rather than verified explicitly.


Defensive Strategies: Mitigating IDOR in Practice

1. Enforce Server-Side Access Control

Always enforce resource-level authorization on the server, not in the client or UI layer.

PHP Example:

$userId = $_SESSION['user_id'];
$requestId = $_GET['id'];
 
$stmt = $conn->prepare("SELECT * FROM documents WHERE id = ? AND owner_id = ?");
$stmt->bind_param("ii", $requestId, $userId);
$stmt->execute();

Even better: centralize authorization logic through a policy engine (e.g., OPA, Casbin) or service middleware.


2. Use Non-Predictable Identifiers

Expose opaque, non-sequential IDs—such as UUIDs or hashed references—to reduce the risk of enumeration.

Bad:

/invoice/10001
/invoice/10002

Better:

/invoice/8f4a9f0e-1b23-4aef-9f00-b94c0c3b9d2a

This doesn’t remove the need for access control, but it slows down attackers significantly and reduces exposure surface.


3. Harden Logs and Detection

  • Log all 403 and 404 responses tied to sensitive objects
  • Alert on high-velocity enumeration of sequential IDs
  • Detect access to multiple unrelated object owners from a single token

Conclusion: Simplicity Doesn’t Mean Safety

IDOR isn’t a novel attack—but it’s still one of the most prevalent and impactful flaws in real-world APIs. It doesn’t require advanced exploitation—just a failure to validate that a user is authorized to interact with a given object.

Trust nothing from the client. Every object reference is a potential vector unless proven safe.

By enforcing strict resource-level access controls, eliminating guessable identifiers, and continuously fuzzing for edge cases, you can mitigate IDOR risks before they’re discovered in production—or by an adversary.

Integrate Axe:ploit into your workflow today!