Post

IDOR: The Coat Check Problem in Web Apps

Understanding Insecure Direct Object References (IDOR) and how to defend against them

IDOR: The Coat Check Problem in Web Apps

Introduction: The Coat Check Problem

Imagine you go to a club and check your coat. The attendant gives you a ticket with the number #100. When you leave, you hand back ticket #100, and they give you Coat #100.

But what if you used a pen to change the number on your ticket to #101? If the attendant simply looks at the number and hands you Coat #101 without checking if you are the person who actually owns that coat, the system has an IDOR vulnerability.

In web apps, database records (Profiles, Invoices, Messages) are often retrieved using simple numbers (IDs). If the server doesn’t check ownership, you can access anyone’s data just by changing the number.

The Mechanism: How it Looks

You are logged into a shopping site and you click “View My Receipt.” The URL in your browser looks like this:

https://shop.com/receipts?id=5000

This tells the server: “Go to the database and show me the file at Row 5000.”

The Attack: You, the curious hacker, simply go to the URL bar and change 5000 to 4999.

https://shop.com/receipts?id=4999

The Result:

  • Secure App: “Error 403: Forbidden. You do not own this receipt.”
  • Vulnerable App (IDOR): The server blindly follows instructions and shows you the receipt for Order #4999, which belongs to a complete stranger. It might contain their address, credit card last-4, and full name.

Variations of IDOR

It’s not always just a number in the URL. IDORs can hide in many places.

1. The “Predictable ID” (The Classic)

  • Input: user_id=100
  • Attack: Change to user_id=101.
  • Why it works: Developers use “Auto-Incrementing” IDs (1, 2, 3, 4…). If you know your ID is 100, you know for a fact that 99 and 101 exist.

2. The “Blind” IDOR

Sometimes changing the ID doesn’t show you data on the screen, but it performs an action.

  • Scenario: A “Change Password” form.
  • Request: POST /change_password
  • Body: { "user_id": 100, "new_password": "hacked" }
  • Attack: Change to { "user_id": 101, ... }
  • Result: You just changed someone else’s password without ever seeing their account.

3. Static Filenames

  • URL: site.com/uploads/resume_john.pdf
  • Attack: Guess site.com/uploads/resume_jane.pdf
  • Result: If the server doesn’t check if you are a recruiter, you can download all resumes.

Why is this “Logic” and not “Injection”?

In SQL Injection, you are tricking the database into running a command it shouldn’t (OR 1=1). In IDOR, the database is doing exactly what it was asked to do: “Fetch Record #101.”

The failure isn’t in the Code (syntax); the failure is in the Business Logic (permissions). The developer forgot to ask: “Does the person requesting #101 actually own #101?”

Tools for Hunting

Automated scanners (like Nessus or basic Burp scans) are terrible at finding IDORs. They don’t know that “User A” isn’t supposed to see “Data B.” You have to find these manually.

1. Burp Suite (Autorize Extension)

The best tool for this.

  1. Login as User A.
  2. Login as User B.
  3. Browse the site as User A.
  4. The “Autorize” extension takes every request User A makes, swaps the session cookies to User B, and replays the request.
  5. If the server responds “200 OK” for User B trying to see User A’s data, Autorize highlights it in RED. BINGO.

Defense: Access Control Lists (ACLs)

You cannot “sanitize” your way out of IDOR. You must implement Access Controls.

Vulnerable Logic:

1
2
3
def get_receipt(receipt_id):
    return db.query("SELECT * FROM receipts WHERE id = ?", receipt_id)

Secure Logic:

1
2
3
4
5
6
7
8
9
def get_receipt(receipt_id, current_user):
    receipt = db.query("SELECT * FROM receipts WHERE id = ?", receipt_id)
    
    # THE CRITICAL CHECK
    if receipt.owner_id != current_user.id:
        return error("403 Forbidden")
        
    return receipt

Summary for the Exam

  • Acronym: IDOR (Insecure Direct Object Reference).
  • Root Cause: Missing Function Level Access Control.
  • Target: URL Parameters (id=), Hidden Form Fields, API calls.
  • Impact: Information Disclosure (High) or Account Takeover (Critical).
  • Remediation: Always verify that current_user owns requested_object before returning data. Use GUIDs (Random long strings) instead of sequential numbers (1, 2, 3) to make guessing harder (though this is “Security by Obscurity” and not a true fix).

This topic teaches you that hacking isn’t always about complex code , sometimes it’s just about trying the next number in line.

This post is licensed under CC BY 4.0 by the author.