IDOR Handbook Ultra Visual Edition

๐Ÿ”“ 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.

10 Interactive Labs
Visual HTTP Flows
Secure vs Insecure
Real-World Breaches
Chain Attack Demos
Full Report Template
OWASP Rank
#0
Broken Access Control
Bug Bounty Reports
0
Across major platforms
Average Bounty
0
Per valid IDOR report
Interactive Labs
0
Hands-on exercises

๐Ÿ“– What is IDOR?

Fundamentals

Insecure 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

1

๐Ÿ”‘ Key Card

You check into a hotel and receive a key card. This is authentication โ€” the hotel knows WHO you are.

2

๐Ÿšช Your Room

Your key card should ONLY open Room 301 โ€” YOUR room. This is authorization โ€” controlling WHAT you can access.

3

๐Ÿ”“ The Bug

You type "302" on the keypad and it opens Room 302! The system never verified your card was authorized for that room.

4

๐Ÿ’ฅ 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

๐Ÿ”‘ Authentication (AuthN) WHO are you?

"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

๐Ÿ›ก๏ธ Authorization (AuthZ) WHAT can you do?

"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

โœ… Normal Request (Your Data)Safe
GET/api/users/1001/profile

Cookie: session=alice_token

โ†’ Returns Alice's data (User 1001) โœ…

Alice is accessing her OWN profile. This is fine.

๐Ÿ”ด IDOR Attack (Other's Data)Vulnerable!
GET/api/users/1002/profile

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

FrameworkClassificationIdentifier
OWASP Top 10 (2021)A01: Broken Access Control#1 most critical
OWASP Top 10 (2017)A5: Broken Access Controlโ€”
OWASP API Top 10API1: Broken Object Level AuthorizationAlso called BOLA
CWECWE-639: Authorization Bypass Through User-Controlled KeyPrimary weakness
SANS Top 25Improper Access ControlCWE-284 family
CVSS v3.1 Range4.0 โ€“ 9.8Medium to Critical
NIST SP 800-53AC-3: Access EnforcementAccess Control family

โš™๏ธ How IDOR Works โ€” The Mechanics

Deep Dive

The Vulnerable Request Flow

1

๐Ÿ”‘ Login

User authenticates and receives a session token. Server knows WHO they are.

2

๐ŸŒ Navigate

User browses to a page/endpoint that contains a resource identifier (user ID, order ID, etc.)

3

๐Ÿ“ค 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 Server LogicMissing Step
1๏ธโƒฃReceive request: GET /api/users/1002
2๏ธโƒฃValidate session token โ†’ Valid โœ…
3๏ธโƒฃExtract user_id from URL โ†’ 1002
4๏ธโƒฃQuery: SELECT * FROM users WHERE id=1002
5๏ธโƒฃReturn full user data โ†’ 200 OK ๐Ÿ’ฅ
โœ… Secure Server LogicProper Check
1๏ธโƒฃReceive request: GET /api/users/1002
2๏ธโƒฃValidate session token โ†’ Valid โœ…
3๏ธโƒฃExtract user_id from URL โ†’ 1002
4๏ธโƒฃCheck: session.user_id (1001) == 1002? โ†’ NO
5๏ธโƒฃCheck: session.role == admin? โ†’ NO
6๏ธโƒฃReturn 403 Forbidden ๐Ÿ›ก๏ธ

๐Ÿ’ป 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 FormatExampleGuessabilityRisk Level
Sequential Integerid=1001, 1002, 1003Trivial โ€” just increment๐Ÿ”ด Critical
Short Numericid=4521Easy to brute-force (10K range)๐Ÿ”ด High
Predictable Stringuser=john.doeGuessable 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 EncodedMTAwMg== (= "1002")Security by obscurity only๐Ÿ”ด High
Hashed Valuea4b2c3d4e5f6...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

Classification

By 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.

GET/api/users/1002/profile
Information Disclosure

โœ๏ธ Write (PUT/PATCH)

Modify another user's data โ€” change email, role, settings, password.

PUT/api/users/1002/email
Data Modification

๐Ÿ—‘๏ธ Delete (DELETE)

Remove another user's resources โ€” messages, files, accounts.

DELETE/api/users/1002
Data Destruction

โž• Create (POST)

Create resources in another user's context โ€” inject content into their account.

POST/api/users/1002/notes
Context Injection

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=csv

ID 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 Guide

Common 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.

Billions of photosBounty: $12,500

๐Ÿš— Uber โ€” Trip Details Exposure

Authenticated users could view any rider's trip history โ€” pickup/dropoff GPS coordinates, driver details, fare amounts, route maps.

Location TrackingPrivacy Violation

๐Ÿ›๏ธ US Department of Defense

Bug bounty hunter discovered IDOR exposing military personnel SSNs, addresses, service records via sequential integer IDs.

National SecuritySSN Exposure

๐Ÿ›๏ธ Shopify โ€” Merchant Revenue Data

Shop A could access Shop B's financial reports, revenue numbers, and profit margins by modifying the report_id parameter.

Financial DataBounty: $15,000+

๐Ÿ› HackerOne โ€” Private Bug Reports

IDOR on the bug bounty platform itself allowed reading private vulnerability reports from other companies' programs. Maximum irony.

Maximum IronySequential IDs

๐Ÿฆ Banking App โ€” Fund Transfer IDOR

Attacker changed source_account in transfer API requests, moving money FROM any customer's bank account to their own.

CVSS 9.8Direct Financial Loss

๐Ÿฅ Healthcare Platform โ€” Patient Records

Patient portal exposed complete medical histories through sequential patient IDs โ€” diagnoses, prescriptions, insurance info, lab results.

HIPAA ViolationMulti-million fine

โœˆ๏ธ Airline Booking System โ€” PNR IDOR

Changing the PNR (booking reference) revealed other passengers' full names, passport numbers, flight itineraries, and payment details.

Passport DataGDPR Impact

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 Labs

The 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

IDUsernameNameEmailRoleDept

๐Ÿ“ฆ Orders Table

IDOwnerProductAmountStatus

๐Ÿ’ฌ Messages Table

IDSenderReceiverSubjectTime

๐Ÿ“ Documents Table

IDOwnerFilenameClassificationType

โš™๏ธ Admin Endpoints Table (Lab 2)

MethodPathDescriptionAccess

๐Ÿงช 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.

Vulnerable Read-Based Horizontal

๐Ÿ“‹ 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.

1

๐Ÿ‘ฉ Alice Logs In

Alice (employee, ID: 1001) authenticates and gets session token. Server knows WHO she is.

2

๐Ÿ”€ Changes ID to 1002

Alice modifies the URL from /users/1001 to /users/1002. Same privilege level โ€” both are employees.

3

๐Ÿ’ฅ Bob's Data Leaked

Server returns Bob's SSN, salary, medical info, bank details โ€” without checking if Alice owns profile 1002.

Try: 1001 (alice), 1002 (bob), 1003 (charlie), 1004 (diana), 1005 (admin)
๐Ÿ“ก Send the request to see the result. Try accessing another user's profile by changing the ID!

๐Ÿงช 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.

Critical Vertical Privilege Escalation

๐Ÿ“‹ 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
โ†”๏ธ Horizontal IDOR (Lab 1)Same Level
๐Ÿ‘ฉAlice (employee)
โ†’Accesses Bob's (employee) data
๐Ÿ“ŠSame privilege level, different user
โ†•๏ธ Vertical IDOR (Lab 2)Higher Level
๐Ÿ‘ฉAlice (employee)
โฌ†๏ธAccesses Admin-only endpoints
๐Ÿ”ดDifferent privilege level โ€” escalation!
โŒ Vulnerable Admin CheckNo Role Check
1๏ธโƒฃReceive: GET /api/v1/admin/users
2๏ธโƒฃValidate session โ†’ โœ… Valid token
3๏ธโƒฃSELECT * FROM users โ†’ Returns ALL ๐Ÿ’ฅ
# โŒ 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 Admin CheckRole Verified
1๏ธโƒฃReceive: GET /api/v1/admin/users
2๏ธโƒฃValidate session โ†’ โœ… Valid token
3๏ธโƒฃRole check: user.role == "admin"? โ†’ NO
4๏ธโƒฃReturn 403 Forbidden ๐Ÿ›ก๏ธ
# โœ… 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())
Login as alice (employee) โ†’ try accessing system-config or reset-password!
๐Ÿ“ก Select an admin endpoint and login as a non-admin user. Can a regular employee access admin secrets?

โš ๏ธ 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.

Vulnerable Read-Based PII Leakage

๐Ÿ“‹ 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.

1

๐Ÿ‘ฉ Login

Alice logs in. Her orders are 5001 and 5002.

2

๐Ÿ“ฆ View Own Order

Alice accesses /orders/5001 โ†’ her MacBook order. Normal.

3

๐Ÿ”€ Change ID

Alice changes URL to /orders/5003 โ€” Bob's Dell Monitor order.

4

๐Ÿ’ฅ Data Leaked

Bob's shipping address, card ****1337, and tracking number exposed!

Alice's: 5001-5002 | Bob's: 5003-5004 | Charlie's: 5005 | Diana's: 5006 | Admin's: 5007
๐Ÿ“ก Try accessing an order that doesn't belong to you! Order 5007 is the admin's $18,999 security device.

๐Ÿงช Lab 4: Private Message IDOR

Read private messages between other users โ€” including confidential financial data and system credentials.

Vulnerable Confidential Espionage Risk

๐Ÿ“‹ 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!
๐Ÿ”ฅ Try 3 (Q4 revenue), 4 (insider directive), or 6 (system passwords!)
๐Ÿ“ก Read a private message you shouldn't have access to. Messages 3, 4, and 6 contain extremely sensitive data!

๐Ÿงช Lab 5: Confidential Document IDOR

Download another user's private files โ€” tax returns, medical reports, salary spreadsheets, and master admin credentials.

Vulnerable Top Secret HIPAA / GDPR

๐Ÿ“‹ 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 Secret
๐Ÿ”ฅ Try 102 (medical), 103 (ALL salaries), or 104 (admin passwords!)
๐Ÿ“ก Access confidential documents you shouldn't see. Doc 104 is ๐Ÿ”ด Top Secret โ€” contains root passwords!

Regulatory 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.

Critical Write-Based Priv Escalation

๐Ÿ“‹ 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
A

๐Ÿ‘‘ Self-Promotion

PUT /users/1001/profile
{"role": "admin"}
Change YOUR own role

B

๐Ÿ“ง Email Hijack

PUT /users/1002/profile
{"email": "attacker@evil.com"}
Change BOB's email

C

๐Ÿ’ฃ Sabotage

PUT /users/1005/profile
{"role": "employee"}
Demote the ADMIN

Try: Set YOUR role to "admin", or change Bob's (1002) email!
๐Ÿ“ก Promote yourself to admin or hijack someone's account!

๐Ÿงช 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.

Educational Compare Best Practice

๐Ÿ“‹ 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.
โŒ Insecure CodeVulnerable
@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
โœ… Secure CodeFixed
@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
Login as alice โ†’ target bob (1002) โ†’ switch between modes to see the difference!
๐Ÿ“ก Compare the behavior! Same request, different server implementations. Try both modes!

๐Ÿงช 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.

Advanced Automated Data Breach

๐Ÿ“‹ 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.

1

๐Ÿ”‘ Login

Attacker authenticates with a single valid account

2

๐Ÿ”„ Loop IDs

Script iterates through IDs: 1001, 1002, 1003โ€ฆ

3

๐Ÿ“ฅ Extract

Each 200 OK response = another user's complete data

4

๐Ÿ’พ 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.

Vulnerable Destructive DELETE Method

๐Ÿ“‹ 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.

1

๐Ÿ—‘๏ธ Evidence Destruction

Delete audit logs, compliance records, or whistleblower reports that prove wrongdoing

2

๐Ÿ’ฅ Denial of Service

Mass-delete another user's emails, files, or data โ€” making their account unusable

3

๐Ÿ•ต๏ธ Cover Tracks

After stealing data via IDOR, delete the messages/logs that reference the stolen items

๐Ÿ”ฅ Try deleting message 3 (confidential Q4 revenue) or 6 (admin's password vault!)
๐Ÿ“ก Attempt to delete a message you don't own. What happens when evidence disappears?

๐Ÿงช 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.

Critical Chain Attack Account Takeover

๐Ÿ“‹ 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:

1

๐Ÿ“ง Read Email

IDOR on /users/1002 leaks bob@techcorp.com

2

๐Ÿ”„ Trigger Reset

POST /forgot-password with Bob's email

3

๐ŸŽซ Steal Token

IDOR on /users/1002/reset-token

4

๐Ÿ” Change Pass

POST /reset-password with stolen token

5

๐Ÿ’€ 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).

๐Ÿ“ก Click through each step above to watch the complete account takeover chain unfold!

๐Ÿ”ด 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

Defense

The 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 (Before)No Check
# โŒ 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 (After)Ownership Check
# โœ… 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

โŒ VulnerableNo Scope
// โŒ Returns ANY order
db.query('SELECT * FROM orders WHERE id = ?',
  [orderId]);
โœ… SecureUser Scoped
// โœ… 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

Practical

Essential Tools

๐Ÿ”ถ Burp Suite

Primary tool. Proxy โ†’ Repeater โ†’ Intruder โ†’ Comparer.

RepeaterIntruderComparer

๐Ÿ”‘ 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

1

๐Ÿ” Recon

Create 2+ accounts. Map endpoints. Note all ID parameters.

2

๐ŸŽฏ Identify

What object? What CRUD op? Where's the ID? What data?

3

๐Ÿ’ฅ Exploit

User A's session โ†’ User B's IDs. All HTTP methods.

4

โœ… Validate

Confirm other's data. Verify impact. Check all methods.

5

๐Ÿ“ 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=1002
echo -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

ResponseMeaningVerdict
200 + other's dataReturned unauthorized data๐Ÿ”ด IDOR!
200 + your dataServer rewrites ID๐ŸŸก Partial fix
403 ForbiddenAuthorization exists๐ŸŸข Enforced
404 Not FoundCould be authz๐ŸŸข May be secure
401 UnauthorizedAuth check only๐ŸŸก Retest with session
500 ErrorServer processing๐ŸŸก Investigate

๐Ÿ”Œ IDOR in APIs (REST, GraphQL, WebSocket)

API Security

BOLA โ€” 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

๐Ÿ—๏ธ MicroservicesPitfall
โœ…API Gateway โ†’ Auth
๐Ÿ’ฅAny service โ†’ IDOR!

Each microservice MUST validate authz independently.

โšก ServerlessPitfall
# โŒ 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

PIIHigh
SSN / National IDCritical
Financial dataCritical
Medical recordsCritical
CredentialsCritical

โœ๏ธ Integrity

Modify profileHigh
Change passwordCritical
Alter financialsCritical
Privilege escalationCritical

๐Ÿ—‘๏ธ Availability

Delete accountCritical
Delete user dataHigh
Lock out usersHigh
Mass destructionCritical

CVSS Scoring

ScenarioCVSSSeverity
Read-only profile (non-sensitive)4.3Medium
Read-only PII exposure6.5Medium
Read + Write IDOR8.1High
Full CRUD on any user's data8.8High
Account takeover chain9.8Critical
Unauthenticated IDOR9.1Critical

๐Ÿ’ฐ 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

Advanced

IDOR vulnerabilities become devastating when chained together or with other vulnerability classes:

1

๐Ÿ“ง Leak Email

Profile IDOR reveals victim's email

2

๐Ÿ”„ Reset

Trigger reset for victim's email

3

๐ŸŽซ Steal Token

IDOR on reset-token endpoint

4

๐Ÿ” New Pass

Use stolen token

5

๐Ÿ’€ Login

Full account control

CVSS 9.8 โ€” Demonstrated in Lab 10. Found in hundreds of real programs.

1

๐Ÿ” Find Admin

Enumerate users

2

๐Ÿ”‘ Get Keys

IDOR on admin's API keys

3

๐Ÿ‘‘ Admin

Use stolen keys

4

๐Ÿšช Backdoor

Persistent access

1

๐Ÿ‘ฅ Enum Users

Loop /users/1โ€ฆ100K

2

๐Ÿ“ Enum Docs

Get each user's docs

3

๐Ÿ“ฅ Download

IDOR to get all files

4

๐Ÿ’ฃ Breach

Complete exfiltration

Demonstrated in Lab 8. Millions of records in minutes.

1

๐Ÿ” Find Account

IDOR reveals victim's account #

2

๐Ÿ’ธ Transfer

Change source_account in API

3

๐Ÿ’ฐ Steal

Money moved to attacker

๐Ÿ“„ IDOR Report Template

Copy & Use

Professional 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
TermDefinition
IDORInsecure Direct Object Reference โ€” accessing objects without authorization checks
AuthNAuthentication โ€” verifying WHO the user is (identity)
AuthZAuthorization โ€” verifying WHAT the user can do (permissions)
BOLABroken Object Level Authorization โ€” API term for horizontal IDOR (#1 API vuln)
BFLABroken Function Level Authorization โ€” API term for vertical IDOR
HorizontalAccessing another user's resources at the same privilege level
VerticalAccessing resources belonging to a higher-privileged user (e.g., admin)
Indirect RefRandom per-user token mapped to real ID server-side
RLSRow-Level Security โ€” database-enforced access control
RBACRole-Based Access Control โ€” permissions by role
ABACAttribute-Based Access Control โ€” permissions by attributes
ATOAccount Takeover โ€” gaining full control of another user's account
Mass AssignmentAPI accepts fields user shouldn't control (e.g., role)
UUID v4Random UUID โ€” harder to guess but doesn't fix IDOR
AutorizeBurp Suite extension for automated authz bypass testing
CWE-639Authorization Bypass Through User-Controlled Key
CVSSCommon Vulnerability Scoring System (0.0โ€“10.0)
๐Ÿงช Labs ๐Ÿ›ก๏ธ Defense ๐Ÿ“– Book