Comprehensive Authorization & Access Control Guide

A practitioner’s reference for Broken Access Control (OWASP A01) — the models, bug classes, bypass techniques, real-world chains, and detection/prevention patterns that matter in modern web and API testing. Compiled from 33 research sources.


Table of Contents

  1. Fundamentals
  2. Authorization Models
  3. Attack Surface & Discovery
  4. Vertical Privilege Escalation
  5. Horizontal Privilege Escalation & IDOR/BOLA
  6. Broken Function Level Authorization
  7. URL, Method & Header Bypasses
  8. Parameter & Keyword Bypasses
  9. JWT & Token Claim Manipulation
  10. OAuth Scope & Redirect Abuse
  11. Multi-Tenant & Session Isolation Failures
  12. Cloud & AI Agent Authorization
  13. Real-World CVEs and Chains
  14. Tools & Automation
  15. Detection & Prevention
  16. Testing Methodology
  17. Quick Reference

1. Fundamentals

Access control is the application of constraints on who or what is authorized to perform actions or access resources. It sits on top of two related primitives:

LayerQuestionFailure mode
Authentication (AuthN)Who are you?Credential stuffing, SQLi login bypass, weak reset tokens
Session managementWhich requests belong to the same caller?Token fixation, weak “remember me” tokens
Authorization (AuthZ)Are you allowed to do this?BOLA, BFLA, privilege escalation

Broken access control has been the #1 OWASP Top 10 vulnerability since 2021 and remains at the top in the 2025/2026 lists. It appears in roughly 94% of tested applications and is responsible for more paid bug bounty reports than any other category on HackerOne.

Why it dominates:

  • Authorization is business logic — no framework, linter, or scanner can infer the correct policy from code alone.
  • Modern apps have many edges (REST, GraphQL, webhooks, background workers, mobile APIs, admin panels) and each edge must independently enforce the same rules.
  • Every new feature introduces a new object type, new role, new endpoint — and new chances to forget a check.
  • Developers conflate hiding a button with enforcing a control.

Three classes of broken access control:

ClassDescriptionTypical impact
VerticalLow-priv user reaches admin-only functionalityFull site takeover, user deletion, config change
HorizontalUser A reaches User B’s resourcesPII disclosure, mass scraping, ATO via reset
Context-dependentUser skips or replays a required stepBuying without paying, approving own request

Impact spectrum: Read one extra record → Enumerate entire user base → Modify other tenants’ data → Escalate to admin → Pivot to cloud/internal systems → Full platform compromise.


2. Authorization Models

Understanding the intended model is a prerequisite to spotting where it breaks.

2.1 DAC — Discretionary Access Control

Resource owners decide who can access what. Think Unix file permissions, Google Docs “share with…”, GitHub repo collaborators. Common failures:

  • Ownership transfers that don’t revoke old permissions.
  • “Public” toggles that silently expose historical objects.
  • Share-link tokens that are low-entropy or never expire.

2.2 MAC — Mandatory Access Control

A central policy dictates access — the user cannot override it. Classic in classified-data systems, SELinux, and regulated workloads. Failures tend to be configuration mistakes (wrong label, missing category) rather than logic bugs.

2.3 RBAC — Role-Based Access Control

Permissions are bundled into roles (viewer, editor, admin, billing_admin) and users are granted roles. The dominant model in SaaS. Failure modes:

  • Role sprawl — dozens of roles that overlap or have undocumented superpowers.
  • Implicit admin — a non-admin role that inherits a dangerous permission (e.g. support can impersonate users).
  • Client-side role checks — the UI hides the button but the API accepts the call.
  • Role escalation endpoints — a PATCH /users/:id that accepts {role: "admin"} from any authenticated user.

2.4 ABAC — Attribute-Based Access Control

Decisions are computed from attributes of the subject, resource, action, and environment (if user.department == resource.department and time < 18:00). Flexible, but the policy surface is huge. Failure modes:

  • Missing attributes default to allow.
  • Attribute sources are user-controllable (e.g. a JWT claim the user can edit).
  • Policy engines (OPA, Cedar) evaluated against stale or cached data.

2.5 ReBAC — Relationship-Based Access Control

Authorization is derived from a graph of relationships (user -> member_of -> team -> owns -> document). Examples: Google Zanzibar, SpiceDB, OpenFGA. Failure modes:

  • Relationship writes not atomic with resource writes (TOCTOU).
  • Indirect paths that the developer didn’t model (a guest in a parent folder inherits access to a sensitive child).
  • Tuple deletion races — revoke arrives after the read.

2.6 Model comparison

ModelGranularityBest forPrimary risk
DACPer-resourceCollaboration toolsOwnership drift
MACPer-labelRegulated dataMisconfiguration
RBACPer-roleEnterprise SaaSRole sprawl, missing checks
ABACPer-attributeDynamic/contextualPolicy gaps, attribute trust
ReBACPer-relationshipGraph-shaped appsIndirect paths, write races

Real apps usually mix these — for example, RBAC for coarse roles plus ABAC for row-level filters plus ReBAC for sharing.


3. Attack Surface & Discovery

3.1 Where broken access control hides

From the Intigriti BugQuest series, seven recurring hotspots:

  1. New features shipped without a full authorization pass.
  2. Premium/paid features gated only in the UI or billing layer.
  3. Legacy code — “we wrote this in 2016, nobody remembers how.”
  4. Third-party integrations — webhooks, OAuth apps, SSO bridges.
  5. APIs — especially mobile and partner APIs not surfaced in the web UI.
  6. Complex permission systems — custom roles, ACL-per-object, impersonation.
  7. File operations — upload, download, export, import.

3.2 Finding hidden endpoints

SourceWhat it reveals
robots.txt, sitemap.xml, security.txtAdmin paths the author tried to hide
.well-known/OIDC, OAuth, assetlinks, apple-app-site-association
package.json, swagger.json, openapi.jsonFull route inventory
.git/config, .env, backup filesCredentials, internal hostnames
JavaScript bundlesRoute strings, role flags (isAdmin), API base URLs
GraphQL introspectionEvery query/mutation/field
Mobile app binaries (APK/IPA)Hidden admin APIs, debug endpoints
Wayback Machine, CT logs, GitHub searchDeleted or internal endpoints still live
Postman collections, PastebinLeaked internal API specs

3.3 Parameters that scream “test me”

  • Path: /api/user/1234, /invoice/550e8400-...
  • Query: ?id=, ?user=, ?org=, ?tenant=, ?role=, ?admin=true
  • Body: {"user_id":321,"order_id":987,"role":"viewer"}
  • Headers/cookies: X-Client-ID, X-Tenant, X-User-Role

Prefer endpoints that read or modify (GET, PUT, PATCH, DELETE) over pure writes, and note whenever identifiers look sequential or predictable — if your ID is 64185742, then 64185741 almost certainly exists.


4. Vertical Privilege Escalation

Vertical escalation = a user reaches functionality reserved for a higher privilege tier.

4.1 Unprotected functionality

The simplest and still most common flaw: admin pages exist at predictable URLs with no server-side role check.

https://target.com/admin
https://target.com/admin.php
https://target.com/administrator
https://target.com/dashboard/admin

Even when the path is “secret” (/administrator-panel-yb556), the URL is often leaked:

<script>
  var isAdmin = false;
  if (isAdmin) {
    var a = document.createElement('a');
    a.href = 'https://target.com/administrator-panel-yb556';
  }
</script>

The script — and the URL — is served to every user regardless of role. “Security by obscurity” is not access control.

4.2 Parameter-based role flags

Some apps store the user’s role in a client-controllable location and trust it:

GET /login/home.jsp?admin=true
GET /dashboard?role=1
POST /account   {"user_type": "admin"}
Cookie: role=user        ->  Cookie: role=admin

If the server reads the role from the request instead of the session, toggling the value escalates.

4.3 Hidden form fields and mass assignment

Registration or profile endpoints that accept an unexpected field:

POST /register
{
  "email": "attacker@example.com",
  "password": "...",
  "role": "admin",        <- accepted silently
  "is_superuser": true,
  "email_verified": true
}

Rails, Django, Laravel, and Spring are especially prone to this when the model is bound directly from request JSON without an allow-list.

4.4 Platform-layer rule bypasses

If the access control is enforced by the web server or API gateway (“deny POST /admin/* to non-admins”) the app may still be vulnerable when:

  • The method is changed (GET/HEAD/OPTIONS/TRACE) and the backend accepts it.
  • URL normalization differs between gateway and app (/admin/./deleteUser, /Admin/DeleteUser, /admin/deleteUser;x=y).
  • A header rewrites the effective path (see §7).

5. Horizontal Privilege Escalation & IDOR/BOLA

5.1 IDOR vs BOLA

The same bug by two names:

  • IDOR (Insecure Direct Object Reference) — the classic web-era term.
  • BOLA (Broken Object Level Authorization) — OWASP API Security Top 10 #1, identical concept reframed for APIs.

Both occur when the application uses a user-supplied identifier to look up an object without verifying the caller owns or is entitled to it.

5.2 The minimal test

With a low-privilege authenticated session, change only the ID — keep the same cookie/token — and see if you still get the target object.

PUT /api/lead/cem-xhr HTTP/1.1
Cookie: auth=<your session>
Content-Type: application/json

{"lead_id": 64185741}     <- someone else's lead

Absence of an authorization error is the tell.

5.3 Enumeration patterns

Sequential integer IDs are the jackpot — sweeping them gives full-table disclosure:

for id in $(seq 1 100000); do
  curl -s -H "Cookie: $COOKIE" "https://target/api/object/$id" \
    | jq -e '.email' && echo "hit $id"
done

With ffuf and an authenticated cookie:

ffuf -u 'https://target/download.php?id=FUZZ' \
     -H 'Cookie: PHPSESSID=<session>' \
     -w <(seq 0 100000) \
     -fr 'File Not Found'

5.4 Predictable “unguessable” identifiers

A GUID/UUID isn’t automatically safe:

  • UUIDv1 leaks MAC address and timestamp — grindable.
  • Base64-encoded integers (Mg== = 2) are just integers in disguise.
  • Hash-encoded IDs with a known salt (md5(user_id + "secret")) fall to rainbow tables or source disclosure.
  • Hex-encoded short strings — the 2026 Carlsberg wristband QR case encoded short IDs like C-285-100 as ASCII-hex 432d3238352d313030; the 26M keyspace was exhausted in about 52 hours at 139 req/s, exposing photos, videos, and names of ~500 visitors in the first sample run.

Lesson: encoding is not entropy. Any ID that functions as a bearer token must be a high-entropy random secret.

5.5 Combinatorial / multi-ID IDORs

Some endpoints take two or more IDs:

GET /chat.php?chat_users[0]=42&chat_users[1]=7

If the check is “are you logged in” and not “are you one of chat_users[*]”, fuzz both positions and keep only unique unordered pairs:

ffuf -u 'https://target/chat.php?chat_users[0]=A&chat_users[1]=B' \
     -w <(seq 1 1000):A -w <(seq 1 1000):B \
     -H 'Cookie: PHPSESSID=<session>'

5.6 Error-response oracles

Subtle differences in responses create enumeration oracles:

ResponseMeaning
404 “User not found”The username doesn’t exist
403 “Forbidden”The username exists but you can’t access it
200 with empty bodyExists, you have partial access
500 stack traceExists, internal state was touched

Length, timing, and status-code deltas are all oracles — even when the body looks identical.

5.7 Redirect leaks

An app that detects unauthorized access and redirects to /login may still include the target’s sensitive data in the redirecting response body or Location header fragment. Always inspect the raw 302 before following it.

5.8 Horizontal → vertical pivot

Horizontal reads on the right victim become vertical escalation. If the IDOR lets you view or change arbitrary users, target an admin account: read their password reset token, change their email, or scrape their session cookie from an exported audit log.


6. Broken Function Level Authorization

OWASP API Security Top 10 #5 — BFLA is the function-level sibling of BOLA.

Where BOLA is “I can read object X I shouldn’t,” BFLA is “I can call function F I shouldn’t.” The function itself is what’s privileged.

Typical shape:

# Low-priv user
POST /api/admin/users/delete  {"id": 42}  -> 200 OK
POST /api/admin/config        {"maintenance": true} -> 200 OK

Watch for:

  • Role naming conventions in paths (/admin/…, /internal/…, /mgmt/…, /debug/…).
  • HTTP method toggles — GET /users works, POST /users creates a user, DELETE /users/1 deletes — and only GET is checked.
  • “Bulk” variants — /users/bulk-update may skip the per-object authorization that /users/:id enforces.
  • GraphQL mutations — resolvers frequently check authentication but not authorization.

CVE-2025-67274 (aangine 2025.2) is textbook BFLA: low-privileged authenticated users could send HTTP requests directly to admin-restricted backend API endpoints (template management, integration jobs, logs, portfolio data) because the JWT’s role and scope claims were never validated at the API layer. The UI correctly hid the admin panel; the API didn’t.


7. URL, Method & Header Bypasses

When the access control is enforced by a reverse proxy, API gateway, or URL pattern matcher, small discrepancies between the gateway’s normalization and the backend’s routing yield bypasses.

7.1 Path normalization tricks

VariantWhy it works
/ADMIN/DELETEUSERCase-insensitive backend, case-sensitive gateway
/admin/deleteUser/Trailing slash collapses to a different route
/admin/deleteUser.anythingSpring useSuffixPatternMatch (default < 5.3)
/admin/deleteUser;x=yMatrix parameter stripped by the backend only
/admin//deleteUserDouble slash collapses on one side
/admin/./deleteUserDot-segment normalized on one side
/admin/%2e/deleteUserEncoded dot segment
/admin/%2fdeleteUserEncoded slash treated literally by the gateway
/admin%20/deleteUserTrailing whitespace ignored by backend
/admin/deleteUser?Query separator at end bypasses exact-match ACL

7.2 HTTP method overrides

OverrideNotes
Raw method changeGETPOST, POSTPUT, DELETEPOST with ?_method=DELETE
X-HTTP-Method-Override: DELETEHonored by many Java and Node frameworks
X-HTTP-Method: DELETEAlternative header name
X-Method-Override: PUTAlternative header name
_method=DELETE form fieldRails, Laravel

Rails “format” bypasses are common: /admin/users checks auth for HTML, but /admin/users.json, .xml, .csv skip the filter.

7.3 URL rewrite headers

Some frameworks let a request header override the URL the router sees:

POST / HTTP/1.1
Host: target.com
X-Original-URL: /admin/deleteUser
X-Rewrite-URL: /admin/deleteUser

If the gateway applies ACLs to the outer path (/) and the app routes on the header, the control is skipped entirely.

7.4 Referer-based access control

Some admin subpages only check Referer: .../admin. Since the client sends it, attackers forge it:

GET /admin/deleteUser?id=1 HTTP/1.1
Referer: https://target.com/admin

7.5 Location/geofencing

IP-based, country-based, or device-based access control falls to:

  • VPN / residential proxy / cloud egress in the right region.
  • X-Forwarded-For: 127.0.0.1, X-Real-IP, X-Client-IP, True-Client-IP, CF-Connecting-IP, Forwarded: for=127.0.0.1 — any of these may be trusted if the app is behind a misconfigured reverse proxy.
  • Client-side geolocation manipulation in the browser dev tools.

8. Parameter & Keyword Bypasses

8.1 Keyword replacement

APIs often accept synonyms like me, current, self, my:

GET /api/users/me
GET /api/users/current
GET /api/users/self

If you replace me with an actual user ID — or vice versa — the check may fail open:

GET /api/users/42        <- explicit ID, sometimes less checked than /me
GET /api/users/me?id=42  <- hybrid that the router may resolve differently

8.2 HTTP Parameter Pollution (HPP)

Duplicate parameters behave differently across stacks:

Stack?id=1&id=2 resolves to
PHPLast (2)
ASP.NETBoth, comma-joined (1,2)
Node ExpressArray ([1,2])
Python FlaskFirst (1)
Java ServletFirst (1)

If the authorization check reads the first and the resource loader reads the last, mismatch = bypass.

8.3 Array/object injection

{"user_id": 42}                       -> checked
{"user_id": [42, 99]}                 -> second ID slips through
{"user_id": {"$ne": null}}            -> NoSQL operator injection
{"user_id": 42, "user_id": 99}        -> duplicate key, last wins

8.4 Multi-step process skipping

A checkout flow of cart → review → pay → confirm may check authorization only on pay. Jumping directly to confirm bypasses payment entirely. Always map multi-step flows and try each step in isolation.

8.5 Race conditions

Authorization decisions made in one request and acted on in another are racy:

  • Invitation accepted + immediately revoked — hit the endpoint in the window.
  • Token-gated one-shot action (redeem coupon, claim refund) — parallel requests.
  • Share-link disabled — still valid in cache for N seconds.

Turbo Intruder’s “single-packet attack” and Burp’s “send group in parallel” are the standard tools.


9. JWT & Token Claim Manipulation

JWTs are a major AuthZ failure surface because the claims inside them (role, scope, tenant_id, sub) are often trusted without proper verification.

9.1 Algorithm confusion

AttackHow
alg: noneServer accepts unsigned tokens
RS256 → HS256Server verifies HS256 using the public RSA key as the HMAC secret
ES256 → HS256Same pattern with ECDSA public keys
Weak HS256 secretBrute-force with hashcat -m 16500
kid injectionSQLi, path traversal, or SSRF in the key-id lookup
jku/x5u with attacker URLServer fetches the signing key from an attacker host
Mixed algorithm librariesDifferent verify paths on different endpoints

9.2 Claim tampering

Even with signatures intact, the server may honor claims it shouldn’t trust:

{
  "sub": "alice",
  "role": "user",
  "tenant": "acme",
  "is_admin": false
}

Watch for:

  • A role or scope claim that the server reads but doesn’t re-verify against the database.
  • sub switched to another user ID after signature bypass.
  • tenant_id / org_id / workspace_id swapped — multi-tenant bypass.
  • exp missing or in the distant future.
  • aud / iss not validated — tokens from a sibling service accepted.
  • nbf in the past but token was marked revoked — revocation cache miss.

9.3 Token chaining and mix-ups

  • Access token accepted where a refresh token was expected.
  • ID token (OIDC) accepted by resource server that expected an access token.
  • First-party token from one environment accepted in another (dev token on prod).
  • Impersonation tokens that don’t embed the impersonator identity in the audit trail.

10. OAuth Scope & Redirect Abuse

OAuth/OIDC is a leading source of high-impact authorization bugs because the protocol has many knobs and few clients implement them rigorously.

10.1 Scope escalation

  • Requesting a broader scope than the client is registered for — the authorization server should reject, but many don’t.
  • Scope upgrade on refresh — refresh token request with new scopes that weren’t in the original grant.
  • Incremental authorization that silently approves future scopes.
  • Scope parameter treated as free-text — scope=read write admin.

10.2 redirect_uri bypasses

VariantHow it works
Open whitelisthttps://*.target.com matches https://evil.target.com
Path prefixRegistered https://target.com/cb, accepts https://target.com/cb/../evil
Scheme mixhttp:// accepted when https:// was registered
Fragment injection# in the URI gets appended to attacker’s URL
Trailing datahttps://target.com@evil.com, https://target.com.evil.com
Path traversalhttps://target.com/cb/%2e%2e/redirect?url=evil
Port manipulationRegistered :443, accepts :8443
IDN / punycodehttps://tаrget.com (Cyrillic а)
Localhost loopholehttp://localhost:1337 accepted for web clients
Missing parameterNo redirect_uri defaults to first registered URI which may be wildcarded

10.3 State and PKCE

  • Missing state → CSRF on the OAuth handshake, attacker-initiated login with attacker’s session swapped mid-flow.
  • Reusable code — single-use should be enforced.
  • PKCE missing or code_challenge_method=plain.
  • PKCE verifier not bound to the session that initiated the flow.

10.4 Token leaks

  • access_token in the URL fragment logged by analytics, Referer leaks, browser history.
  • JWTs stored in localStorage — exfiltrated via any XSS.
  • Tokens echoed in error pages or email receipts.

11. Multi-Tenant & Session Isolation Failures

Multi-tenant SaaS adds a tenant dimension to every authorization check. Miss it on one endpoint and a customer can read another customer’s data.

11.1 Patterns

  • Tenant ID in the URL, not the session. /api/v1/orgs/ORG123/users/42 — if the server looks up user 42 in the global table and returns it, ORG123 is just decoration.
  • Tenant-scoped token used cross-tenant. The token says org: A, but the query runs SELECT * FROM users WHERE id = ? with no tenant filter.
  • Row-level security forgotten in a background job. The web layer enforces tenant_id = current.tenant, the worker doesn’t.
  • Shared cache keys. cache.get("user:42") without a tenant prefix — user 42 from tenant B is served to tenant A.
  • Global admin accounts that silently inherit all tenants — single compromise = full SaaS compromise.

11.2 OpenClaw-style session isolation failures

The OpenClaw CMS disclosure is a representative multi-user session isolation bug: authenticated users of one tenant could view, modify, and impersonate users of other tenants by altering a single tenant parameter, because the session stored only user_id and the tenant was derived from the request. Fix: bind the tenant into the session at login time and compare against it on every lookup.

11.3 Testing multi-tenancy

  1. Create two tenants (A, B) with distinct users.
  2. Perform every action as tenant A and capture the object IDs.
  3. Log in as tenant B and replay every request, substituting A’s IDs.
  4. Repeat with the tenant identifier swapped in each header, cookie, path, and body field.
  5. Repeat for bulk/export endpoints, webhooks, and downloadable artifacts.

Burp’s Autorize extension with two sessions (one per tenant) automates step 3.


12. Cloud & AI Agent Authorization

Cloud IAM and managed-AI services have introduced a new generation of authorization failures where the victim is the cloud tenant and the attacker is a workload or agent inside it.

12.1 Over-privileged service identities

The 2026 Unit 42 “Double Agents” research on GCP Vertex AI is a representative case:

  • A deployed Vertex AI agent’s per-project service agent (P4SA) had default permissions that let the agent extract its own credentials from the metadata service (metadata.google.internal/computeMetadata/v1/instance/?recursive=true).
  • The harvested token had storage.buckets.get/list and storage.objects.get/list on the entire consumer project — granting the agent read of every bucket.
  • The same token also reached Google-owned producer-side Artifact Registry repositories (cloud-aiplatform-private/reasoning-engine), exposing internal container images.

Generalizable takeaways:

  • Default roles on managed services are almost always broader than the tenant needs.
  • Any workload with code execution + metadata service access is one step from its full IAM role.
  • Producer/consumer boundaries blur when a managed identity crosses them.

12.2 IAM failure patterns

PatternRisk
* in role/actionTrivially abusable
iam:PassRole without a resource filterPrivilege escalation via Lambda/EC2/Glue
Trust policies with wildcard principalsCross-account assume-role from anyone
Service control policies that only deny by tagUntagged resources bypass
Resource policies less restrictive than identity policiesLeast-restrictive wins
Long-lived static keys in CILeaked → durable access

12.3 AI agent authorization

Beyond cloud IAM, AI agents introduce their own AuthZ surface:

  • The agent acts under a single identity but reasons over prompts from many users — per-prompt authorization must be enforced in tool invocations.
  • Tools that wrap APIs must re-check the user’s entitlements on every call, not trust the agent loop.
  • Prompt injection can coerce an agent to misuse its privileges — treat agents as confused deputies by default.
  • Training data, vector stores, and RAG indexes need tenant isolation or they leak across customers.

13. Real-World CVEs and Chains

13.1 CVE-2025-67274 — aangine BOLA

Low-privileged authenticated users at aangine 2025.2 could call multiple admin-restricted API endpoints — template management, integration jobs, logging, portfolio data — because the API layer never validated the role/scope claims in the JWT. Fix: backend-side authorization checks on every endpoint, plus verification testing. Textbook BFLA.

13.2 CVE-2026-34886 — WordPress Simple Membership

Versions ≤ 4.7.1 of the Simple Membership WordPress plugin allowed unauthenticated actors to trigger privileged operations (membership level changes, subscriber record edits, potential API key disclosure). Root cause: AJAX/REST endpoints lacking capability and nonce verification — the handler assumed the request was authenticated because it came from an admin UI context. Patched in 4.7.2. Pattern: AJAX handlers in WordPress plugins consistently ship without current_user_can() and without check_ajax_referer().

13.3 McHire / Paradox.ai — 64M Applicant BOLA (2025)

Paradox.ai-powered McHire recruitment portal:

  • Endpoint: PUT /api/lead/cem-xhr
  • Auth: any restaurant test account session (obtained via default creds 123456:123456)
  • Body: {"lead_id": <8-digit sequential>}

Decrementing lead_id returned arbitrary applicant PII (name, email, phone, address, shift prefs) plus a consumer JWT usable for session hijacking. Keyspace 1..64,185,742 implied ~64M records. Chain: default credentials → tenant access → sequential ID IDOR → mass PII + token theft.

13.4 Carlsberg “Memories” wristband IDOR (2026)

Exhibition visitors got QR-coded wristbands whose ID (C-285-100) was hex-encoded and sent to a Cloud Function backend to fetch stored media. No session binding — knowing the ID was authorization. The ~26M-combination keyspace was exhaustible in ~52 hours at modest rates. Vendor’s claimed rate limit was ineffective — identical throughput after “remediation.” Lesson: short IDs are bearer tokens; encoding is not entropy; rate limits must be measured, not asserted.

13.5 OpenClaw — cross-tenant session isolation

Authenticated users of one OpenClaw tenant could alter a tenant parameter to pivot into other tenants’ user stores, enabling user read, modify, and impersonation. Root cause: session bound user_id but not tenant_id; tenant derived from the request at every call. Chain: valid low-priv session → parameter swap → cross-tenant admin impersonation → full multi-tenant takeover.

13.6 GCP Vertex AI — Double Agents (2026)

Malicious agent deployed to Agent Engine → metadata service → P4SA token → consumer buckets + Google-internal Artifact Registry images. Chain: agent code execution → metadata → IAM token → cross-project pivot. Google’s remediation was documentation and default-permission tightening — the underlying design of broad P4SA defaults is the root cause.

13.7 HackerOne top authorization disclosures

From the reddelexchacker HackerOne top-authorization disclosures snapshot, the most-paid authorization classes (descending): BOLA/IDOR in user-owned resources, admin-function access via missing role checks, tenant-crossing in collaboration features, password reset token reuse, invite/share link authorization bypass, GraphQL field-level authorization gaps, and impersonation features that forget to log or authorize properly.


14. Tools & Automation

14.1 Core toolkit

ToolUse
Burp Suite ProManual request tampering, Repeater, Intruder, session handling
Burp AutorizeTwo-session automated authorization diffing
Burp AuthMatrixMatrix-style per-role per-endpoint testing
Burp Turbo IntruderHigh-throughput enumeration, race conditions
OWASP ZAPOpen-source alternative; ForcedBrowse, AuthMatrix add-on
Firefox Multi-Account ContainersMultiple simultaneous sessions in one browser
ffufContent discovery, ID enumeration, parameter fuzzing
Postman / InsomniaCollection-driven API testing with auth profiles

14.2 Scanning and discovery

ToolUse
gau, waybackurls, katanaURL harvesting for endpoint discovery
LinkFinder, SecretFinder, JSParserExtract routes and keys from JS
graphql-voyager, InQL, clairvoyanceGraphQL schema and field discovery
kiterunnerContent discovery tuned for APIs and routes
nucleiTemplates for default-admin paths, CVE checks
trufflehog, gitleaksSecret/credential discovery

14.3 IDOR-specific

ToolUse
Burp AutorizeDiff responses between roles
bwapp-idor-scannerIDOR sweeping for common patterns
BlindyBulk IDOR discovery
Custom curl/requests loopsMost effective on a per-target basis

14.4 JWT and OAuth

ToolUse
jwt_toolAlg confusion, key confusion, brute force
hashcat -m 16500Cracking HS256 secrets
Burp JWT EditorGUI token manipulation
oauthtester, Burp OAuth extensionsredirect_uri / state / PKCE testing

14.5 Cloud and IAM

ToolUse
pacuAWS exploitation framework
ScoutSuite, ProwlerMulti-cloud posture
cloudsplaining, iam-vulnerableIAM policy analysis
peiratesKubernetes privilege escalation
kubiscanKubernetes RBAC review

15. Detection & Prevention

15.1 Core principles

  1. Deny by default. Unless a resource is explicitly intended to be public, it is denied. Every new route inherits this default.
  2. Single enforcement point. Route all authorization through one library/middleware. Multiple implementations drift.
  3. Server-side only. Never trust client-side role flags, hidden fields, or UI state.
  4. Check on every request. No “we already checked in step 2” logic — every step re-authorizes.
  5. Tie identity to session, not to request parameters. Tenant, user, and role are derived from the authenticated session on the server.
  6. Use opaque identifiers. UUIDv4 or ULIDs over auto-increment integers. Not a substitute for proper authorization — it raises the cost of enumeration.
  7. Log authorization failures. Every 403 is a data point. Alerts on rate spikes catch enumeration.
  8. Rate-limit identifier sweeps. Measure it in production, not just in staging.

15.2 Defense-in-depth checklist

  • Central authorization middleware (e.g. Rails CanCan, Django permissions, Spring @PreAuthorize, custom OPA).
  • Row-level security at the database layer (Postgres RLS, per-tenant schema, application-enforced tenant_id filter).
  • Deny-by-default router — if no explicit policy attached to a route, reject the request and log.
  • Session binds user_id, tenant_id, roles, and scopes at login; never re-read from request.
  • JWTs validated with fixed algorithm, correct audience, issuer, expiry; alg: none and algorithm confusion rejected.
  • OAuth redirect_uri matched by exact string equality, not prefix or wildcard.
  • Admin endpoints on a distinct host/path plus network-layer ACL plus application-layer role check.
  • Audit log of all privileged actions with actor, target, outcome, source IP, and session.
  • Automated integration tests for every endpoint × every role.
  • Regression suite that catches any new route without a policy decorator.
  • Continuous authorization testing in CI/CD via Autorize-style diffing.

15.3 Framework-specific landmines

FrameworkLandmine
RailsMass assignment, format-based bypass (.json skips before_action)
DjangoForgotten @permission_required, ORM global managers
SpringuseSuffixPatternMatch, method-level security without class-level fallback
ExpressMiddleware order — authorize after the route handler is a no-op
LaravelPolicies registered but not invoked in API controllers
WordPressAJAX handlers without current_user_can() + check_ajax_referer()
GraphQLResolvers that authenticate but don’t authorize per-field

15.4 Detection signals in logs

  • Spike of 403s from a single IP or session → enumeration in progress.
  • 200 responses with unusual object-ID deltas (your user never touches ID 99999 normally).
  • Access to admin paths from non-admin sessions — even if denied, the attempt itself is signal.
  • JWT alg or kid field changing mid-session.
  • redirect_uri values that don’t match your registered clients.
  • Cross-tenant object references in the same session.

16. Testing Methodology

A systematic flow for AuthZ assessment, blending PortSwigger Academy, OWASP WSTG, and Intigriti BugQuest guidance.

16.1 Three-step baseline

  1. Discovery — inventory every route, method, parameter, and ID field.
  2. Context — understand the intended authorization model (roles, tenants, relationships).
  3. Test — verify enforcement against each role × each endpoint × each object.

16.2 Account matrix

Before testing, provision:

AccountPurpose
UnauthenticatedBaseline for public endpoints
Low-priv user AHorizontal target
Low-priv user BHorizontal attacker
Privileged userVertical target
Tenant A adminMulti-tenant target
Tenant B adminMulti-tenant attacker
Deactivated / bannedRevocation checks

16.3 WSTG-aligned checks

From the OWASP Web Security Testing Guide authorization section:

  • WSTG-ATHZ-01 — Directory traversal / file include (file endpoints).
  • WSTG-ATHZ-02 — Authorization schema bypass (reading the role/ACL model and probing each entry).
  • WSTG-ATHZ-03 — Privilege escalation (vertical and horizontal).
  • WSTG-ATHZ-04 — Insecure direct object references (BOLA).
  • WSTG-ATHZ-05 — OAuth weaknesses (scope, redirect_uri, state/PKCE).

16.4 Per-endpoint checklist

For every endpoint:

  • Called unauthenticated → expected deny?
  • Called as every role → matches the policy?
  • Called with every HTTP method → only permitted methods accepted?
  • Called with modified object ID → cross-user / cross-tenant access blocked?
  • Called with modified role/scope claim in JWT → rejected?
  • Called with header override (X-Original-URL, X-HTTP-Method-Override) → rejected?
  • Called via bulk/export variant → same enforcement as per-object?
  • Called through GraphQL / mobile / partner API variant → same enforcement?
  • Response body inspected for overshared fields (role, tenant, internal flags)?
  • 403/redirect responses checked for sensitive data leakage?

16.5 Reporting with impact

Frame findings with the CIA triad:

  • Confidentiality — what can be read that shouldn’t be (PII, tokens, internal docs).
  • Integrity — what can be written or changed (account takeover, payment, config).
  • Availability — what can be deleted or locked (user deletion, tenant wipe).

Chain horizontal → vertical where possible: “IDOR reveals admin password reset token → ATO of admin → full tenant compromise.” A simple IDOR that returns PII is valuable; a simple IDOR that returns an admin’s reset token is critical.


17. Quick Reference

17.1 Header bypass cheat sheet

X-Original-URL: /admin/deleteUser
X-Rewrite-URL: /admin/deleteUser
X-HTTP-Method-Override: DELETE
X-HTTP-Method: DELETE
X-Method-Override: DELETE
X-Forwarded-For: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
CF-Connecting-IP: 127.0.0.1
Forwarded: for=127.0.0.1
Referer: https://target.com/admin
X-Original-URL: /admin

17.2 Path normalization cheat sheet

/admin/deleteUser
/admin/deleteUser/
/admin//deleteUser
/admin/./deleteUser
/admin/%2e/deleteUser
/admin%20/deleteUser
/admin/deleteUser;x=y
/admin/deleteUser?
/admin/deleteUser.json
/admin/deleteUser.xml
/admin/deleteUser.html
/ADMIN/DELETEUSER
/%61dmin/deleteUser
/admin%2fdeleteUser

17.3 Parameter tampering cheat sheet

?admin=true
?role=admin
?is_admin=1
?user=admin
?debug=1
?test=1
?id=1&id=2
?id[]=1&id[]=2
id=1,2
{"role":"admin"}
{"is_superuser":true}
{"user_id":{"$ne":null}}
{"user_id":["me",42]}

17.4 JWT attack cheat sheet

alg: none
alg: None
alg: NONE
RS256 -> HS256 with public key as secret
kid: ../../../../dev/null
kid: ' UNION SELECT 'x
jku: https://attacker.example/keys.json
x5u: https://attacker.example/cert.pem
sub: <victim>
role: admin
is_admin: true
scope: admin:*
tenant: <victim_tenant>

17.5 OAuth redirect_uri cheat sheet

https://target.com.attacker.com/cb
https://target.com@attacker.com/cb
https://attacker.com/target.com/cb
https://target.com/cb/../evil
https://target.com/cb#@attacker.com
https://target.com/cb?x=https://attacker.com
https://tаrget.com/cb        (IDN)
http://target.com/cb         (scheme downgrade)
https://target.com:8443/cb   (port change)

17.6 IDOR target list

/api/users/{id}
/api/users/me
/api/users/me/documents/{id}
/api/orgs/{org}/users/{id}
/api/invoices/{id}
/api/orders/{id}
/api/messages/{id}
/api/files/{uuid}
/api/exports/{id}
/download?id={id}
/export?id={id}
/view?username={u}&file={f}

17.7 Default credential list (starting points)

admin:admin
admin:password
administrator:administrator
root:root
test:test
demo:demo
guest:guest
user:user
123456:123456
<company>:<company>
<company>:password

(Full set: SecLists Passwords/Default-Credentials/default-passwords.csv, 2.8K+ entries.)

17.8 Always-rejected-without-chain list

These, on their own, are usually out of scope or N/A. Chain them into something.

  • Missing rate limits with no account lockout demonstration.
  • “IDOR” on public resources.
  • Role names exposed in JS with no actual endpoint bypass.
  • Password reset without a working token disclosure.
  • CSRF on unauthenticated endpoints.
  • “Session doesn’t expire after logout” with no session fixation.

17.9 Chain-to-critical list

BugChain into
IDOR on user recordPassword reset → ATO
IDOR on invitesAdmin invite → vertical escalation
BFLA on role changeSelf-promotion → admin
JWT alg confusionClaim tampering → cross-tenant
redirect_uri bypassToken theft → ATO
Multi-tenant leakCross-tenant enumeration → mass PII
Cloud metadata accessIAM token → cross-project
Default credsTenant access → IDOR at scale

Appendix: Source Index

This guide synthesizes 33 clipped research sources covering:

  • PortSwigger Web Security Academy — access control fundamentals
  • OWASP — A01 Broken Access Control (2021/2025/2026), API Security Top 10, WSTG
  • HackTricks — IDOR/BOLA reference
  • Intigriti BugQuest — 31 days of broken access control
  • Hackviser — IDOR attack guide
  • Black Hat — broken access control primer
  • Imperva, HackerOne, Bright Security — BOLA/IDOR overviews
  • Palo Alto Unit 42 — Vertex AI “Double Agents” research
  • SANS 2026 Identity Threats Report
  • Managed-WP — WordPress Simple Membership (CVE-2026-34886)
  • aangine BOLA disclosure (CVE-2025-67274)
  • McHire/Paradox.ai 64M applicant IDOR
  • Carlsberg “Memories” wristband IDOR
  • OpenClaw multi-user session isolation disclosure
  • HackerOne top-authorization disclosure archive
  • Multiple practitioner guides on horizontal/vertical escalation and detection

The source material is captured under ~/Documents/obsidian/chs/raw/AuthZ/ for primary-source review.