Skip to content
intermediate Phase 7 · Web Vulnerabilities and the OWASP Top 10

Broken Access Control and IDOR

Find object-level authorization gaps that expose other users' records.

55m
0 problems
Topic Progress 0%

Authentication vs. Authorization and the IDOR Vulnerability

Broken Access Control: When the Server Forgets to Check Permissions

Broken access control is the #1 risk in the OWASP Top 10 2021. It occurs when a user can act outside their intended permissions — accessing other users' data, performing privileged actions, or viewing restricted pages. The root cause is almost always the same: the server authenticates the user (verifies identity) but fails to authorize the specific action (verify permission).

Authentication vs. Authorization

These two terms are frequently confused but serve fundamentally different purposes:

Authentication (AuthN) answers: "Who are you?" It verifies the user's identity through credentials (passwords, tokens, biometrics). After authentication, the server knows which user is making the request.

Authorization (AuthZ) answers: "Are you allowed to do this?" It verifies whether the authenticated user has permission to perform the requested action on the requested resource.

A system can have strong authentication and zero authorization. For example, a login page that correctly verifies usernames and passwords but then allows any logged-in user to access any other user's data by changing an ID in the URL.

What Is IDOR?

Insecure Direct Object Reference (IDOR) is the most common manifestation of broken access control. It occurs when an application exposes a reference to an internal object (database record, file, key) and the server fails to verify that the user is authorized to access that specific object.

Vulnerable code:

@app.route('/api/orders/<order_id>')
def get_order(order_id):
    user = get_current_user()  # Authenticated
    order = db.query(f"SELECT * FROM orders WHERE id = {order_id}")
    return jsonify(order)  # No authorization check!

An authenticated user can change order_id from 1001 (their own order) to 1002 (another user's order) and the server returns it.

Why IDOR Is So Prevalent

  1. Developers assume obscurity is security: "The IDs are sequential, but users won't guess other IDs." Attackers do guess — it is trivial.
  2. Client-side checks are trusted: A UI may hide admin buttons from non-admin users, but the server does not enforce the same restriction.
  3. Object-level checks are forgotten: Developers check authentication at the login endpoint but forget to check authorization at every object access endpoint.
  4. API endpoints are overlooked: REST APIs often expose object references without the same authorization logic as the web UI.

Real-World Impact

IDOR has been responsible for numerous high-profile breaches:

  • Facebook (2018): An IDOR vulnerability allowed attackers to access any user's phone number by manipulating an API parameter.
  • Equifax (2017): While not purely IDOR, the breach involved accessing records the attacker was not authorized to view.
  • Uber (2016): An IDOR flaw allowed drivers to view other drivers' earnings and personal information.

The Principle of Complete Mediation

The security principle that prevents IDOR is complete mediation: every access to every object must be checked against the access control policy. There are no exceptions, no "trusted" internal calls, and no assumptions about how the user arrived at the request.

Implementing Server-Side Authorization

Fixing IDOR with Object-Level Authorization Checks

The fix for IDOR is conceptually simple — check authorization on every object access — but requires discipline to implement consistently across an entire application.

The Authorization Check Pattern

Every endpoint that accesses a specific object must verify that the current user is authorized to access that object:

@app.route('/api/orders/<order_id>')
def get_order(order_id):
    user = get_current_user()
    order = db.query(f"SELECT * FROM orders WHERE id = {order_id}")
    
    # Authorization check — verify ownership
    if order.user_id != user.id:
        return jsonify({'error': 'Forbidden'}), 403
    
    return jsonify(order)

This single check transforms a vulnerable endpoint into a secure one. The key is that the check happens on the server, every time, for every object.

Role-Based Access Control (RBAC)

For applications with distinct user roles (user, moderator, admin), implement role-based checks:

@app.route('/api/admin/users/<user_id>/delete', methods=['DELETE'])
def delete_user(user_id):
    user = get_current_user()
    
    # Role check
    if user.role != 'admin':
        return jsonify({'error': 'Admin access required'}), 403
    
    # Proceed with deletion
    db.delete_user(user_id)
    return jsonify({'status': 'deleted'})

Attribute-Based Access Control (ABAC)

For more complex scenarios, attribute-based access control evaluates multiple attributes (user role, resource ownership, time of day, location, etc.):

def can_access_order(user, order):
    # Owner can always access
    if order.user_id == user.id:
        return True
    # Support team can access during business hours
    if user.role == 'support' and is_business_hours():
        return True
    # Admin can always access
    if user.role == 'admin':
        return True
    return False

Why Unpredictable IDs Are Not Enough

A common misconception is that using UUIDs or random IDs instead of sequential integers prevents IDOR. It does not:

  1. IDs leak through other channels: API responses, referrer headers, browser history, and logs all expose object IDs.
  2. IDs can be brute-forced: A UUIDv1 contains timestamp information. Even UUIDv4 can be guessed if the attacker has access to any other user's ID (they can infer the ID space).
  3. Security through obscurity fails: Relying on the secrecy of an identifier is not a security control. The server must enforce authorization regardless of how the ID was obtained.

Unpredictable IDs are a useful defense-in-depth measure (they make enumeration harder), but they must never replace authorization checks.

Testing for IDOR

To test for IDOR, follow this methodology:

  1. Create two accounts: Account A (low privilege) and Account B (different user or higher privilege).
  2. Identify object references: Note every ID in URLs, request parameters, and API responses.
  3. Swap IDs: Make requests to Account A's endpoint but replace Account A's object IDs with Account B's object IDs.
  4. Verify the response: If the server returns Account B's data, the vulnerability exists.
# Account A requests their own order
curl -H "Cookie: session=A" https://api.example.com/orders/1001
# Returns order 1001 (expected)

# Account A requests Account B's order
curl -H "Cookie: session=A" https://api.example.com/orders/1002
# If this returns order 1002's data, IDOR exists

Practice: Find the IDOR

Review this code and identify the vulnerability:

@app.route('/api/documents/<doc_id>')
def download_document(doc_id):
    user = get_current_user()
    document = Document.query.get(doc_id)
    
    if not document:
        return jsonify({'error': 'Not found'}), 404
    
    return send_file(document.file_path)

The vulnerability: there is no check that document.owner_id == user.id. Any authenticated user can download any document by guessing or enumerating document IDs. The fix is to add an ownership check before serving the file.

Practice Problems

0 / 2 solved
Fix IDOR in a File Download Endpoint

The following endpoint allows users to download their uploaded files. Identify the IDOR vulnerability and fix it. ```python @app.route('/files/<file_id>') def download(file_id): user = get_current_user() file = File.query.get(file_id) if not file: abort(404) return send_file(file.path) ```

Solution
The endpoint does not verify that the file belongs to the current user. Any authenticated user can download any file by changing the file_id. Fix:

```python
@app.route('/files/<file_id>')
def download(file_id):
    user = get_current_user()
    file = File.query.get(file_id)
    if not file:
        abort(404)
    if file.owner_id != user.id:
        abort(403)
    return send_file(file.path)
```

The ownership check (`file.owner_id != user.id`) ensures users can only download their own files.
Design an Authorization System

Design an authorization system for a multi-tenant SaaS application where: - Users belong to organizations - Users have roles within their organization (owner, admin, member) - Resources (projects, documents) belong to organizations - Owners and admins can manage all resources in their organization - Members can only access resources explicitly shared with them

Solution
Implement a middleware-based authorization system:

```python
def authorize(resource_type, resource_id, required_permission):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user = get_current_user()
            resource = get_resource(resource_type, resource_id)
            
            # Check organization membership
            if resource.org_id != user.org_id:
                abort(403)
            
            # Check role-based permissions
            if user.role in ('owner', 'admin'):
                return f(*args, **kwargs)
            
            # Check explicit sharing for members
            if has_explicit_access(user.id, resource_type, resource_id):
                return f(*args, **kwargs)
            
            abort(403)
        return decorated_function
    return decorator

@app.route('/api/projects/<project_id>')
@authorize('project', 'project_id', 'read')
def get_project(project_id):
    # ...
```

Key principles: verify organization membership first, then check role-based permissions, and finally check explicit sharing for non-admin roles.

Quiz

1. What is the difference between authentication and authorization?

Question 1 options

2. An authenticated user changes the `orderId` parameter in a URL from `orderId=1001` to `orderId=1002` and sees another user's order. What is this vulnerability called?

Question 2 options

3. Why are unpredictable IDs (UUIDs) not a sufficient defense against IDOR?

Question 3 options

4. Which security principle requires that every access to every object is checked against the access control policy?

Question 4 options

5. A web application hides admin buttons from non-admin users in the UI, but the server does not enforce the same restriction. An attacker can directly call the admin API endpoint. What type of vulnerability is this?

Question 5 options

Flashcards

Question

What is the difference between authentication and authorization?

Answer

Authentication verifies identity ('Who are you?'). Authorization verifies permissions ('Are you allowed to do this?'). A system can have strong authentication but zero authorization.

Question

What is IDOR?

Answer

Insecure Direct Object Reference — a vulnerability where an application exposes a reference to an internal object and fails to verify that the user is authorized to access that specific object. The most common manifestation of broken access control.

Question

Why are unpredictable IDs (UUIDs) not a sufficient defense against IDOR?

Answer

IDs leak through API responses, headers, history, and logs. Security through obscurity is not a security control. The server must enforce authorization regardless of how the ID was obtained.

Question

What is the Complete Mediation principle?

Answer

Every access to every object must be checked against the access control policy. No exceptions, no trusted internal calls, no assumptions about how the user arrived at the request.

Question

Why are client-side authorization checks insufficient?

Answer

Client-side checks (hiding buttons, disabling UI elements) can be bypassed by directly calling the API endpoint. Authorization must always be enforced on the server, where the attacker cannot manipulate the request.

Revision Notes

Key Takeaways

  • 1. Broken access control is the #1 risk in OWASP Top 10 2021 — the server authenticates but fails to authorize
  • 2. IDOR occurs when changing an identifier in a request returns another user's data
  • 3. Authorization must be enforced server-side for every object reference, not just at login
  • 4. Unpredictable IDs (UUIDs) make enumeration harder but do not replace authorization checks
  • 5. The Complete Mediation principle requires checking every access against the access control policy

Interview Tips

  • • Explain the difference between authentication and authorization with a concrete example of a system that has one but not the other
  • • Walk through how you would test for IDOR using two accounts and ID swapping
  • • Discuss why unpredictable IDs are not a substitute for authorization — mention ID leakage through multiple channels
  • • Connect broken access control to real-world breaches (Facebook, Uber) to show practical understanding

Cheat Sheet

Broken Access Control and IDOR Cheat Sheet

Phase: Web Vulnerabilities and the OWASP Top 10 | Level: intermediate

Key Concepts:

  • Authentication = identity verification; Authorization = permission verification
  • IDOR = accessing other users' objects by manipulating references
  • Complete Mediation = check authorization on every object access

Common IDOR Patterns:

  • URL parameters: /api/orders/<order_id> — swap IDs to access other users' data
  • API endpoints: /api/users/<user_id>/profile — no ownership check
  • File downloads: /files/<file_id> — no ownership check

Defense:

  • Server-side authorization check on every object access
  • Role-Based Access Control (RBAC) for role-based permissions
  • Attribute-Based Access Control (ABAC) for complex scenarios
  • Never rely on client-side checks or ID unpredictability

Testing Method:

  1. Create two accounts (A and B)
  2. Note all object IDs in requests
  3. Swap Account A's IDs with Account B's IDs
  4. If the server returns Account B's data, IDOR exists

Key points:

  • IDOR occurs when changing an identifier in a request returns another user's data
  • Authorization must be enforced server-side for every object reference, not just at login
  • Sequential IDs make guessing trivial, but unpredictable IDs do not replace authorization checks
  • Client-side checks (hiding UI elements) are not a security control

Tags: access-control, web-security

Continue Learning

Before You Start

  • No prerequisites listed.