We now tackle the number one risk in the OWASP Top 10: Broken Access Control. This category encompasses vulnerabilities where users can perform actions or access data beyond their intended permissions. Insecure Direct Object Reference (IDOR) is the most common and impactful example.
Access control enforces policies such that users cannot act outside their intended permissions. When these policies fail, users can access other users' data, perform administrative actions, or view resources they should never be able to see.
Broken access control is the most common web vulnerability found in real-world bug bounty programs. It is also one of the most impactful โ a single access control flaw can expose millions of user records or grant full administrative access.
IDOR occurs when an application exposes a reference to an internal object (like a database ID, filename, or account number) in a URL or parameter, and does not verify that the authenticated user is authorized to access that specific object.
# Example: Viewing your own account
GET /api/account/12345
Authorization: Bearer <your_token>
# Response: Your account data โ this is correct
# IDOR Attack: Changing the ID to another user's
GET /api/account/12346
Authorization: Bearer <your_token>
# Response: Another user's account data โ VULNERABILITY!
# The server authenticated you but did not check if you
# were authorized to view account 12346The critical distinction here is between authentication (proving who you are) and authorization (verifying what you are allowed to do). IDOR vulnerabilities exist because applications authenticate users but fail to authorize their access to specific resources.
Access control failures fall into two categories based on the direction of the privilege escalation.
| Type | Description | Example |
|---|---|---|
| Horizontal | Accessing another user's data at the same privilege level | User A views User B's bank account by changing the account ID |
| Vertical | Gaining privileges of a higher-level user | Regular user accesses admin functions by calling admin API endpoints |
| Context-Dependent | Accessing data in an unauthorized context | A teacher views another school's student records by changing a school ID parameter |
The most effective way to find IDOR vulnerabilities is to create two accounts and systematically test whether one account can access the other's resources. Burp Suite's Repeater and Comparer tools are essential for this.
# Step 1: Log in as User A and capture a request
GET /api/user/profile?user_id=1001
Cookie: session=userA_session
Response:
{"user_id": 1001, "name": "Alice", "email": "alice@example.com",
"ssn": "123-45-6789", "balance": 5000}
# Step 2: Log in as User B and capture the same request
GET /api/user/profile?user_id=1002
Cookie: session=userB_session
Response:
{"user_id": 1002, "name": "Bob", "email": "bob@example.com",
"ssn": "987-65-4321", "balance": 3200}
# Step 3: As User B, try to access User A's profile
GET /api/user/profile?user_id=1001
Cookie: session=userB_session
# If this returns Alice's data โ IDOR vulnerability confirmed!๐ก Use Burp Suite's 'Autorepeater' extension to automatically replace parameters across many requests. This helps you quickly test multiple IDOR scenarios without manually editing each request.
Forced browsing occurs when an attacker directly accesses pages or endpoints that are not linked from the application but are accessible if you know the URL. This is a vertical privilege escalation when it involves admin functions.
# A regular user might discover admin endpoints through:
# 1. JavaScript source code analysis
# 2. API documentation or Swagger UI left accessible
# 3. Common path enumeration
# Common admin paths to test:
/admin
/admin/dashboard
/admin/users
/admin/config
/api/admin
/api/v1/admin/users
/manage
/console
/phpmyadmin
# If any of these are accessible without admin privileges,
# you have found a function-level access control flawIDOR is even more dangerous when it affects state-changing operations (POST, PUT, DELETE) rather than just data retrieval. An attacker might not only read another user's data but modify or delete it.
# Reading another user's data (GET) โ High impact
GET /api/orders/5678 # Another user's order
# Modifying another user's data (POST/PUT) โ Critical impact
PUT /api/user/profile
{"user_id": 1001, "email": "attacker@evil.com"}
# Changes Alice's email to the attacker's email
# Deleting another user's data (DELETE) โ Critical impact
DELETE /api/documents/9012 # Another user's document
# Permanently deletes someone else's data
# Transferring funds (POST) โ Maximum impact
POST /api/transfer
{"from_account": 12345, "to_account": 99999, "amount": 10000}
# Steals money from another user's accountโ ๏ธ State-changing IDOR vulnerabilities can cause irreversible damage. In your lab, always verify you are working with test data. In real assessments, document the vulnerability but do not execute destructive operations without explicit permission.
Some applications try to prevent IDOR by using indirect references โ mapping random tokens to internal IDs. However, these can still be vulnerable if the mapping is predictable or if the tokens leak.
# Instead of sequential IDs, the app uses random tokens:
GET /api/documents/abc123def456
# This is better than /api/documents/123, but still vulnerable if:
# 1. The token is shared or leaked (in URLs, logs, Referer headers)
# 2. The token space is small enough to enumerate
# 3. The token is predictable (not cryptographically random)
# Test: Can you enumerate valid tokens?
GET /api/documents/aaaaaa โ 404 Not Found
GET /api/documents/aaa123 โ 200 OK (someone's document!)
# If the token space is small, brute-forcing is practicalSometimes access control is implemented at the application layer but can be bypassed through path manipulation in the URL.
# Normal access (blocked by access control)
GET /admin/users โ 403 Forbidden
# Bypass attempts:
GET /admin/users/ โ 200 OK (trailing slash bypass)
GET /ADMIN/users โ 200 OK (case sensitivity bypass)
GET /admin/../admin/users โ 200 OK (path normalization bypass)
GET /admin%2fusers โ 200 OK (URL encoding bypass)
GET /api/v1/admin/users โ 200 OK (API version bypass)Proper access control requires a defense-in-depth approach with checks at multiple layers of the application.
# Python (Flask) โ Proper access control pattern
from flask import g, abort
from functools import wraps
def require_owner(resource_type):
def decorator(f):
@wraps(f)
def decorated_function(resource_id, *args, **kwargs):
# Fetch the resource
resource = Resource.query.get_or_404(resource_id)
# Verify ownership โ THIS CHECK IS CRITICAL
if resource.owner_id != g.current_user.id:
# Log the access attempt
app.logger.warning(
f"Access denied: User {g.current_user.id} "
f"attempted to access {resource_type} {resource_id} "
f"owned by {resource.owner_id}"
)
abort(403)
return f(resource_id, *args, **kwargs)
return decorated_function
return decorator
@app.route('/api/documents/<uuid:doc_id>')
@login_required
@require_owner('document')
def get_document(doc_id):
document = Resource.query.get_or_404(doc_id)
return jsonify(document.to_dict())The most important principle in access control: verify authorization on every single request, for every single resource. A single missing check is all an attacker needs. Build access control as a centralized, reusable component โ not as scattered if-statements throughout your codebase.
Verify exercises to earn โ 180 XP and unlock next lab level.