Authenticated session handling
When it applies
The interesting bugs live after login — cross-tenant IDOR/BOLA, mass assignment, injection on
the real data APIs — but the run keeps collapsing to pre-auth findings because login is gated by
MFA/OTP, or the token expires and every automated request 401s halfway through. This skill is the
setup step that makes authenticated testing actually work. It does not attack the auth
mechanism (web-oauth, web-auth-jwt, api-auth-attacks do that) — it obtains one legitimate
session and keeps it usable.
Why it works
Post-auth coverage silently disappears when tooling can't hold a session: the scanner logs out,
the token lapses, or MFA can't be satisfied autonomously. Capturing the session as a reusable
artifact — the cookie/localStorage state for browser-driven testing and the raw bearer token for
direct API replay — decouples "log in once" from "test a thousand requests," so the whole
authenticated surface stays reachable for the life of the token.
Method
- Get a real test account, in writing. For a pentest, the client provides it; for bounty,
use an account you own (register two: attacker + victim, for IDOR diffing). Record it by
reference in
scope.txt — never paste live credentials into notes or a prompt.
- Handle the MFA/OTP gate by the least-privileged means available:
- TOTP seed shared → generate the current code from the Base32 seed (
oathtool --totp -b <SEED>)
and submit it. Fully repeatable.
- Operator-relayed OTP → drive login to the OTP prompt, a human supplies the code once, continue.
- Out-of-band / human-in-the-loop → persist the pre-OTP browser context, pause, let the
operator complete the challenge, then resume and capture state.
- Capture the session as artifacts:
- Browser state — Playwright
storageState.json (context.storage_state(path=...)) holds cookies + localStorage.
- API replay — the raw access/ID token plus its
expires_at.
- Feed it to your tools:
- Burp/Caido — a session-handling rule + macro that re-auths on a logged-out signature
(a redirect to
/login, a 401, a specific body), so long scans don't drop the session.
- Playwright — launch with
storage_state="storageState.json".
- API fuzzers (
ffuf, api-fuzzing) — inject the bearer as a header.
- Refresh, don't re-login. Record
expires_at; when a refresh token exists, refresh; only
re-run the full login when it doesn't. A wave of sudden 401s means the token lapsed — refresh
before you trust "not vulnerable."
- Diff two sessions for access control. Hold A's and B's artifacts side by side and replay
each other's object references (
web-idor, api-bola) — the core authenticated test.
Gotchas
- Present creds ≠ working creds. Before spawning a big authenticated scan, verify the session
actually reaches a known post-auth endpoint (200 with your data) — otherwise you "test" a
logged-out surface and report false negatives.
- Never inline the token. Pass sessions by file reference; keep secrets out of prompts, notes,
and any committed file. The
engagements/ tree is git-ignored — keep them there.
- If a realm truly can't be authenticated (no test account, no OTP relay), say so honestly —
record the post-auth surface as deferred/untested, don't fake an N/A or silently skip it.
- Scope the session's blast radius — a test account with real admin rights can do real damage;
prefer least-privilege test roles, and treat destructive actions per RoE.
Verify success
A request built entirely from your captured artifact (no live browser) returns a known post-auth
response with your account's data — and a two-session IDOR probe runs cleanly against it.
References
Playwright authentication/storageState docs; Burp/Caido session-handling rules; RFC 6238 (TOTP).
1---2name: web-auth-session-handling3description: Legitimately acquire and KEEP an authenticated session (through MFA/OTP) so you can test the post-auth attack surface, and hand that session to your tools without leaking secrets. Load when the high-value classes (IDOR/BOLA, mass assignment, injection on real data APIs) are blocked behind login/SMS-OTP/TOTP, when tokens keep expiring mid-test, or when Burp/Playwright keep logging you out. Distinct from attacking auth — this one authenticates and reuses the session.4---56# Authenticated session handling78## When it applies9The interesting bugs live *after* login — cross-tenant IDOR/BOLA, mass assignment, injection on10the real data APIs — but the run keeps collapsing to pre-auth findings because login is gated by11MFA/OTP, or the token expires and every automated request 401s halfway through. This skill is the12setup step that makes authenticated testing actually work. It does **not** attack the auth13mechanism (`web-oauth`, `web-auth-jwt`, `api-auth-attacks` do that) — it obtains one legitimate14session and keeps it usable.1516## Why it works17Post-auth coverage silently disappears when tooling can't hold a session: the scanner logs out,18the token lapses, or MFA can't be satisfied autonomously. Capturing the session as a *reusable19artifact* — the cookie/localStorage state for browser-driven testing and the raw bearer token for20direct API replay — decouples "log in once" from "test a thousand requests," so the whole21authenticated surface stays reachable for the life of the token.2223## Method241. **Get a real test account, in writing.** For a pentest, the client provides it; for bounty,25 use an account you own (register two: attacker + victim, for IDOR diffing). Record it by26 reference in `scope.txt` — never paste live credentials into notes or a prompt.272. **Handle the MFA/OTP gate** by the least-privileged means available:28 - **TOTP seed shared** → generate the current code from the Base32 seed (`oathtool --totp -b <SEED>`)29 and submit it. Fully repeatable.30 - **Operator-relayed OTP** → drive login to the OTP prompt, a human supplies the code once, continue.31 - **Out-of-band / human-in-the-loop** → persist the pre-OTP browser context, pause, let the32 operator complete the challenge, then resume and capture state.333. **Capture the session as artifacts:**34 - Browser state — Playwright `storageState.json` (`context.storage_state(path=...)`) holds cookies + localStorage.35 - API replay — the raw access/ID token plus its `expires_at`.364. **Feed it to your tools:**37 - **Burp/Caido** — a session-handling rule + macro that re-auths on a logged-out signature38 (a redirect to `/login`, a 401, a specific body), so long scans don't drop the session.39 - **Playwright** — launch with `storage_state="storageState.json"`.40 - **API fuzzers** (`ffuf`, `api-fuzzing`) — inject the bearer as a header.415. **Refresh, don't re-login.** Record `expires_at`; when a refresh token exists, refresh; only42 re-run the full login when it doesn't. A wave of sudden 401s means the token lapsed — refresh43 before you trust "not vulnerable."446. **Diff two sessions for access control.** Hold A's and B's artifacts side by side and replay45 each other's object references (`web-idor`, `api-bola`) — the core authenticated test.4647## Gotchas48- **Present creds ≠ working creds.** Before spawning a big authenticated scan, verify the session49 actually reaches a known post-auth endpoint (200 with your data) — otherwise you "test" a50 logged-out surface and report false negatives.51- **Never inline the token.** Pass sessions by file reference; keep secrets out of prompts, notes,52 and any committed file. The `engagements/` tree is git-ignored — keep them there.53- **If a realm truly can't be authenticated** (no test account, no OTP relay), say so honestly —54 record the post-auth surface as *deferred/untested*, don't fake an N/A or silently skip it.55- **Scope the session's blast radius** — a test account with real admin rights can do real damage;56 prefer least-privilege test roles, and treat destructive actions per RoE.5758## Verify success59A request built entirely from your captured artifact (no live browser) returns a known post-auth60response with your account's data — and a two-session IDOR probe runs cleanly against it.6162## References63Playwright authentication/storageState docs; Burp/Caido session-handling rules; RFC 6238 (TOTP).