Just because you have a key to the building, doesn't mean you can open every door.
IDOR occurs when an application provides direct access to objects based on user-supplied input (like a URL parameter), but fails to verify if the user requesting the object actually owns it or has permission to view it. Simply being logged in (Authenticated) does not mean you are Authorized.
Never trust the user_id passed by the client. Always check the requested object against the user_id stored securely in the server-side session or JWT.
# VULNERABLE: IDOR
@app.route("/api/invoices/<invoice_id>")
def get_invoice(invoice_id):
# DANGER: Fetches the invoice without checking who owns it!
# Anyone who guesses the invoice_id can download it.
invoice = db.query(Invoice).get(invoice_id)
return invoice.to_json()
# SECURE: Authorization Check
@app.route("/api/invoices/<invoice_id>")
@login_required
def get_invoice(invoice_id):
invoice = db.query(Invoice).get(invoice_id)
# Crucial step: Verify ownership!
if invoice.owner_id != current_user.id:
return "403 Forbidden", 403
return invoice.to_json()
/user/1004, they will easily guess that /user/1005 exists. Using UUIDs (/user/f47a...) makes guessing impossible, but UUIDs are NOT a replacement for authorization checks. A leaked UUID will still cause a breach if IDOR is present.<input type="hidden" name="user_id" value="5"> to value="1" using Browser DevTools to trick the server into updating the Admin's account instead of yours.A banking app allows users to view their statements at /statements?account=987654321. A user realizes that changing the URL to ?account=987654322 allows them to view a stranger's bank statement. Because the database uses sequential IDs, the attacker writes a simple script to loop from 000000000 to 999999999, downloading millions of financial records. This is a classic IDOR breach.
Does switching all database primary keys from auto-incrementing integers (1, 2, 3...) to UUIDs (e.g. 550e8400-e29b-41d4-a716-446655440000) fix an IDOR vulnerability?