Authorization & access control
Authorization bugs — IDOR/BOLA, privilege escalation, missing authentication — are where
Nullhawk's evidence-first model earns its keep. There are three layers: declaring object
ownership, replaying one request as everyone (authz), and sweeping the whole app
(authz-sweep). For a hands-on walkthrough see
Tutorial 2.
Identifiers and objects
Analysis reads captured traffic and offers values that vary where an identifier would against a path that holds still — not because they look numeric. It sends nothing and declares no ownership on its own:
nullhawk identifiers ./engagement --analyze # offer candidate identifiers, with signals
nullhawk identifiers ./engagement --accept <value>
nullhawk object ./engagement add <id> --owner "User A" --name account --in-request <req>
nullhawk object ./engagement list
Declaring an object records a claim of ownership — it's the tester's assertion, not something Nullhawk establishes. That's what both convicts (a non-owner reached it) and exonerates (every caller gets their own record in an identical shape).
Single-request replay: authz
Replay a captured request as several identities and compare what came back — the core IDOR and privilege-escalation check:
nullhawk authz ./engagement <request-id> # as every other identity + anon control
nullhawk authz ./engagement <request-id> --as-identity alice # whose request this is
nullhawk authz ./engagement <request-id> --as-identity alice --verify # reproduce before reporting
nullhawk authz ./engagement <request-id> --construct --verify # substitute a declared object
The anonymous control is what makes the matrix trustworthy: without it, a public page
produces one "violation" per identity, all true and all worthless. The comparison is
structural — it reports which field differed ($.email present for A, absent for B), not a
similarity percentage. Only GET/HEAD/OPTIONS are replayed by default; a state-changing
method needs an explicit opt-in.
Whole-app sweep: authz-sweep (Pro)
The automated version: across the whole project, infer which identity owns each object, then have every other identity try to reach each object it doesn't own — built from the owner's own captured request.
nullhawk authz-sweep ./engagement --budget 40 --verify
nullhawk authz-sweep ./engagement --host api.example.com --no-save
It runs two cross-identity tests per endpoint:
- Replay — the owner's own request sent as each non-owner (catches the classic URL-id IDOR).
- Construction — one identity's object substituted into a different endpoint.
Both carry an anonymous control and send through the same scope-guarded path as everything else. Leads are ranked, de-duplicated by claim, and each cites two replayable requests. In the desktop app this is Testing → Access Control.
Flags: --host (one host), --budget <n> (ceiling spread across endpoints), --verify
(Firm → Confirmed), --no-save (preview), --insecure (skip TLS verification).
What's covered, and what isn't
- Replay needs no declarations and works on any engagement.
- Construction needs object ownership to have been declared/inferred.
- An endpoint whose captured credential matches no declared identity is reported as untested, never guessed at. "No findings" from a sweep is not "no IDOR" — it's bounded by what was captured and attributed.