> ## Documentation Index
> Fetch the complete documentation index at: https://0-x.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# IDOR (Insecure Direct Object Reference)

An IDOR happens when an application uses a user-supplied identifier (an ID in a URL, a filename, a reference in a JSON body) to fetch an object, but only checks that you're logged in, not that you're allowed to access that specific object. Change the ID, see someone else's data. It's one of the simplest vulnerability classes to understand and one of the easiest to miss in a quick scan, since there's no payload to detect, just a number that happens to belong to someone else.

> **Horizontal IDOR**
>
> Accessing another user's data at the same privilege level, someone else's invoice, profile, or order instead of your own.

> **Vertical IDOR**
>
> Accessing data or functionality meant for a higher privilege level, an admin-only object or action, without actually having that role.

> [!TIP]
>
> IDOR testing is inherently a two account strategy. Register (or get) two accounts at the same privilege level, perform an action as account A, note every ID it touches, then try to access those same IDs as account B. Without a second account, you're just guessing at what "someone else's object" would look like.

***

## Finding the Reference Point

Start by identifying every place an object reference shows up, not just the obvious URL parameter.

```text theme={null}
http://SERVER_IP:PORT/documents.php?uid=3
```

> **Where else object references hide**
>
> * **POST body fields**: `{"user_id": 1042, "action": "view"}`
> * **Custom headers**: `X-User-ID: 1042`
> * **Cookies**: a session or preference cookie carrying a raw user/account ID
> * **File paths**: `/invoices/INV-1042.pdf`, `/uploads/1042/profile.jpg`
> * **Hidden form fields**: an `<input type="hidden">` carrying an account or object ID
> * **API responses and page source**: a GUID or internal ID leaked in a JS variable, an API response's metadata, or an email notification link

***

## Testing Numeric IDs

Simple increment/decrement is the first thing to try, sequential integer IDs are still extremely common.

```text theme={null}
curl -s "http://SERVER_IP:PORT/documents.php?uid=3"
curl -s "http://SERVER_IP:PORT/documents.php?uid=4"
curl -s "http://SERVER_IP:PORT/documents.php?uid=1"
```

Fuzzing a range at once is faster once you've confirmed the parameter is exploitable at all, filtering on response size separates real objects from "not found" pages.

```text theme={null}
ffuf -w <(seq 1 1000) -u "http://SERVER_IP:PORT/documents.php?uid=FUZZ" -fs BASELINE_SIZE
```

> [!TIP]
>
> Don't stop at read access. The same ID swap against a `DELETE`, `PUT`, or state-changing `POST` request (cancel an order, change an email, delete a document) is often more impactful than just viewing someone else's data, and is just as easy to test with the same tooling.

***

## Bypassing Non-Sequential IDs

> **GUIDs / UUIDs in use instead of integers**
>
> A random-looking GUID isn't guessable by incrementing, but it's rarely actually unguessable, it's just not visible in the current request. Check everywhere else the same object's GUID might already be exposed: another API response, a notification email, page source, or a different endpoint that happens to list GUIDs alongside more guessable data (like a username).

> **App checks role instead of ownership**
>
> A common flawed implementation: the backend checks "is this user an authenticated member" rather than "does this user own this specific object." If horizontal IDOR (same role, different user) fails, try vertical: does a regular user's token work against an endpoint clearly meant for a different role, just because the role check itself is missing rather than the object-ownership check?

> **Content-Type / format switching**
>
> An authorization check implemented for the JSON endpoint sometimes isn't mirrored on an XML or form-encoded version of the same endpoint. Try sending the identical request with `Content-Type: application/xml` or `application/x-www-form-urlencoded` instead of `application/json`, and see if the authorization logic still applies.

> **Parameter pollution and array wrapping**
>
> Sending the same parameter twice, or wrapping it in an array, sometimes causes the backend's authorization check and its actual data fetching logic to read different values of the "same" parameter.
>
> ```text theme={null}
> ?uid=YOUR_ID&uid=VICTIM_ID
> ?uid[]=VICTIM_ID
> ```

> **Mass assignment alongside the ID**
>
> If an update endpoint accepts a JSON body, try adding fields beyond what the form actually exposes, a `role`, `isAdmin`, or `balance` field the client never sends but the backend might still bind.
>
> ```json theme={null}
> {"user_id": 1042, "email": "me@test.com", "role": "admin"}
> ```

***

## Blind IDOR

Not every IDOR gives you a response to read, deleting, disabling, or modifying another user's object often returns a generic success message either way. Confirm impact out-of-band instead of relying on the response body.

**1. Perform the action against a known victim ID**

E.g. attempt to cancel or modify an object belonging to your second test account.

**2. Check from the victim's side**

Log back in as the victim account (or have a teammate check) to confirm whether the state actually changed, a generic "Success" response from account A proves nothing on its own.

***

## Tooling

**Burp Suite**

**Autorize** (extension) automates exactly the two-account comparison this vulnerability class needs: it replays every request you make as a low-privileged user's session and flags any that still return data, catching IDOR and broken access control across an entire app as you browse normally.

**Intruder** works for straightforward numeric ID brute forcing, same idea as the FFUF command above, just inside Burp if you're already working there.

**FFUF**

Covered above, fastest for raw numeric range fuzzing once you know the parameter and baseline response size.

**Postman**

Useful for manually replaying a captured request with one field (the ID) swapped, particularly for multi-step or authenticated API flows where a simple curl one-liner doesn't carry enough context (auth headers, CSRF tokens).

***

## Quick Reference Tools

> **[Hackviser: IDOR](https://hackviser.com/tactics/pentesting/web/idor)**
>
> Reference used for this page.

> **[HackTricks: IDOR](https://hacktricks.wiki/en/pentesting-web/idor.html)**
>
> Deeper dive into additional bypass tricks.

> **[Autorize](https://github.com/portswigger/autorize)**
>
> Burp extension automating authorization/IDOR testing across an entire browsing session.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.