๐ The IDOR Handbook
The most comprehensive interactive guide to Insecure Direct Object Reference vulnerabilities. Master the concepts with 10 ultra-visual labs, study real-world breaches, and learn battle-tested prevention techniques.
๐ What is IDOR?
FundamentalsInsecure Direct Object Reference (IDOR) is an access control vulnerability that occurs when an application uses user-supplied input to directly access objects โ such as database records, files, or API resources โ without verifying that the authenticated user is authorized to access that specific object.
The Core Problem in One Sentence
The application checks "Is someone logged in?" (Authentication โ ) but never checks "Is THIS person allowed to access THIS specific resource?" (Authorization โ)
๐จ The Hotel Analogy
๐ Key Card
You check into a hotel and receive a key card. This is authentication โ the hotel knows WHO you are.
๐ช Your Room
Your key card should ONLY open Room 301 โ YOUR room. This is authorization โ controlling WHAT you can access.
๐ The Bug
You type "302" on the keypad and it opens Room 302! The system never verified your card was authorized for that room.
๐ฅ Impact
You can now enter ANY room simply by guessing the number. Every guest's belongings, privacy, and safety are compromised.
๐ Authentication vs Authorization โ The Critical Difference
"Prove your identity"
- Username & Password verification
- Session tokens & cookies
- JWT / OAuth 2.0 tokens
- Multi-factor authentication (MFA)
- Biometric verification
โ Verifies identity โ establishes who the user is
"Check your permissions"
- Can you view this specific record?
- Can you edit this specific file?
- Can you delete this specific resource?
- Are you the owner of this object?
- Do you have the required role/permission?
โ IDOR happens when this step is completely missing
๐๏ธ See the Vulnerability
Cookie: session=alice_token
โ Returns Alice's data (User 1001) โ
Alice is accessing her OWN profile. This is fine.
Cookie: session=alice_token (same session!)
โ Returns Bob's data (User 1002) ๐ด
Alice just read Bob's SSN, salary, and private data!
๐ IDOR in Security Frameworks
| Framework | Classification | Identifier |
|---|---|---|
| OWASP Top 10 (2021) | A01: Broken Access Control | #1 most critical |
| OWASP Top 10 (2017) | A5: Broken Access Control | โ |
| OWASP API Top 10 | API1: Broken Object Level Authorization | Also called BOLA |
| CWE | CWE-639: Authorization Bypass Through User-Controlled Key | Primary weakness |
| SANS Top 25 | Improper Access Control | CWE-284 family |
| CVSS v3.1 Range | 4.0 โ 9.8 | Medium to Critical |
| NIST SP 800-53 | AC-3: Access Enforcement | Access Control family |
โ๏ธ How IDOR Works โ The Mechanics
Deep DiveThe Vulnerable Request Flow
๐ Login
User authenticates and receives a session token. Server knows WHO they are.
๐ Navigate
User browses to a page/endpoint that contains a resource identifier (user ID, order ID, etc.)
๐ค Request
Browser sends request with the object reference:GET /api/users/1002
โ ๏ธ No AuthZ Check
Server validates the session โ but never checks if user 1001 can access user 1002's data โ
๐ฅ Data Leaked
Server returns user 1002's complete profile including SSN, salary, medical info โ to the wrong person!
๐ฅ๏ธ What Happens Server-Side
๐ป Vulnerable Code โ Multi-Language Examples
No ownership check โ any logged-in user can access any profile by changing the ID.
# โ VULNERABLE CODE โ DO NOT USE IN PRODUCTION!
@app.route('/api/users/<int:user_id>/profile')
@login_required
def get_profile(user_id):
# user_id comes directly from URL โ attacker-controlled!
# No check: does session['user_id'] == user_id?
user = db.query(User).filter(User.id == user_id).first()
if user:
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email,
'ssn': user.ssn, # ๐ด Sensitive!
'salary': user.salary, # ๐ด Sensitive!
'bank': user.bank_account # ๐ด Sensitive!
})
return jsonify({'error': 'Not found'}), 404
// โ VULNERABLE CODE
app.get('/api/orders/:orderId', authenticateToken, (req, res) => {
const orderId = req.params.orderId; // Attacker controls this!
// No check: req.user.id owns this order?
db.query('SELECT * FROM orders WHERE id = ?', [orderId],
(err, results) => {
if (results.length > 0) {
res.json(results[0]); // Returns ANY order!
} else {
res.status(404).json({ error: 'Not found' });
}
});
});
// โ VULNERABLE CODE
@GetMapping("/accounts/{accountId}")
public ResponseEntity<Account> getAccount(
@PathVariable Long accountId,
@AuthenticationPrincipal UserDetails userDetails) {
// Fetches by ID without ownership check!
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new NotFoundException("Not found"));
// Returns even if it belongs to someone else!
return ResponseEntity.ok(account);
}
<?php
// โ VULNERABLE CODE
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /login'); exit;
}
$doc_id = $_GET['doc_id']; // Attacker controls this!
// NO authorization check!
$stmt = $pdo->prepare("SELECT * FROM documents WHERE id = ?");
$stmt->execute([$doc_id]);
$doc = $stmt->fetch();
// Serves ANY user's document!
header('Content-Type: application/pdf');
readfile($doc['file_path']);
?>
๐ข Identifier Types & Guessability
| ID Format | Example | Guessability | Risk Level |
|---|---|---|---|
| Sequential Integer | id=1001, 1002, 1003 | Trivial โ just increment | ๐ด Critical |
| Short Numeric | id=4521 | Easy to brute-force (10K range) | ๐ด High |
| Predictable String | user=john.doe | Guessable from username patterns | ๐ก Medium |
| UUID v1 (Time-based) | 550e8400-e29b-41d4-... | Partially predictable (timestamp) | ๐ก Medium |
| UUID v4 (Random) | f47ac10b-58cc-4372-... | Hard to guess but can leak | ๐ต Lower* |
| Base64 Encoded | MTAwMg== (= "1002") | Security by obscurity only | ๐ด High |
| Hashed Value | a4b2c3d4e5f6... | Depends on algorithm | ๐ก Medium |
โ ๏ธ UUIDs Do NOT Fix IDOR!
Using UUIDs makes IDs harder to guess, but if a UUID leaks through URLs, emails, logs, API list responses, or referrer headers, the IDOR is fully exploitable. Server-side authorization checks are the ONLY real fix. UUIDs are defense-in-depth, not a solution.
๐ Types of IDOR Vulnerabilities
ClassificationBy Direction (Who's Affected?)
โ๏ธ Horizontal IDOR Most Common
Same privilege level. User A (regular user) accesses User B's (regular user) data. Both users have identical roles.
# Alice (employee) โ reads Bob's (employee) profile
GET /api/users/1002/profile
Authorization: Bearer alice_session_token
# Returns Bob's SSN, salary, medical data!
โ๏ธ Vertical IDOR Privilege Escalation
Different privilege level. Regular user accesses admin-level resources or elevates their own permissions.
# Regular user โ accesses admin endpoints
GET /api/admin/users/all
Authorization: Bearer regular_user_token
# Returns ALL users' data including admin secrets!
By Operation (What Can the Attacker Do?)
๐๏ธ Read (GET)
View another user's private data โ profile, orders, messages, documents.
โ๏ธ Write (PUT/PATCH)
Modify another user's data โ change email, role, settings, password.
๐๏ธ Delete (DELETE)
Remove another user's resources โ messages, files, accounts.
โ Create (POST)
Create resources in another user's context โ inject content into their account.
By Parameter Location (Where's the ID?)
The object ID is embedded directly in the URL path. Most common and easiest to spot. Simply change the number in the URL bar or intercept with Burp Suite.
GET /api/users/1001/profile โ Your profile
GET /api/users/1002/profile โ Bob's profile (IDOR!)
GET /api/users/1003/profile โ Charlie's profile (IDOR!)
GET /api/users/1004/profile โ Diana's profile (IDOR!)
GET /api/users/1005/profile โ Admin's profile (IDOR!)ID passed as a query string. Common in search endpoints, filters, exports, and legacy applications.
GET /api/profile?userId=1002
GET /download?fileId=8829
GET /api/export?user=1002&format=csvID inside POST/PUT request body (JSON, form data, XML). Harder to spot without intercepting traffic.
POST /api/transfer
{"from_account": "1002", "to_account": "9999", "amount": 5000}
// Attacker changes from_account to victim's ID!Custom headers carrying user identifiers. Common in microservice architectures where API gateways pass user context via headers.
Object references stored in cookies. Trivially manipulated with browser dev tools, cookie editors, or Burp Suite.
๐ฏ Where to Find IDOR โ Attack Surfaces
Hunting GuideCommon Vulnerable Endpoints
๐ค User Management
GET /api/users/{id}
GET /api/users/{id}/profile
PUT /api/users/{id}/settings
DELETE /api/users/{id}
GET /api/users/{id}/notifications๐ณ Financial / Billing
GET /api/invoices/{invoiceId}
GET /api/transactions/{txnId}
POST /api/refund/{orderId}
GET /api/payment-methods/{id}
GET /api/billing/{userId}/history๐ Documents / Files
GET /api/documents/{docId}/download
GET /api/files/{fileId}
DELETE /api/attachments/{id}
GET /api/reports/{reportId}
GET /api/exports/{exportId}๐ฌ Messaging
GET /api/messages/{messageId}
GET /api/conversations/{id}
GET /api/inbox/{userId}
DELETE /api/messages/{messageId}
GET /api/threads/{threadId}๐ E-Commerce
GET /api/orders/{orderId}
GET /api/orders/{id}/tracking
GET /api/cart/{cartId}
GET /api/wishlist/{userId}
GET /api/returns/{returnId}๐ฅ Healthcare
GET /api/patients/{patientId}/records
GET /api/prescriptions/{id}
GET /api/appointments/{id}
GET /api/lab-results/{resultId}
GET /api/insurance/{policyId}๐ต๏ธ Hidden Locations Often Missed by Testers
๐ Redirect URLs
POST /login โ 302 Location: /dashboard?user=1002
๐ WebSocket Messages
{"channel": "user_1002_notifications"}
๐ GraphQL Queries
query { user(id: 1002) { ssn salary } }
๐ค File Upload Paths
name="userId" value="1002" in multipart
๐ฅ Export / Reports
GET /export?userId=1002&format=csv
๐ Password Reset
POST /reset {"user_id": 1002}
๐ซ Support Tickets
GET /api/tickets/TKT-4521
๐ Webhooks / Callbacks
POST /webhooks {"user_id": 1002}
๐ฑ Mobile API Endpoints
Often less tested than web endpoints
๐ Real-World Case Studies
Breaches๐ Facebook โ Private Photos (2015)
Any user could view ANY user's private photos by changing the photo ID in the Graph API. Affected billions of private images.
๐ Uber โ Trip Details Exposure
Authenticated users could view any rider's trip history โ pickup/dropoff GPS coordinates, driver details, fare amounts, route maps.
๐๏ธ US Department of Defense
Bug bounty hunter discovered IDOR exposing military personnel SSNs, addresses, service records via sequential integer IDs.
๐๏ธ Shopify โ Merchant Revenue Data
Shop A could access Shop B's financial reports, revenue numbers, and profit margins by modifying the report_id parameter.
๐ HackerOne โ Private Bug Reports
IDOR on the bug bounty platform itself allowed reading private vulnerability reports from other companies' programs. Maximum irony.
๐ฆ Banking App โ Fund Transfer IDOR
Attacker changed source_account in transfer API requests, moving money FROM any customer's bank account to their own.
๐ฅ Healthcare Platform โ Patient Records
Patient portal exposed complete medical histories through sequential patient IDs โ diagnoses, prescriptions, insurance info, lab results.
โ๏ธ Airline Booking System โ PNR IDOR
Changing the PNR (booking reference) revealed other passengers' full names, passport numbers, flight itineraries, and payment details.
Bug Bounty Stats
IDOR accounts for roughly 15โ20% of all bug bounty reports across platforms like HackerOne and Bugcrowd. It's one of the most commonly found AND most commonly rewarded vulnerability classes, with average bounties of $500โ$5,000 per valid report.
๐๏ธ Lab Database Reference
Used by All 10 LabsThe following simulated database is used by all interactive labs below. Reference these tables when exploring vulnerabilities.
๐ก Lab Credentials
Login as any user: alice, bob, charlie, diana, or admin. Passwords are not checked in the simulator โ just select the username. Each user has a different role and owns different resources.
๐ฏ Challenge Yourself
Login as alice (a regular employee) and try to access admin's credentials (Doc ID 104), charlie's confidential revenue message (Msg ID 3), or bob's medical records (Doc ID 102). Can you extract ALL data without being the owner?
๐ค Users Table
| ID | Username | Name | Role | Dept |
|---|
๐ฆ Orders Table
| ID | Owner | Product | Amount | Status |
|---|
๐ฌ Messages Table
| ID | Sender | Receiver | Subject | Time |
|---|
๐ Documents Table
| ID | Owner | Filename | Classification | Type |
|---|
โ๏ธ Admin Endpoints Table (Lab 2)
| Method | Path | Description | Access |
|---|
๐งช Lab 1: Horizontal Profile IDOR
Login as one user and attempt to view another user's full profile โ including SSN, salary, passport, medical conditions, and bank details.
๐ Scenario
TechCorp's internal HR portal lets employees view their own profiles via GET /api/v1/users/{id}/profile. The endpoint checks authentication (valid session token) but does NOT check whether the logged-in user owns the requested profile. An attacker can simply change the user ID in the URL to access anyone's sensitive data.
๐ฉ Alice Logs In
Alice (employee, ID: 1001) authenticates and gets session token. Server knows WHO she is.
๐ Changes ID to 1002
Alice modifies the URL from /users/1001 to /users/1002. Same privilege level โ both are employees.
๐ฅ Bob's Data Leaked
Server returns Bob's SSN, salary, medical info, bank details โ without checking if Alice owns profile 1002.
๐งช Lab 2: Vertical IDOR โ Accessing Admin Endpoints
A regular employee attempts to access admin-only API endpoints โ system config, all user data, audit logs, revenue figures, and password reset functionality.
๐ Scenario
TechCorp has several admin-only API endpoints that should be restricted to users with the "admin" role. However, the API only checks if the user is authenticated (has a valid session token) โ it never checks the user's role. This means any logged-in employee can access:
- /api/v1/admin/users โ Full list of ALL users with SSNs and salaries
- /api/v1/admin/audit-log โ System audit trail with IP addresses
- /api/v1/admin/system-config โ Database passwords, AWS keys, JWT secrets
- /api/v1/admin/analytics/revenue โ Company-wide financial data
- /api/v1/admin/users/{id}/reset-password โ Force-reset ANY user's password
# โ VULNERABLE โ no role check!
@app.route('/api/v1/admin/users')
@login_required # Only checks authentication
def admin_get_users():
# Anyone with a valid session can access this!
return jsonify(db.query(User).all())
# โ
SECURE โ checks role!
@app.route('/api/v1/admin/users')
@login_required
@admin_required # Checks session.role == "admin"
def admin_get_users():
return jsonify(db.query(User).all())
โ ๏ธ Why Vertical IDOR is Often More Dangerous
While horizontal IDOR leaks one user's data at a time, vertical IDOR gives an attacker admin-level access โ meaning they can access ALL users' data, modify system configuration, reset any password, and potentially take over the entire application. The /admin/system-config endpoint above leaks database passwords and AWS keys โ this is a full system compromise.
๐งช Lab 3: Order Information IDOR
Access another user's order details โ product info, pricing, full shipping address, payment card digits, and tracking numbers.
๐ Scenario
TechCorp's e-commerce system allows users to check their order status at GET /api/v1/orders/{orderId}. The order IDs are sequential integers. An attacker can enumerate order IDs to access other customers' orders โ revealing products purchased, amounts paid, shipping addresses, and partial credit card numbers.
๐ฉ Login
Alice logs in. Her orders are 5001 and 5002.
๐ฆ View Own Order
Alice accesses /orders/5001 โ her MacBook order. Normal.
๐ Change ID
Alice changes URL to /orders/5003 โ Bob's Dell Monitor order.
๐ฅ Data Leaked
Bob's shipping address, card ****1337, and tracking number exposed!
๐งช Lab 4: Private Message IDOR
Read private messages between other users โ including confidential financial data and system credentials.
๐ Scenario
TechCorp's internal messaging system uses sequential message IDs. The endpoint GET /api/v1/messages/{msgId} checks if the user is authenticated but doesn't verify whether they are the sender or receiver. An attacker can read any private conversation in the company.
๐ฌ Msg 1-2
Casual team lunch conversation between Alice and Bob
Low sensitivity๐ฌ Msg 3-4
CONFIDENTIAL Q4 revenue data between Charlie (Finance) and Admin
Insider trading risk!๐ฌ Msg 6
Admin's personal password vault โ DB root, AWS keys, VPN credentials
๐จ Full system compromise!๐งช Lab 5: Confidential Document IDOR
Download another user's private files โ tax returns, medical reports, salary spreadsheets, and master admin credentials.
๐ Scenario
TechCorp's document management system allows downloading files via GET /api/v1/documents/{docId}. Documents have different classification levels, but the API endpoint ignores classification and doesn't check document ownership.
๐ Doc 101
alice's tax return
Private๐ Doc 102
bob's medical history
Confidential๐ Doc 103
ALL employee salaries
Restricted๐ Doc 104
Admin master credentials
๐จ Top SecretRegulatory Impact
Doc 102 (medical records) = HIPAA violation โ fines up to $1.5M/year. Doc 101 (tax return) = GDPR/PII breach โ fines up to 4% of revenue. Doc 103 (salary data) = employee lawsuit risk. Doc 104 (admin creds) = full system compromise.
๐งช Lab 6: Write IDOR โ Privilege Escalation
Modify another user's account data or escalate your own role from "employee" to "admin" โ using a simple PUT request.
๐ Scenario
TechCorp's profile update endpoint PUT /api/v1/users/{id}/profile allows users to update their display name and email. However, the API also accepts a role field in the request body โ and doesn't verify ownership OR role authorization. An attacker can:
- Self-promotion: Change own role to admin (vertical IDOR)
- Account takeover prep: Change another user's email to yours (horizontal IDOR)
- Sabotage: Demote an admin to a regular user
๐ Self-Promotion
PUT /users/1001/profile
{"role": "admin"}
Change YOUR own role
๐ง Email Hijack
PUT /users/1002/profile
{"email": "attacker@evil.com"}
Change BOB's email
๐ฃ Sabotage
PUT /users/1005/profile
{"role": "employee"}
Demote the ADMIN
๐งช Lab 7: Secure vs Insecure โ Side-by-Side
See exactly how the same request behaves with and without proper authorization checks. Toggle between vulnerable and secure server code.
๐ Scenario
This lab lets you toggle between two server implementations of the same endpoint:
- Insecure โ Server uses
db.query(User).get(user_id)with NO ownership check. Returns ALL fields including SSN and salary. - Secure โ Server verifies
session.user_id == requested_id, removes sensitive fields, and returns 403 for unauthorized access.
@app.route('/api/v1/users/<int:uid>/profile')
@login_required
def get_profile(uid):
# โ No ownership check!
user = db.query(User).get(uid)
return jsonify(user.to_dict())
# Returns ALL fields including SSN, salary
@app.route('/api/v1/users/<int:uid>/profile')
@login_required
def get_profile(uid):
# โ
Check ownership!
if session['user_id'] != uid \
and session['role'] != 'admin':
return jsonify({'error':'Forbidden'}), 403
user = db.query(User).get(uid)
return jsonify(user.to_safe_dict())
# Returns ONLY non-sensitive fields
๐งช Lab 8: Mass Enumeration Attack Simulator
Watch a real-time automated enumeration attack that loops through all IDs and extracts every record from the database. This is what an attacker does in practice.
๐ Scenario
An attacker writes a simple Python script (or uses Burp Intruder) to loop through sequential IDs, sending authenticated requests for each one. In minutes, they can extract the entire database. This lab simulates that attack in your browser โ watch as records stream in real-time.
๐ Login
Attacker authenticates with a single valid account
๐ Loop IDs
Script iterates through IDs: 1001, 1002, 1003โฆ
๐ฅ Extract
Each 200 OK response = another user's complete data
๐พ Store
Millions of records extracted in minutes โ mass data breach
Terminal output will stream here in real-timeโฆ
โ ๏ธ Why Mass Enumeration is Devastating
In a real attack, an attacker loops through millions of IDs in minutes using parallel requests, extracting every user's SSN, salary, medical records, private messages, and credentials. IDOR + sequential IDs = mass data breach. Rate limiting alone isn't sufficient โ you need proper authorization on every request.
๐งช Lab 9: Delete-Based IDOR โ Destroying Evidence
Delete another user's private messages. In real scenarios, attackers use this to destroy evidence, tamper with audit logs, or cause denial-of-service.
๐ Scenario
TechCorp's messaging API allows deleting messages at DELETE /api/v1/messages/{msgId}. The endpoint checks authentication but doesn't verify that the authenticated user is the sender or receiver. An attacker can delete ANY message in the system.
๐๏ธ Evidence Destruction
Delete audit logs, compliance records, or whistleblower reports that prove wrongdoing
๐ฅ Denial of Service
Mass-delete another user's emails, files, or data โ making their account unusable
๐ต๏ธ Cover Tracks
After stealing data via IDOR, delete the messages/logs that reference the stolen items
๐งช Lab 10: IDOR Chain Attack โ Full Account Takeover
Walk through a 5-step attack chain that starts with a simple profile IDOR and ends with complete account takeover. Each step exploits a different IDOR vulnerability.
๐ Scenario: Alice Takes Over Bob's Account
Alice (attacker) wants full access to Bob's account. She chains 5 IDOR vulnerabilities together, each building on the previous one:
๐ง Read Email
IDOR on /users/1002 leaks bob@techcorp.com
๐ Trigger Reset
POST /forgot-password with Bob's email
๐ซ Steal Token
IDOR on /users/1002/reset-token
๐ Change Pass
POST /reset-password with stolen token
๐ Takeover!
Login as Bob with new password
๐ Chain Logic
Step 1 leaks the email โ Step 2 uses that email to trigger a reset โ Step 3 steals the token via another IDOR โ Step 4 uses the token to change the password โ Step 5 logs in with the new password. Each step alone is a vulnerability; together they achieve full account takeover (CVSS 9.8).
๐ด This is How Real Account Takeovers Happen
This exact pattern has been reported in hundreds of bug bounty programs. A single IDOR that leaks an email address can cascade into full account takeover when combined with other weak endpoints. This is why every endpoint needs authorization checks โ not just the ones you think are "sensitive". CVSS for this chain: 9.8 (Critical).
๐ก๏ธ Prevention & Mitigation
DefenseThe Golden Rule of IDOR Prevention
NEVER trust user input for authorization decisions. ALWAYS verify on the server side that the authenticated user is authorized to access, modify, or delete the specific requested resource โ on every single request.
Fix 1: Server-Side Authorization Checks (Primary Fix)
The most important fix. Every endpoint that handles a resource MUST verify ownership before processing.
# โ VULNERABLE
@app.route('/api/users/<int:uid>/profile')
@login_required
def get_profile(uid):
user = db.query(User).get(uid)
return jsonify({
'ssn': user.ssn, # ๐ด Leaked!
'salary': user.salary # ๐ด Leaked!
})
# โ
SECURE
@app.route('/api/users/<int:uid>/profile')
@login_required
def get_profile(uid):
if session['user_id'] != uid \
and session['role'] != 'admin':
return jsonify({'error':'Forbidden'}), 403
user = db.query(User).get(uid)
return jsonify(user.to_safe_dict())
Fix 2: Scope Database Queries to Current User
// โ Returns ANY order
db.query('SELECT * FROM orders WHERE id = ?',
[orderId]);
// โ
Only returns user's own orders
db.query(
'SELECT * FROM orders WHERE id=? AND user_id=?',
[orderId, req.user.id]);
Fix 3: Session-Based Queries (Eliminate User Input)
# โ
BEST PRACTICE: No user-controlled ID needed!
@app.route('/api/my-profile')
@login_required
def get_my_profile():
user_id = session['user_id'] # From SERVER session
user = db.query(User).get(user_id)
return jsonify(user.to_safe_dict())
# Instead of: GET /api/users/1002/profile โ attackable!
# Use: GET /api/my-profile โ safe!
Fix 4: Indirect Object References
# Map random per-user tokens to real IDs
# Instead of: GET /api/orders/5001 โ real DB ID
# Use: GET /api/orders/a8Kx9mPq2wL โ random token
# Token "a8Kx9mPq2wL" maps to order 5001 ONLY for User A
# If User B uses same token โ resolves to NOTHING
import secrets
class IndirectRefMap:
def __init__(self):
self.maps = {}
def create(self, user_id, real_id):
token = secrets.token_urlsafe(12)
self.maps.setdefault(user_id, {})[token] = real_id
return token
def resolve(self, user_id, token):
return self.maps.get(user_id, {}).get(token)
Fix 5: Reusable Authorization Middleware
# โ
Reusable decorator โ apply to ANY endpoint
def owns_resource(model, id_param='id', owner_field='user_id'):
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
rid = kwargs.get(id_param)
resource = db.query(model).get(rid)
if not resource:
return jsonify({'error':'Not found'}), 404
if getattr(resource, owner_field) != session['user_id']:
if session.get('role') != 'admin':
return jsonify({'error':'Forbidden'}), 403
kwargs['resource'] = resource
return f(*args, **kwargs)
return wrapped
return decorator
# Usage โ clean, consistent!
@app.route('/api/orders/<int:id>')
@login_required
@owns_resource(Order)
def get_order(id, resource=None):
return jsonify(resource.to_dict())
Fix 6: Database-Level Row Security
-- PostgreSQL Row Level Security
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_orders ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::int);
-- Even buggy app code can't bypass this!
-- DB silently returns ZERO unauthorized rows ๐ก๏ธ
Fix 7: Framework-Level Solutions
class OrderViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Order.objects.filter(user=self.request.user)
# GET /api/orders/5003 โ auto 404 if not yours!
class OrderPolicy {
public function view(User $user, Order $order) {
return $user->id === $order->user_id;
}
}
// Controller: $this->authorize('view', $order);
@GetMapping("/orders/{id}")
@PreAuthorize("@orderSec.isOwner(authentication, #id)")
public Order getOrder(@PathVariable Long id) {
return orderRepository.findById(id).orElseThrow();
}
async function ownsOrder(req, res, next) {
const order = await Order.findById(req.params.id);
if (!order) return res.status(404).json({error:'Not found'});
if (order.userId !== req.user.id && req.user.role !== 'admin')
return res.status(403).json({error:'Forbidden'});
req.resource = order; next();
}
app.get('/api/orders/:id', auth, ownsOrder, handler);
โ Complete Prevention Checklist
๐ Server-Side Controls
- โ Every endpoint has authorization checks
- โ Authorization checked on EVERY request
- โ Ownership verified against server-side session
- โ Queries scoped to authenticated user
- โ Framework-level access control policies
- โ Row-level security at database level
- โ Default-deny access control (whitelist)
๐ฅ Input & Response
- โ User-supplied IDs validated & sanitized
- โ Indirect object references where possible
- โ Error responses don't reveal existence
- โ Sensitive fields removed from responses
- โ Return 404 (not 403) to prevent enum
- โ UUIDs as defense-in-depth (not sole fix)
๐งช Testing & QA
- โ Automated IDOR tests in CI/CD
- โ Regular penetration testing
- โ Code review checklist includes authz
- โ Two-account testing for all features
- โ Autorize / AuthMatrix in QA cycle
- โ Regression tests for fixed IDORs
๐ Monitoring & Architecture
- โ Failed authz attempts logged & alerted
- โ Unusual access patterns detected
- โ Enumeration blocked via rate limiting
- โ Authorization logic centralized
- โ API gateway enforces access policies
- โ Microservices validate authz independently
๐ง Tools & Testing Methodology
PracticalEssential Tools
๐ถ Burp Suite
Primary tool. Proxy โ Repeater โ Intruder โ Comparer.
๐ Autorize
Burp extension. Auto-replays with low-priv session. ๐ด bypassed ๐ข enforced.
Must-Have๐ท๏ธ OWASP ZAP
Free alternative. Access Control Testing add-on.
Free & Open Source๐ฎ Postman / Insomnia
Manual API testing with multiple environments.
๐ Python + Requests
Custom scripts for automated enumeration.
๐ฅ๏ธ cURL / HTTPie
Quick terminal-based spot-checks.
5-Phase Testing Methodology
๐ Recon
Create 2+ accounts. Map endpoints. Note all ID parameters.
๐ฏ Identify
What object? What CRUD op? Where's the ID? What data?
๐ฅ Exploit
User A's session โ User B's IDs. All HTTP methods.
โ Validate
Confirm other's data. Verify impact. Check all methods.
๐ Report
Clear title. Repro steps. HTTP evidence. Remediation.
Advanced Bypass Techniques
GET /api/users/1002 โ 403 (blocked)
POST /api/users/1002 โ 200 OK! (IDOR!)
PUT /api/users/1002 โ 200 OK! (IDOR!)Original: /users/1002 Leading 0: /users/01002
Float: /users/1002.0 Null byte: /users/1002%00
URL enc: /users/%31%30%30%32
Negative: /users/-1 Array: /users/[1002]POST /api/update-profile
{"name":"Attacker", "user_id": 1002} โ injected!
GET /api/users/1001/profile?user_id=1002echo -n "1002" | base64 โ MTAwMg==
echo -n "1003" | base64 โ MTAwMw==
# If endpoint uses MTAwMQ==, try MTAwMg==GET /api/users/*/profile
GET /api/users/all
POST /api/users/batch {"ids":[1001,1002,1003]}Response Analysis Guide
| Response | Meaning | Verdict |
|---|---|---|
200 + other's data | Returned unauthorized data | ๐ด IDOR! |
200 + your data | Server rewrites ID | ๐ก Partial fix |
403 Forbidden | Authorization exists | ๐ข Enforced |
404 Not Found | Could be authz | ๐ข May be secure |
401 Unauthorized | Auth check only | ๐ก Retest with session |
500 Error | Server processing | ๐ก Investigate |
๐ IDOR in APIs (REST, GraphQL, WebSocket)
API SecurityBOLA โ API-Specific Term
In OWASP API Security Top 10, IDOR = BOLA (Broken Object Level Authorization) โ the #1 API vulnerability. BFLA = vertical IDOR.
๐ REST
GET /api/v1/users/{userId}
PUT /api/v1/users/{userId}/settings
# Nested โ check EVERY level:
GET /orgs/{orgId}/teams/{teamId}/members/{id}โผ๏ธ GraphQL
query { user(id:"1002") { ssn salary } }
# Batch IDOR:
query {
a: user(id:"1001") { ssn }
b: user(id:"1002") { ssn }
}๐ WebSocket
ws.send(JSON.stringify({
action: "subscribe",
channel: "user_1002_notifs"
}));
// Receives another user's real-time data!Architecture Pitfalls
Each microservice MUST validate authz independently.
# โ Vulnerable Lambda
uid = event['pathParameters']['userId']
return table.get_item(Key={'userId': uid})
# โ
Secure Lambda
auth_user = event['requestContext'] \
['authorizer']['claims']['sub']
if auth_user != requested_user:
return {'statusCode': 403}๐ฅ Impact & Risk Assessment
Critical๐ Confidentiality
| PII | High |
| SSN / National ID | Critical |
| Financial data | Critical |
| Medical records | Critical |
| Credentials | Critical |
โ๏ธ Integrity
| Modify profile | High |
| Change password | Critical |
| Alter financials | Critical |
| Privilege escalation | Critical |
๐๏ธ Availability
| Delete account | Critical |
| Delete user data | High |
| Lock out users | High |
| Mass destruction | Critical |
CVSS Scoring
| Scenario | CVSS | Severity |
|---|---|---|
| Read-only profile (non-sensitive) | 4.3 | Medium |
| Read-only PII exposure | 6.5 | Medium |
| Read + Write IDOR | 8.1 | High |
| Full CRUD on any user's data | 8.8 | High |
| Account takeover chain | 9.8 | Critical |
| Unauthenticated IDOR | 9.1 | Critical |
๐ฐ Business Impact
๐ธ Financial
- GDPR: Up to โฌ20M / 4%
- HIPAA: Up to $1.5M/yr
- PCI: $5K-$100K/mo
- Lawsuits
๐ Reputation
- Customer trust loss
- Negative media
- Stock decline
- Customer churn
โ๏ธ Legal
- Breach notifications
- Regulatory audits
- Class-action suits
- Exec liability
๐ง Operations
- IR costs
- System downtime
- Security re-audits
- Architecture rework
๐ IDOR Chain Attacks
AdvancedIDOR vulnerabilities become devastating when chained together or with other vulnerability classes:
๐ง Leak Email
Profile IDOR reveals victim's email
๐ Reset
Trigger reset for victim's email
๐ซ Steal Token
IDOR on reset-token endpoint
๐ New Pass
Use stolen token
๐ Login
Full account control
CVSS 9.8 โ Demonstrated in Lab 10. Found in hundreds of real programs.
๐ Find Admin
Enumerate users
๐ Get Keys
IDOR on admin's API keys
๐ Admin
Use stolen keys
๐ช Backdoor
Persistent access
๐ฅ Enum Users
Loop /users/1โฆ100K
๐ Enum Docs
Get each user's docs
๐ฅ Download
IDOR to get all files
๐ฃ Breach
Complete exfiltration
Demonstrated in Lab 8. Millions of records in minutes.
๐ Find Account
IDOR reveals victim's account #
๐ธ Transfer
Change source_account in API
๐ฐ Steal
Money moved to attacker
๐ IDOR Report Template
Copy & UseProfessional template for reporting IDOR vulnerabilities in bug bounty programs or penetration tests:
# IDOR Vulnerability Report
## Title
Insecure Direct Object Reference in [Endpoint]
allows [Action] on other users' [Resource Type]
## Severity
[Critical / High / Medium / Low] โ CVSS: X.X
## Summary
The endpoint `[METHOD] [URL]` does not verify that the
authenticated user is authorized to access the requested
[resource]. An attacker can [action] by modifying the
`[parameter]` in the [URL path / query / body / header].
## Steps to Reproduce
1. Create two accounts: Attacker (A) + Victim (B)
2. Login as User A โ note session token
3. Login as User B โ note their [resource] ID: [value]
4. As User A, send:
[Full HTTP Request with headers]
5. Observe: User B's data returned without authorization
## Proof of Concept
[Paste full HTTP request + response]
## Impact
- Attacker can [read/modify/delete] ANY user's [resource]
- Sensitive data exposed: [list fields]
- Affected users: [estimated number]
- Regulatory: [GDPR/HIPAA/PCI if applicable]
- Chain potential: [describe if applicable]
## Remediation
1. Server-side authz: session.user_id == resource.owner_id
2. Scoped queries: WHERE id=? AND user_id=?
3. Return 404 (not 403) to prevent enumeration
4. Consider indirect object references
5. Add rate limiting for enumeration defense
## References
- OWASP A01:2021 Broken Access Control
- CWE-639: AuthZ Bypass via User-Controlled Key
- OWASP API Top 10: API1 BOLA
๐ Glossary
Reference| Term | Definition |
|---|---|
| IDOR | Insecure Direct Object Reference โ accessing objects without authorization checks |
| AuthN | Authentication โ verifying WHO the user is (identity) |
| AuthZ | Authorization โ verifying WHAT the user can do (permissions) |
| BOLA | Broken Object Level Authorization โ API term for horizontal IDOR (#1 API vuln) |
| BFLA | Broken Function Level Authorization โ API term for vertical IDOR |
| Horizontal | Accessing another user's resources at the same privilege level |
| Vertical | Accessing resources belonging to a higher-privileged user (e.g., admin) |
| Indirect Ref | Random per-user token mapped to real ID server-side |
| RLS | Row-Level Security โ database-enforced access control |
| RBAC | Role-Based Access Control โ permissions by role |
| ABAC | Attribute-Based Access Control โ permissions by attributes |
| ATO | Account Takeover โ gaining full control of another user's account |
| Mass Assignment | API accepts fields user shouldn't control (e.g., role) |
| UUID v4 | Random UUID โ harder to guess but doesn't fix IDOR |
| Autorize | Burp Suite extension for automated authz bypass testing |
| CWE-639 | Authorization Bypass Through User-Controlled Key |
| CVSS | Common Vulnerability Scoring System (0.0โ10.0) |