Skip to content

Authz Diffing Playbook

Paul White edited this page Aug 31, 2026 · 1 revision

Authz diffing playbook

claviger replay sends the same request (or a whole set of requests) under several identities at once and prints a status matrix. It reports facts: the status, the size, and where identities disagree. It does not decide whether a difference is a vulnerability; you do that by eye or by piping the output into your own diff.

One request, several identities

claviger replay --as admin --as user-a --as anon --path /api/records/42
IDENTITY     STATUS SIZE     TIME
admin        200    412      1.2ms
user-a       403    21       0.9ms
anon         401    27       0.7ms

Read it top to bottom. The expected shape for an owner-only record is owner 200 / other 403 / anon 401. Anything else is worth a look:

  • user-a returns 200 on a record it does not own: horizontal privilege escalation (IDOR).
  • anon returns 200: a missing authentication check.
  • user-a and admin return the same size on a 200: the low-privilege user may be seeing admin-only data.

anon is a reserved identity name meaning "send no session at all"; you do not configure it.

A whole corpus at once

Sweep many endpoints in one run with --corpus. Three formats are accepted, auto detected by extension or forced with --format:

claviger replay --corpus endpoints.txt --as admin --as user-a --as anon
claviger replay --corpus session.har --as admin --as user-a --as anon
claviger replay --corpus openapi.yaml --include-unsafe --as admin --as user-a
  • requests file: one METHOD /path per line; # comments allowed.
  • HAR export: captured from a browser or proxy. Claviger strips the captured identity headers (Cookie, Authorization, Proxy-Authorization, X-CSRF-Token) at load, so each identity is tested with its own session and the capturing user's credentials do not leak into the comparison.
  • OpenAPI spec: one request per operation. Only safe methods (GET/HEAD/OPTIONS) are included by default; --include-unsafe adds POST/PUT/PATCH/DELETE.

The matrix marks a row DIFFERS when the non-error statuses across identities are not all equal:

GET /api/records/42            DIFFERS
  admin   200 412
  user-a  403 21
  anon    401 27
GET /api/records/42/history    DIFFERS
  admin   200 980
  user-a  200 980
  anon    401 27

The second row is the finding: user-a gets the same 200 and size as admin on a history endpoint, so the object-level check is missing there.

Building a corpus from a real session

A fast workflow: browse the app as an admin through Burp, export the proxy history (or a specific set of requests) as HAR, then replay that HAR under a low-privilege identity and anon. Because Claviger strips the captured credentials, the admin's own cookies do not carry over; every row is a clean test of whether the lower identity should have reached that endpoint.

claviger replay --corpus admin-session.har --as admin --as user-a --as anon

Scan the output for any user-a or anon row that is not blocked where it should be.

Exit code

replay exits non-zero only when every request failed for every identity (for example the target is down). A row that returns a 200, 403, or 401 is a successful measurement, not an error, so the exit code stays 0; the findings are in the table, which is the deliverable.

Clone this wiki locally