October 1, 2026
The Bug That Needs No Hacking: IDOR and Broken Access Control
One number. Someone else’s data.

By Faizan Baig
7 min read
The first time you find an IDOR, it almost feels like you did something wrong.
You're looking at a URL like this:
https://shop.example.com/invoice?id=1042https://shop.example.com/invoice?id=1042You change 1042 to 1043.
And there it is.
Someone else's invoice. Their name. Their address. What they bought.
No exploit. No payload. No complicated trick.
You just changed a number in the address bar.
That's the whole bug and it's still one of those vulnerabilities that developers and security testers need to take seriously.
I recently cleared the OSCP, and honestly, OSCP focuses heavily on infrastructure and Active Directory. Web access-control issues like IDOR aren't always the main focus.
But one habit OSCP definitely reinforces is:
Enumerate everything. Don't ignore the boring stuff.
And that mindset is exactly what makes you notice bugs like this.
So let's talk about IDOR properly.
What IDOR Actually Is
IDOR stands for Insecure Direct Object Reference.
The idea is simple:
An application uses an identifier supplied by the user to access an object, but fails to check whether that user is actually authorized to access it.
For example:
GET /api/invoices/1042GET /api/invoices/1042The application sees 1042 and retrieves that invoice.
But what happens if you change it to:
GET /api/invoices/1043GET /api/invoices/1043and the server returns an invoice belonging to another user?
That's a potential IDOR vulnerability.
The application asked:
"Which invoice do you want?"
But forgot to ask:
"Are you allowed to see it?"
That's the important part.
IDOR and Broken Access Control
IDOR is commonly associated with Broken Access Control, which is a much broader security problem.
Access control is basically the set of rules that determines:
- Who can access something
- What they can access
- What they can change
- What actions they are allowed to perform
When those rules aren't properly enforced, you have a broken access-control issue.
IDOR is one example of this.
Other examples include:
- Accessing another user's resources
- Performing administrative actions as a normal user
- Modifying data without permission
- Accessing restricted API endpoints
- Bypassing role-based restrictions
Logged In ≠ Allowed
This is one of the most important concepts to understand.
Authentication answers:
"Who are you?"
Authorization answers:
"What are you allowed to do?"
An application can correctly authenticate a user while completely failing to authorize their actions.
For example, imagine Alice logs into an application.
The server sees that Alice has a valid session and thinks:
User is authenticated ✓User is authenticated ✓But when Alice requests another user's invoice, the server also needs to check:
Is Alice authorized to access this invoice?Is Alice authorized to access this invoice?If that second check is missing, you have a problem.
Being logged in doesn't automatically mean you're allowed to access everything.
A Simple Example
Let's say an application has three users:
UserIDAlice101Bob102Charlie103
Alice requests her own profile:
GET /api/users/101GET /api/users/101The server returns Alice's profile.
Everything looks normal.
Now Alice changes the ID:
GET /api/users/102GET /api/users/102If the server returns Bob's private information, the application has failed to enforce authorization.
The server shouldn't only ask:
"Does user 102 exist?"
It should ask:
"Is Alice allowed to access user 102?"
That's the core idea behind IDOR.
Where I Actually Look for IDOR
IDOR isn't limited to numbers in URLs.
When testing an application, I pay attention to anything that looks like an object reference.
API Endpoints
For example:
GET /api/orders/5521GET /api/orders/5521If your account can access order 5521, what happens when you access another test account's order?
Modern applications rely heavily on APIs, so this is one of the first places worth checking.
Request Bodies
You might see something like:
{
"user_id": 88,
"address": "New Address"
}{
"user_id": 88,
"address": "New Address"
}If the server trusts user_id without checking authorization, changing it could potentially modify another user's information.
This can be more serious than a simple read because you're dealing with a write operation.
Download and Export Features
Look for things such as:
/download/receipt_2291.pdf/download/receipt_2291.pdfor APIs that accept:
file_id
document_id
invoice_idfile_id
document_id
invoice_idDownload and export functionality is worth paying attention to because applications often expose direct references to files and documents.
Forgotten Flows
Some of the interesting bugs aren't in the main application.
Look at features such as:
- Password reset
- Email changes
- Invitations
- Document sharing
- Account settings
- Export functions
Developers may protect the main application correctly while overlooking an older or less-used endpoint.
Administrative Functions
Sometimes the problem isn't accessing another user's data.
It can be accessing an administrative function as a normal user.
For example:
POST /admin/deleteUserPOST /admin/deleteUserIf the endpoint only relies on the frontend hiding the button, that's not real security.
A user can send the HTTP request directly.
Hiding a button is not access control.
The Two-Account Test
You don't need fancy tooling to understand the basic IDOR testing process.
The easiest approach is to use two accounts that you are authorized to control.
1. Create two test accounts
For example:
Account A → Alice
Account B → BobAccount A → Alice
Account B → BobMake sure both accounts have their own test data.
2. Use Account A
Log in as Alice and access one of her resources.
For example:
GET /api/invoices/5001GET /api/invoices/5001Capture the request using a proxy such as Burp Suite.
3. Switch to Account B
Log in as Bob and access one of Bob's own resources first.
This gives you a baseline for what Bob is normally allowed to access.
4. Test the object reference
Now, while authenticated as Bob, request Alice's test object:
GET /api/invoices/5001GET /api/invoices/50015. Compare the response
If Bob receives Alice's invoice even though Bob has no permission to access it, you've potentially found an IDOR / Broken Access Control issue.
Look at:
- HTTP status code
- Response body
- Returned object
- Error messages
- Sensitive information
- Whether the requested action succeeded
The goal isn't simply to prove that an ID can be changed.
The goal is to determine whether the server correctly enforces authorization.
Only perform these tests on applications you own, intentionally vulnerable labs, or systems where you have explicit authorization.
Burp Suite Makes This Easier
A typical workflow looks like this:
Capture request
↓
Identify object reference
↓
Switch to another test account
↓
Replay request
↓
Compare responseCapture request
↓
Identify object reference
↓
Switch to another test account
↓
Replay request
↓
Compare responseFor example, you might capture:
GET /api/orders/1001 HTTP/1.1
Host: lab.example
Cookie: session=...GET /api/orders/1001 HTTP/1.1
Host: lab.example
Cookie: session=...Then, in your authorized lab, test another object:
GET /api/orders/1002 HTTP/1.1
Host: lab.example
Cookie: session=...GET /api/orders/1002 HTTP/1.1
Host: lab.example
Cookie: session=...Then compare the responses.
You're looking for a difference between:
Expected:
Access deniedExpected:
Access deniedand:
Unexpected:
Another user's resource is returnedUnexpected:
Another user's resource is returnedDon't Test Only GET Requests
One mistake beginners often make is checking only whether they can read another user's data.
IDOR can also affect actions that modify or delete data.
Read
GET /api/profile/102GET /api/profile/102Potential impact: Unauthorized information disclosure.
Update
PUT /api/profile/102PUT /api/profile/102Potential impact: Unauthorized modification.
Delete
DELETE /api/document/102DELETE /api/document/102Potential impact: Unauthorized deletion.
That's why, when your authorized scope allows it, it's worth checking different operations rather than stopping after finding a read-only issue.
"But My IDs Are Random UUIDs"
Random IDs are useful.
They're just not a replacement for authorization.
You might see an application using sequential IDs:
1001
1002
1003
10041001
1002
1003
1004and think:
"The IDs are predictable, so the application has IDOR."
Not necessarily.
Predictable identifiers can make enumeration easier, but they aren't the root cause.
Even if the application uses UUIDs:
550e8400-e29b-41d4-a716-446655440000550e8400-e29b-41d4-a716-446655440000the server still needs to check authorization.
If that UUID is leaked through an email, shared link, log, or another API response, anyone who obtains it shouldn't automatically gain access to the resource.
Unpredictable IDs are an additional layer. Authorization is the real security control.
Chaining Can Increase the Impact
Sometimes an IDOR doesn't look particularly dangerous at first.
Imagine an endpoint that exposes:
Name
Email
User IDName
Email
User IDThat might initially appear to be a limited information disclosure.
But vulnerabilities don't always exist in isolation.
For example, if an application has another weak endpoint that uses the exposed email address in a sensitive workflow, the two issues could potentially be chained.
The important lesson is:
Don't stop at "I can access this." Ask what that access enables.
When writing a vulnerability report, explain the actual impact instead of simply saying:
"IDOR exists."
Show what an unauthorized user can actually read, modify, or delete.
How to Fix IDOR
The fix is usually straightforward.
The difficult part is making sure it's applied consistently.
1. Check Authorization on the Server
Don't only check:
Is the user logged in?Is the user logged in?Also check:
Does this user have permission to access this specific object?Does this user have permission to access this specific object?For example:
# Vulnerable
invoice = db.get_invoice(request.args["id"])
return invoice# Vulnerable
invoice = db.get_invoice(request.args["id"])
return invoiceA safer approach is to scope the lookup to the authenticated user:
# Better
invoice = db.get_invoice(
request.args["id"],
owner=current_user.id
)
if not invoice:
return "Not found", 404
return invoice# Better
invoice = db.get_invoice(
request.args["id"],
owner=current_user.id
)
if not invoice:
return "Not found", 404
return invoiceThe exact implementation depends on the application's authorization model, but the principle remains the same.
2. Don't Trust Client-Supplied Identity
Anything sent by the browser can be modified.
For example, don't blindly trust:
{
"user_id": 88
}{
"user_id": 88
}to determine who the current user is.
Where possible, derive the authenticated identity from the server-side session or validated authentication token.
3. Deny by Default
If a new endpoint is created, access shouldn't automatically be granted to everyone.
Define who can access it and what they can do.
4. Centralize Authorization
If every endpoint implements its own permission logic, someone will eventually forget a check.
Use shared middleware, policies, or authorization functions where appropriate.
5. Test Access Control
Create automated tests where:
User A → tries to access User B's resourceUser A → tries to access User B's resourceThe expected result should be denial.
These tests can catch authorization problems before they reach production.
Why This Bug Refuses to Die
One reason IDOR can be difficult for automated scanners to identify is that authorization depends heavily on application context.
A scanner can send strange characters to an input and potentially identify things like SQL injection.
But how does it know that:
Invoice #1043Invoice #1043belongs to another user?
That's a business-logic question.
The scanner may not know:
- Which user owns the object
- Which roles should have access
- Which resources are private
- Which actions are allowed
- What the application's authorization rules are
That's where manual testing and understanding the application's behavior become important.
The Takeaway
If you build applications:
A logged-in user is not automatically an authorized user.
Check authorization on the server, for every protected resource.
If you test applications:
Get curious about every ID you see.
Change it. Replay the request. Compare the response. Try different authorized test accounts.
The important question isn't:
"Can I change this ID?"
It's:
"What happens when I change it, and does the server correctly enforce authorization?"
That's the mindset that helps uncover Broken Access Control vulnerabilities.
And if you're new to web security, IDOR is a great vulnerability to learn.
You don't need an exploit toolkit.
You need two test accounts, a proxy, and the habit of asking:
"Wait… what happens if I change this?"
Sometimes the answer is more interesting than it should be.
I'm a cybersecurity student and OSCP holder writing about what I learn along the way. If you enjoyed this breakdown, follow along for more practical web security content and let me know which vulnerability I should cover next.