bb-authz
Hunt broken access control — IDOR/BOLA (all 10 variants), BAC, auth bypass, privilege escalation, 403/401 bypass, and capability-URL design flaws — on authorized bug bounty targets. Use whenever the u
SKILL.mdskills/bb-authz/SKILL.md
GitHubBroken Access Control Hunter
The highest-ROI bug class (1,364 of 11,338 paid disclosures in the local corpus are access-control; top payouts $20k–$35k). Every test needs: authorization context (in-scope program), and object IDs from the user's own accounts. Cross-account reads are proven with own-account PoCs only — never touch a third party's data.
Real-bug anchors (what actually paid — query bb-disclosures for more)
- $35,000 Account takeover via password reset without user interaction — the money is in reset/recovery flows, not login.
- $22,300 RepositoryPipeline allows importing local git repos — import/migration features move objects across trust boundaries server-side.
- $20,000 Getting all CD keys of any game — anything that vends codes/keys/tickets with a swappable batch ID.
- $20,000 Steal private objects of other projects via project import (IDOR on import target).
- $13,000 Mass ATO via alternate auth path (mobile/legacy endpoint accepted weaker checks). Delta lesson: the expensive versions were interaction-free and mass-scale. If your variant needs the victim to do something, keep chaining before submitting.
Before testing
- Build the object inventory first (carts, addresses, orders, invoices, tickets, files, API keys). Empty accounts = zero signal; tell the user exactly what to seed.
- Know the "who am I" endpoint per session. Identify jars before use.
- Plan the positive control: the owner must succeed at the same request, or every 4xx is meaningless.
- Map the auth model from JS bundles first: which endpoints take IDs in path vs body vs GraphQL args, and which header set identifies the caller (
x-je-auser-style anonymous IDs count as identity claims — test whether the server actually binds them).
The 10 IDOR variants (run the table, not one swap)
| # | Variant | Test |
|---|---|---|
| V1 | Direct path | /api/users/123 → /456 (other OWN account) |
| V2 | Body param | {"user_id": other} in POST/PUT/PATCH |
| V3 | GraphQL node | node(id: base64("OtherType:123")) |
| V4 | Batch/bulk | ?ids=1,2,3 — one foreign ID among own |
| V5 | Nested scope | /orgs/{own}/users/{other} — parent own, child foreign |
| V6 | File/path | ?path=.../other-user/file.pdf, ?file_id= |
| V7 | Method swap | GET 403 → try PUT/PATCH/DELETE (and POST) |
| V8 | Version rollback | /api/v2/x guarded → /api/v1/x legacy |
| V9 | Header/param injection | X-User-ID, X-Forwarded-User, ?user_id=, ?account= |
| V10 | Predictable IDs | sequential ints, timestamps, UUID v1, md5(int)-style derived IDs |
Use idor-runner skill's script to execute matrices with positive controls and evidence logs.
Capability-URL verdicts (unguessable-ID APIs)
Modern APIs key anonymous objects by unguessable IDs (base64-UUID tokens) with no session at all. Judge them like this:
- Unguessable ID + no leak vector + fabrication returns clean 404 → working as designed; note as informational only.
- Unguessable ID plus a leak vector (ID appears in URLs shared/logs/referrer/support tools; deterministic/derived IDs; ID predictable from another endpoint) → the capability IS the finding: prove the leak, then demo full read/write with the leaked ID.
- Test whether the capability expires and whether login binds the object to the account. Permanent unbound capabilities on objects that later hold PII (checkout address) are worth reporting even without a leak PoC — frame as design risk with the ID-leak scenarios.
Auth bypass additions
- Remove the auth entirely: same endpoint, no cookie/token → 200 = unauthenticated access.
- Force-browse:
/admin,/internal,/api/admin/*,/_debug,/backoffice/*— 403 (exists, ACL) vs 404 (nothing) is itself intel. - Header tricks for edge routers:
X-Original-URL: /admin,X-Rewrite-URL: /admin,X-Forwarded-For: 127.0.0.1. - Alternate paths: the $13k TaxJar bug was the same auth on a parallel channel — check mobile APIs, legacy versions, partner/BFF mirrors of the same resource.
- JWT:
alg:nonere-sign; RS256→HS256 confusion (verify with public key as HMAC secret);kidpath injection; claim tampering (role,sub,account_id). Only report if the server accepts the forged token — never "the token CAN be tampered". - Mass assignment: send role/isAdmin/accountId fields the server didn't ask for in profile-update endpoints. Own account, check what stuck, revert.
Verdicts (be strict)
- Foreign 2xx with owner's object body → confirmed IDOR. Re-run once for the report, capture exact request.
- Explicit ownership error (
"does not belong to current user/cart") → TESTED-NEGATIVE with the error string. - Uniform 400/500 without a green positive control → INCONCLUSIVE, not negative.
- Remember most programs pre-reject: "theoretical" authz issues without a working read/write.
Impact framing
Read vs write vs delete changes severity: PII read (High) < write/delete (High/Critical). Chain upward: address read → address ID as input to order-refund flow → money impact. See bb-chains skill before writing up, and calibrate against bb-disclosures precedent before setting severity.