Back to Blog
SecurityJanuary 11, 20269 min read

Broken Access Control (OWASP A01): How to Detect and Fix It at Scale

Broken Access Control has been the #1 OWASP vulnerability since 2021, found in 94% of tested applications. This post covers detection strategies, common patterns, automated testing approaches, and a remediation framework.

Broken Access Control is the most prevalent application vulnerability in existence. OWASP found it in 94% of applications tested for the 2025 list. It has held the #1 position since 2021. Yet it remains one of the hardest to automatically detect at scale — because the definition of "correct" access requires understanding business logic that no generic tool possesses.

This post covers what the vulnerability class actually includes, how to detect it programmatically, and how to remediate it systematically.

What Broken Access Control Actually Covers

The category is broader than most developers realize. It includes:

Insecure Direct Object Reference (IDOR) The most common variant. Accessing resources by changing a predictable identifier:

GET /api/invoices/1047  → returns YOUR invoice
GET /api/invoices/1046  → returns ANOTHER USER's invoice (IDOR)

Forced Browsing Navigating to URLs that should require elevated privilege but don't enforce it server-side:

GET /admin/users        → should require admin role
GET /reports/all        → should require manager role

Method-Based Access Control Bypass An endpoint may block POST but not PUT, or block DELETE but not PATCH:

DELETE /api/users/42    → 403 Forbidden
PUT /api/users/42       → 200 OK (same effect, no check)

Privilege Escalation A standard user accessing or modifying resources that belong to admin or other roles:

PATCH /api/users/me {"role": "admin"}  → should fail, sometimes doesn't

JWT Claim Manipulation Modifying JWT payload to escalate privileges when signature validation is absent or weak:

{ "userId": 42, "role": "user" }  →  { "userId": 42, "role": "admin" }

CORS Misconfiguration An overly permissive CORS policy allows a malicious origin to make credentialed requests:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true  ← critical misconfiguration

Why Automated Detection Is Partial

The reason Broken Access Control remains prevalent despite extensive tooling: authorization rules are business logic, not syntax. A scanner can inject ' and detect SQL injection because the injection signal (database error) is universal. It cannot know that /api/invoices/1046 belongs to a different user unless it understands user identity and resource ownership.

What automation CAN detect:

TestAutomated?
IDOR via ID enumeration (sequential IDs)Yes
IDOR via UUID prediction (UUIDs are hard to predict)No
Forced browsing (known path list)Partially
Method bypass (test all HTTP methods on each endpoint)Yes
JWT algorithm confusion (alg:none attack)Yes
CORS misconfiguration detectionYes
Horizontal privilege escalation (User A accessing User B's data)Partial (requires two accounts)
Vertical privilege escalation (user accessing admin functions)Partial

The partial cases require two authenticated sessions — a standard user and an elevated user — so the scanner can test whether the standard user can access the elevated user's resources. PentestCheck supports multi-session authentication configuration for exactly this purpose.

Testing Strategy: The Two-Account Method

The most effective automated approach for IDOR:

  1. Configure scan with two accounts: user_a (standard) and user_b (standard)
  2. Crawl the application as user_a, collecting all resource URLs (documents, invoices, orders, profiles)
  3. Replay each resource request as user_b
  4. Flag any 200-response to a resource that belongs to user_a's session

This catches horizontal IDOR reliably. For vertical escalation:

  1. Configure with user_standard and user_admin
  2. Crawl as user_admin, collecting admin-only URLs
  3. Replay admin-only URLs as user_standard
  4. Flag any 200-response that should require admin

Common Patterns and Root Causes

Pattern 1: Object-Level Authorization Missing at Service Layer

The controller checks authentication (is the user logged in?) but not authorization (does this user own this object?):

# Vulnerable
@app.route('/api/invoices/<int:invoice_id>')
@login_required  # checks authentication only
def get_invoice(invoice_id):
    return Invoice.get(invoice_id)  # no owner check

# Secure
@app.route('/api/invoices/<int:invoice_id>')
@login_required
def get_invoice(invoice_id):
    invoice = Invoice.get(invoice_id)
    if invoice.owner_id != current_user.id:
        abort(403)
    return invoice

Pattern 2: Frontend-Only Access Control

The UI hides admin navigation from non-admin users. The API endpoint itself is unprotected because "users can't see the link."

Security assumption: if users can't navigate to it, they can't call it. Reality: attackers don't use your UI.

Pattern 3: Sequential Identifiers

UUIDs (random, 128-bit) are not practically enumerable. Auto-increment integers are. If your API uses integer IDs for resources, IDOR is trivial to test:

/api/documents/1000
/api/documents/1001
/api/documents/1002

The fix is using non-sequential identifiers (UUIDs or opaque tokens), but the real fix is always server-side authorization. Obscuring IDs is defense-in-depth, not a security control.

Remediation Framework

Short-term (critical, same sprint):

Medium-term (within 30 days):

Long-term (next quarter):

Measuring Progress

Track remediation using:

A mature access control program shows IDOR findings discovered in CI/CD (before production) rather than in production scans. That shift means your authorization model is being tested before it's exploitable.


PentestCheck tests for IDOR using multi-account session replay across your full application scope. Broken Access Control findings include the exact request that demonstrates exploitability.

Scan your attack surface today

Free plan includes 3 targets and 10 scans/month. No credit card required.

Start Free Scan