Docs / Finding bugs / IDOR & Shadow Replay
Identities & Shadow Replay (IDOR/BOLA)
The short version. Authorization bugs are diff-shaped: the same request answers two actors differently. Capture two test accounts as Identities (cookie, bearer, API key, or client cert), then re-run any saved request as the other actor with Replay as… — or let Identity Shadow Replay diff every captured request across identities automatically and rank the drift. Scale it from the CLI with crusader hunt run --identities a,b, which replays your History — it is not a URL probe. Confirm by hand, then promote to a finding. Identities and manual replay are broadly usable; Shadow Replay and Hunt are Hunter Pro.
01Why authorization bugs are diff-shaped
IDOR and BOLA — broken object-level authorization — are the bugs a single-actor workflow can't see. Browsing as one account, GET /api/orders/1042 returns your own order and looks completely correct. The bug only exists relative to another actor: the server hands Alice's order to Bob because it checked that Bob is authenticated but never that he owns object 1042. There's no error, no anomaly in the single response — the defect is the difference between how that one request answers two actors.
So the method is comparative. You need at least two authenticated contexts plus the unauthenticated one, the same request expressed in each, and a disciplined diff of the responses. The signals that matter:
- Cross-actor object access — Bob's identity returns Alice's object (status
200, same body). Classic IDOR/BOLA. - Missing auth gate — the unauthenticated replay also returns the object, or a sensitive endpoint answers anonymously at all.
- Policy drift — two accounts that should have identical rights get different answers, exposing inconsistent enforcement across endpoints.
Crusader is built around this shape. It keeps each actor as a reusable Identity, lets you re-issue any captured request under a different one, and automates the cross-identity diff so you're reading ranked drift instead of eyeballing two tabs.
Only run cross-actor replays against targets you're authorized to test — replaying a request under a second identity is sending live, authenticated traffic to real object IDs.
02Step 1 — Capture two accounts as identities
An Identity is a saved authentication context Crusader can re-attach to a request. To compare actors you need two of them — call them Alice and Bob — captured the same way so the only variable is who's asking.
Log in as each account through the proxy, then save its auth material from the Identities screen. Crusader stores four kinds:
| Kind | Carries |
|---|---|
| Cookie | Session cookies for the host — the common browser-session case. |
| Bearer | An Authorization: Bearer token, e.g. a JWT from an SPA or mobile app. |
| API key | An API-key header or token used by the target's API. |
| Client cert | A PEM client cert + key for mTLS. Often auto-imported from the mobile mTLS-capture flow. |
From the CLI the verbs are:
# save, list, and inspect identities
crusader identity create # add an identity (Alice, Bob)
crusader identity list # see what's saved
crusader identity get # inspect one
crusader identity use # set the active identity
crusader identity refresh # re-pull a stale token / cookie
Host-scoping and scope safety
Each identity is host-scoped: it carries a host pattern so Crusader only ever presents Alice's cookie to the host it belongs to. That's not cosmetic — it stops a session token from leaking to an unrelated domain during replay, and it's why mTLS identities infer their host pattern from the cert CN (falling back to * only when the CN isn't a hostname).
Replay is also target-scope-aware. Before re-issuing a request under another identity, Crusader checks that the destination is replayable — host-scope match plus engagement scope — so an automated cross-identity pass stays inside the boundary you set in Target scope. Plugins see the same gate as replayable_to_url === true before api.requestAs(...) will fire. Keep a real scope set; it's what keeps Shadow Replay and Hunt from wandering off-target.
If the target uses mTLS, a failed handshake raises an mTLS REQUIRED toast pointing you at Mobile > Capture mTLS; a captured cert auto-imports as an identity. Note that hardware-backed keys (AndroidKeyStore/StrongBox, iOS Secure Enclave) are non-exportable — Crusader reports that honestly rather than faking a PEM, and you can't replay what you can't extract.
03Step 2 — Replay one request as another actor
With two identities saved, the fastest manual test is a one-shot Replay as… backfill: take a request you captured as Alice and re-run it as Bob, or unauthenticated. This is the single-request, hands-on version of the whole technique, and it's usable broadly — you don't need Pro to re-issue a request under a saved identity by hand.
Worked example. As Alice you captured:
GET /api/orders/1042 HTTP/2
Host: shop.example.com
Cookie: session=<alice>
Find that row in HTTP History, choose Replay as…, and pick Bob. Crusader re-sends the exact request with Bob's auth material swapped in (and only to the host the identity is scoped to). Read the result against Alice's original:
- 403 / 404 / empty — authorization is enforced. Bob can't reach Alice's object. Expected, healthy.
- 200 with Alice's order — Bob just read another user's object. That's the IDOR/BOLA. Repeat as unauthenticated to see whether there's any gate at all.
For an iterative session, open the request in Repeater and set the per-tab identity — each tab can carry its own actor, so you can flip Alice → Bob → none and resend while tweaking the object ID. crusader repeater send <history-id> drives the same replay from the CLI. To enumerate candidate object-reference endpoints in the first place, the data helpers crusader idor candidates / idor scan / idor explain (also History's idor-candidates) surface the requests worth replaying.
Replaying a single saved request as another identity is a manual, broadly available action. The automated cross-identity comparison in the next step is the Hunter Pro layer on top of it.
04Step 3 — Identity Shadow Replay Hunter Pro
Doing Replay as… by hand proves one bug. Identity Shadow Replay does it across your captured traffic automatically: it takes requests you made as one actor, silently re-issues them under your other identities (and unauthenticated), and computes the authorization drift — where the responses diverge in a way that implies broken object-level or function-level access control.
Instead of reading raw response pairs, you get a ranked view of where two actors who asked the same thing got materially different answers — the 200-vs-403 splits, the bodies that should have differed but didn't, the endpoints that answered anonymously. That collapses the tedious part of access-control testing — the bookkeeping of which request was tried as whom — into a list of candidates to confirm.
It's gated as Hunter Pro (IdentityShadowReplay). Because it re-sends real authenticated requests, it obeys the same host-scope and target-scope rules as manual replay — it won't fire at hosts outside scope or present an identity to a host it isn't scoped to. The matching plugin host call is api.requestAs(identityId, opts), which respects identity scope and requires replayable_to_url === true.
05Step 4 — Run it at scale with hunt run Hunter Pro
To sweep a whole engagement, drive the same machinery from the CLI:
# replay captured History across two identities for access-control drift
crusader hunt run --identities alice,bob
The mental model matters: hunt run replays existing History rows with your saved identities to look for IDOR / access-control drift. It is not a URL probe or a crawler — it never discovers new endpoints. It needs two things to do anything:
- Captured traffic in History — the requests it will re-issue. Browse the app as your primary actor first (or import a Burp/HAR/SAZ/Caido session).
- At least one identity — and for a real cross-actor diff, two. With
--identities alice,bobit re-runs each captured request as each actor and compares.
It's a Hunter Pro feature: the underlying One-Button Hunt composes OneButtonHunt + IdentityShadowReplay + ActiveScanner. In the GUI the same one-button flow surfaces as Look for security bugs / Unlock one-button hunt (the Look for bugs step in Easy Mode). Over MCP it's the hunt.run tool, gated by the same feature set; called on Free it returns a structured requires_upgrade result rather than throwing.
Because it replays History, the quality of a hunt is bounded by what you captured. Walk the high-value, object-scoped flows as your first actor — orders, profiles, documents, exports — so there's something authorization-shaped to replay.
06The authz scanner modules
Under the hood, the authorization work is a family of scanner modules that run over already-captured History (the scanner is not a crawler). The cross-identity ones are exactly the engines Shadow Replay and Hunt lean on:
| Module | Looks for |
|---|---|
AuthzOwnership | Object-ownership checks — does actor B reach actor A's object (IDOR/BOLA)? |
AnonymousAccess | Endpoints that answer with no authentication at all. |
ActorDifferential | Same request, different actors — the core authorization-drift diff. |
MultiActorBolaPlus | BOLA across more than two actors / roles. |
PolicyDrift | Inconsistent enforcement across endpoints for equivalent actors. |
DownloadExportAuthz | Authorization on file download / export endpoints. |
On Free the scanner runs these passively (analyze-only) — button Run passive scan. Hunter Pro adds active proof replay (button Run scan, shortcut ⌘R): for the authz allow-list it actually re-issues the bounded reads needed to prove the drift, budget-capped (~60s) and refusing anything that isn't a safe read or a tightly bounded canary write. Out-of-scope hosts and private/loopback/link-local/cloud-metadata IPs are blocked at connect time. Results land under the Scanner page's Authz tab alongside Findings / Intelligence / Probe queue.
07Step 5 — Confirm and promote to a finding
Automation ranks candidates; you confirm the bug. Before promoting, reproduce the cross-actor access by hand — the cleanest proof is a fresh Replay as… or Repeater pair showing Bob's identity returning Alice's object, plus the unauthenticated result for contrast. Confirm it's a real object you shouldn't reach, not two accounts that legitimately share it.
Then promote. In the GUI Scanner, open Findings and triage the candidate to Confirmed (the other states are NeedsReview, Hypothesis, Dismissed). A confirmed active finding gets a ProofPack — a reproduction artifact carrying the summary, validation status, the actors involved, the evidence, a minimal reproduction, copy-ready report text, and remediation. That's your IDOR write-up, assembled.
# triage findings from the CLI
crusader findings list
crusader findings get <id>
crusader findings add # record a manual finding
Two honesty notes. Status is the source of truth; confidence is a ranking signal, not a vulnerability claim — a high-confidence candidate is still a candidate until you mark it Confirmed. And ProofPacks are unredacted by design for replay fidelity (they hold the real identities and bodies); external shares redact by default, so review before pasting into a tracker or an LLM. Generating exported reports (Markdown, SARIF, JSON, CSV, HackerOne .h1.md, Jira .jira.md) is itself a Hunter Pro feature — local findings stay available without it.
08Troubleshooting
"hunt run" found nothing
It only replays what's in History. Confirm you actually captured the object-scoped flows (check HTTP History) and that you passed --identities with at least one — ideally two — saved identities. Empty History or no identity means nothing to diff.
Replay returns 401/403 for both actors
The identity probably went stale — an expired session cookie or token. Re-pull it with crusader identity refresh (or re-login through the proxy and re-save), then replay again.
Shadow Replay or Run scan is greyed out / returns requires_upgrade
Those are Hunter Pro (IdentityShadowReplay, ActiveScanner). Identities themselves and manual Replay as… work without Pro — check crusader license status. The 14-day trial unlocks the active path with just an email.
Replay never leaves the host I expected
That's the scope guard working. An identity only presents to its host pattern, and replay won't fire at hosts outside Target scope. Widen the host pattern on the identity, or add the host to scope, then retry.
An IDOR candidate is really shared access
Some objects are legitimately visible to both accounts (a shared team resource). Confirm the object belongs to the other actor exclusively before promoting — and remember a DNS-only or weak signal is noise, not proof. Mark genuine false positives Dismissed so the next pass is cleaner.
Want a guide that isn't here yet? Email hello@crusaderproxy.com.