@murk-security/runner-linux-x64 0.0.0-stage → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +2 -2
  2. package/bin/harness +0 -0
  3. package/package.json +15 -4
  4. package/runners/basic_harness_run.py +1271 -0
  5. package/runners/checklists/anonymous.json +24 -0
  6. package/runners/cve_feed.json +53 -0
  7. package/runners/prod_env.py +1686 -0
  8. package/runners/profile.py +307 -0
  9. package/runners/recon.py +396 -0
  10. package/runners/remediate.py +429 -0
  11. package/runners/report.py +365 -0
  12. package/runners/skills/api/idor-enumeration/SKILL.md +42 -0
  13. package/runners/skills/exploit/authz-differential/SKILL.md +62 -0
  14. package/runners/skills/exploit/business-logic/SKILL.md +53 -0
  15. package/runners/skills/exploit/client-side-exploitation/SKILL.md +63 -0
  16. package/runners/skills/exploit/client-side-rendering/SKILL.md +57 -0
  17. package/runners/skills/exploit/credential-chaining/SKILL.md +56 -0
  18. package/runners/skills/exploit/known-cve-validation/SKILL.md +46 -0
  19. package/runners/skills/exploit/path-traversal/SKILL.md +44 -0
  20. package/runners/skills/exploit/reflected-xss/SKILL.md +43 -0
  21. package/runners/skills/exploit/service-exploitation/SKILL.md +64 -0
  22. package/runners/skills/exploit/service-probing/SKILL.md +51 -0
  23. package/runners/skills/exploit/sqli-inference/SKILL.md +46 -0
  24. package/runners/skills/exploit/ssrf-oob/SKILL.md +48 -0
  25. package/runners/skills/exploit/state-change-exploitation/SKILL.md +72 -0
  26. package/runners/skills/exploit/stateful-authz/SKILL.md +50 -0
  27. package/runners/skills/recon/subdomain-takeover/SKILL.md +45 -0
  28. package/runners/skills/web/content-discovery/SKILL.md +59 -0
  29. package/runners/skills/web/graphql-exposure/SKILL.md +40 -0
  30. package/runners/skills.py +174 -0
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: credential-chaining
3
+ description: Turn a recovered secret into reach — stash a credential/token/key/id you extract (from a JS bundle, an IDOR, an error, a response body) and replay it on later requests to prove the next hop, then record the chain so a multi-step compromise is reported end to end, not as scattered single findings.
4
+ when-to-use: you have recovered something that looks usable — an API key in a JS/source-map, a bearer token or cookie from a response, an internal id or endpoint — and you want to prove what it actually unlocks, or you have several findings that together form one escalation and need to report the chain.
5
+ surface: web
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Credential chaining & kill-chain reporting
11
+
12
+ A non-destructive playbook for the `loot`, `http`, and `diff` ops. Single findings
13
+ under-state risk; criticals are usually *chains* — a leak enables access, which
14
+ yields a credential, which unlocks data. `loot` is structured memory for exactly
15
+ that: it carries a recovered credential forward (the third credential source beside
16
+ static identities and `login` sessions — this one for values YOU recovered) and
17
+ records the edges between findings so the report shows the whole path.
18
+
19
+ ## 1. Recognize loot
20
+ As you probe, watch for anything replayable: an API key or token in a JS bundle or
21
+ source-map (`http` with `grep`), a bearer token or session cookie in a response, an
22
+ internal id, a hidden endpoint, a password in a verbose error. If you could send it
23
+ on another request, it is loot.
24
+
25
+ ## 2. Stash it with its source
26
+ `loot {"action":"stash","kind":"bearer","value":"<the token>","source":"/account",
27
+ "note":"alice api key"}`. Pick `kind`: `cookie`, `bearer` (default), or `header`
28
+ (give `value` as `"X-API-Key: abc"`). You get back a loot id. The `source` is the
29
+ evidence for this hop — the request that yielded the credential.
30
+
31
+ ## 3. Replay it to prove the next hop
32
+ Send the stashed credential on a later request by reference:
33
+ `http {"path":"/api/privileged","loot":"loot-1"}`. The adapter injects it (only for
34
+ the origin host, like an identity). A response you could NOT reach anonymously, now
35
+ reachable with the replayed credential, proves the hop. Use `diff` to show it
36
+ crisply: the same endpoint anonymous vs. with `loot`.
37
+
38
+ ## 4. Record the chain edge
39
+ `loot {"action":"link","from":"/account (authenticated)","to":"loot-1",
40
+ "note":"account view leaked the api key"}` then
41
+ `loot {"action":"link","from":"loot-1","to":"/api/privileged",
42
+ "note":"key unlocked all users' SSNs"}`. Edges are free-form (ids or short labels) —
43
+ they capture *A enabled B*. `loot {"action":"list"}` returns the loot and edges.
44
+
45
+ ## 5. Distinguish a real chain from a dead end
46
+ Only a hop if the replayed credential actually reaches something it should not, or
47
+ something new. A stashed value that still returns 401/403 is NOT a hop — it proves
48
+ the opposite (good access control), so do not claim it. A credential replayed to the
49
+ resource it was *issued* for is expected, not a finding.
50
+
51
+ ## Evidence & severity
52
+ Report the chain as an ordered path: each hop cites the request that yielded the
53
+ credential and the request that the credential unlocked. A chain that reaches
54
+ another principal's data, an admin function, or a tenant boundary is high — critical
55
+ when it ends in full account/admin takeover or a mass data dump. Replay recovered
56
+ credentials for READ proof only; never use one to modify or delete.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: known-cve-validation
3
+ description: Turn a version/CVE lead into a confirmed-or-refuted finding by checking the deployed version and a non-destructive version/behaviour signature — never by firing a destructive exploit.
4
+ when-to-use: recon surfaced a CVE or a version banner (internetdb vulns, a Server/X-Powered-By header, a JS library version, a login page naming a product+version), and you need to decide whether the target is actually affected.
5
+ surface: web
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Known-CVE validation (non-destructive)
11
+
12
+ A playbook for the `http` and recon (`internetdb`) ops. Goal: decide whether a
13
+ CVE lead actually applies to THIS target, with a reproducible, non-destructive
14
+ signal — not by exploiting it. A CVE id alone (from a banner or a scanner) is a
15
+ lead, not a finding.
16
+
17
+ ## 1. Pin the lead and the exact version
18
+ From recon/banners, record the product and the CVE's affected-version range.
19
+ `internetdb` returns `cpes`/`vulns` for the origin IP; `http` headers (`Server`,
20
+ `X-Powered-By`, `X-AspNet-Version`), a JS library path, or a product login page
21
+ give the deployed version. Note where you read it.
22
+
23
+ ## 2. Confirm the version is in range
24
+ Compare the deployed version to the CVE's affected range. Out of range (patched,
25
+ or a different major) → REFUTE the lead with the version evidence. A banner can
26
+ lie or be stripped; prefer two independent version signals when you can.
27
+
28
+ ## 3. Non-destructive behaviour signature
29
+ Where the CVE has a safe, read-only tell, check THAT instead of exploiting:
30
+ - a fixed path/endpoint the vulnerable version exposes (a debug route, an admin
31
+ servlet, a default file) — fetch it and read the response;
32
+ - a response header/body fingerprint unique to the affected build;
33
+ - for a blind/SSRF-class CVE, use `oob_mint`/`oob_poll` so proof is a logged
34
+ callback, not damage.
35
+ Never run a destructive or availability-affecting PoC (no RCE payloads, no DoS, no
36
+ data-wiping request) — that belongs to a signed exploit-tier engagement, not here.
37
+
38
+ ## 4. Decide
39
+ Confirm only when the version is in range AND a non-destructive signature matches;
40
+ cite both. If the version is out of range, or no safe signature confirms it, record
41
+ refuted or inconclusive — do not escalate a banner into a "confirmed RCE".
42
+
43
+ ## Evidence & severity
44
+ Cite the version evidence and the behaviour-signature response (or the OOB hit).
45
+ Severity from the CVE, but downgrade when only the version matches and
46
+ exploitability is unproven (theoretical vs demonstrated).
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: path-traversal
3
+ description: Prove a directory/path-traversal read — an endpoint that serves files lets a crafted path escape its intended directory to read files it should never return.
4
+ when-to-use: an endpoint takes a filename/path parameter or serves files (e.g. /download?file=, /static?path=, ?template=, ?page=), or a response reflects a filesystem path; and you want to show it can be made to read out of bounds.
5
+ surface: web
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Path / directory traversal (read)
11
+
12
+ A non-destructive playbook for the `http` op. Goal: show that a file-serving
13
+ endpoint can be driven to read a file OUTSIDE the directory it is meant to serve.
14
+ Read only — prove the escape by reading one file you should not reach; never write.
15
+
16
+ ## 1. Find the file sink
17
+ Collect endpoints that name a file or path: `/download?file=`, `?path=`, `?page=`,
18
+ `?template=`, `?doc=`, static-asset handlers. Note the intended directory from the
19
+ normal response (e.g. a public report served from a docs folder).
20
+
21
+ ## 2. Establish the in-bounds baseline
22
+ Fetch the endpoint as intended: `http {"path":"/download?file=report.txt"}`. This
23
+ is the control — a normal, authorized read. It is NOT a finding.
24
+
25
+ ## 3. Attempt the escape
26
+ Walk up out of the base directory toward a file that should be unreachable:
27
+ `http {"path":"/download?file=../secret/service.key"}`, then
28
+ `/download?file=../../etc/passwd`. Try URL-encoded (`%2e%2e%2f`) and doubled
29
+ (`....//`) forms if a naive filter is in the way. A 200 returning the contents of
30
+ an out-of-bounds file (a key, `/etc/passwd`, another tenant's document) is the
31
+ traversal.
32
+
33
+ ## 4. Distinguish a real escape from correct behavior
34
+ It is only a finding if the file is one the endpoint should NOT serve: outside the
35
+ intended directory, and sensitive or private. An endpoint that correctly rejects
36
+ `../` (404/403, or normalizes back into the base) is behaving — not a finding.
37
+ Pair it with the `diff` op if you need to show in-bounds vs escaped responses side
38
+ by side.
39
+
40
+ ## Evidence & severity
41
+ Cite the in-bounds baseline and the escaped read, and name the out-of-bounds file
42
+ and the sensitive content it returned. Severity high, or critical if it reaches
43
+ credentials/keys or arbitrary files. Read the proof file once; do not enumerate
44
+ the whole filesystem or exfiltrate at scale.
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: reflected-xss
3
+ description: Prove reflected cross-site scripting — show that a request parameter is reflected into the HTML response without encoding, so script-context markup survives intact.
4
+ when-to-use: a parameter is echoed back in an HTML response (search terms, error messages, "you searched for…", query values in the page), and you want to show it is reflected without output encoding.
5
+ surface: web
6
+ severity: medium
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Reflected XSS
11
+
12
+ A non-destructive playbook for the `http` op. Goal: show a parameter is reflected
13
+ into an HTML response WITHOUT output encoding, in a context where markup executes.
14
+ Prove reflection with a benign, unique marker — do not weaponize against real users
15
+ or chain to a live payload.
16
+
17
+ ## 1. Find a reflection
18
+ Identify parameters echoed into the page (search terms, messages, path segments).
19
+ Fetch normally and confirm your input appears in the response body.
20
+
21
+ ## 2. Probe the encoding with a unique marker
22
+ Send a distinctive, harmless marker containing markup characters, URL-encoded:
23
+ `http {"path":"/search?q=%3Cxss7f3%3E"}` (i.e. `<xss7f3>`). Then read how it comes
24
+ back:
25
+ - reflected verbatim as `<xss7f3>` in an HTML body → output encoding is MISSING;
26
+ injected markup would be parsed as HTML. That is the finding.
27
+ - reflected as `&lt;xss7f3&gt;` (entity-encoded) → correctly escaped, NOT a finding.
28
+
29
+ ## 3. Confirm the context is executable
30
+ Check the reflection sits where markup runs: HTML body/attribute, not inside an
31
+ already-encoded JSON string or a `text/plain` response. A reflection in a
32
+ `Content-Type: application/json` body is not script-executable in a browser.
33
+ Note the response's Content-Type.
34
+
35
+ ## 4. Minimal execution proof (only if needed)
36
+ If you must go past reflection, use the smallest tag that proves parsing (e.g. an
37
+ `<svg>` with a harmless marker). Never target real users, never exfiltrate, never
38
+ persist.
39
+
40
+ ## Evidence & severity
41
+ Cite the request and the response line showing the marker reflected unescaped, plus
42
+ the Content-Type proving an executable context. Severity medium (reflected), higher
43
+ if it hits authenticated pages or combines with a CSRF/token leak.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: service-exploitation
3
+ description: Exploit an exposed non-HTTP service by actually talking its protocol — send commands over a raw TCP connection and read the reply to prove an unauthenticated Redis, memcached, SMTP relay, or similar line protocol processes attacker input — the exploit-tier step beyond a read-only banner grab.
4
+ when-to-use: a `probe` banner grab (or a port scan) found an open non-HTTP port you suspect is reachable without auth (Redis 6379, memcached 11211, SMTP 25, an admin/debug socket), and the engagement is at the exploit tier — you want to prove the service accepts your commands, not just that the port is open.
5
+ surface: network
6
+ severity: high
7
+ tier: exploit
8
+ ---
9
+
10
+ # Service exploitation (raw protocol send)
11
+
12
+ The exploit-tier counterpart to `probe`. `probe` connects and reads the banner the
13
+ service volunteers — it sends nothing. `send` connects and TRANSMITS protocol bytes,
14
+ then reads the reply, so you can prove an exposed service actually processes your
15
+ commands. It rides a raw TCP socket, two-key gated (exploit tier AND destructiveActions)
16
+ and scope-bound to the target host, exactly like probe.
17
+
18
+ Talking a live protocol can change service state. The discipline is to prove
19
+ reachability with the most benign command first, and never run a destructive one to
20
+ "confirm" what a read-only command already shows.
21
+
22
+ ## 1. Start from a probe
23
+ You should already know the port is open (from `probe` or a scan) and have a guess at
24
+ the protocol from the banner or the port number. Don't blind-send to arbitrary ports.
25
+
26
+ ## 2. Send the most benign proving command first
27
+ Prove the service accepts commands with a read-only/no-op command before anything
28
+ else:
29
+ - **Redis**: `send {"port":6379,"data":"PING\n"}` → `+PONG` proves it is an open,
30
+ unauthenticated Redis. Then `INFO` for version/role. (`\n` is sent as CRLF.)
31
+ - **memcached**: `send {"port":11211,"data":"stats\n"}` → a `STAT` block.
32
+ - **SMTP**: read the `220` banner, then `EHLO probe` → the capability list; an open
33
+ relay shows in the `250` responses.
34
+ Use `hex` instead of `data` for a binary protocol.
35
+
36
+ ## 3. The reply is the proof
37
+ A command-specific reply (`+PONG`, a `STAT` block, a `250` capability) proves the
38
+ service processed YOUR input — that is the finding: an unauthenticated, reachable
39
+ service. Quote the exact bytes sent and the reply received.
40
+
41
+ ## 4. Distinguish processed from ignored (precision)
42
+ Only a finding if the service ACTED on your command. A connection that returns just
43
+ its connect banner and then nothing, an `-ERR`/`-NOAUTH`/`5xx auth-required` reply, or
44
+ a silent socket that never answers, means the command was NOT accepted — do not claim
45
+ interactive access. A banner alone is a `probe` result, not an exploitation.
46
+
47
+ ## 5. Stop at proof; do not escalate destructively
48
+ Once a benign command proves reachability, STOP — report the capability. Do NOT run a
49
+ command that destroys or exfiltrates real data (`FLUSHALL`, `CONFIG SET` to overwrite
50
+ files, mail injection) just to "confirm" it; the accepted read-only command already
51
+ proves the missing authentication. If a write is the only way to demonstrate a
52
+ specific impact, prefer a reversible one on a throwaway key and undo it.
53
+
54
+ ## Scope & safety
55
+ `send` only reaches the target host and hosts under the verified root domain; it
56
+ refuses anything else. One service, one proving exchange per finding. Keep commands
57
+ reversible; never pivot raw sends across hosts or fan them out.
58
+
59
+ ## Evidence & severity
60
+ Cite the host:port, the exact bytes sent, and the reply that proves the service
61
+ processed them. Severity tracks impact: an unauthenticated data store (Redis/memcached)
62
+ reachable from the engagement surface is high-to-critical (data theft, often RCE via
63
+ known techniques); an open relay or an info-only socket is lower. No accepted command,
64
+ no service-exploitation finding.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: service-probing
3
+ description: Find and fingerprint NON-HTTP services the web surface hides — TCP-connect to ports and read the banner a service volunteers (SSH/SMTP/FTP/database version strings), to surface exposed services, outdated versions, and listeners that should not be reachable. Often the highest-severity findings on an engagement live off the web port.
4
+ when-to-use: the target host (or a host under the verified root domain) likely runs more than a web server, recon (internetdb) reports open ports, or you want to confirm whether a non-HTTP port is actually reachable and what is listening on it.
5
+ surface: network
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Non-HTTP service probing
11
+
12
+ A non-destructive playbook for the `probe` op (with `internetdb`/`dns` recon as the
13
+ lead). A web app is one service on one port; the host usually runs more. An exposed
14
+ SSH with an old OpenSSH, a reachable database, an open Redis, a mail relay — these
15
+ are frequently the real criticals, and the `http` op cannot see any of them.
16
+
17
+ `probe` CONNECTS AND READS ONLY. It sends nothing, so it cannot change a service's
18
+ state. Sending protocol commands (an active handshake) is exploit-tier and out of
19
+ scope here — do not try to work around that.
20
+
21
+ ## 1. Decide which ports to look at
22
+ Lead with recon: `internetdb` on the host's IP reports observed open ports and CPEs
23
+ cheaply, without touching the host. Confirm the interesting ones with `probe`. If
24
+ recon is unavailable, probe the common service ports (22, 25, 465/587, 3306, 5432,
25
+ 6379, 3389, …) as a bounded list.
26
+
27
+ ## 2. Probe and read the banner
28
+ `probe {"ports":[22,3306,6379]}` (defaults to the origin host; pass `host` to probe
29
+ a recon-discovered host UNDER the verified root domain). You get per-port
30
+ open/closed and the connect-banner. SSH, SMTP, FTP and MySQL announce a version on
31
+ connect; HTTP and Redis stay silent — `open:true` with an empty banner still proves
32
+ the port is reachable.
33
+
34
+ ## 3. Reason about the banner
35
+ A version string is the finding's core: `SSH-2.0-OpenSSH_7.4` names a specific build
36
+ you can map to known CVEs (cross-check with the known-cve-validation playbook — but
37
+ only claim a CVE with a real version-match signature, never a destructive PoC). An
38
+ exposed database or cache port that should never face the internet is a finding on
39
+ reachability alone.
40
+
41
+ ## 4. Distinguish real exposure from nothing
42
+ `open:false` means closed/filtered — not a service; never report a phantom. An open
43
+ port with a banner is a reachable service; map it. An open port with no banner is
44
+ reachable but unidentified — report the exposure, do not guess the service. A port
45
+ reachable only because YOU are in-network (not from the internet) is weaker — say so.
46
+
47
+ ## Evidence & severity
48
+ Cite the host, port, open/closed, and the exact banner. An exposed admin/database/
49
+ cache service, or a service whose version maps to a known RCE, is high to critical;
50
+ a benign, expected service (a web server's own 80/443) is not a finding. Probe only
51
+ hosts in scope (the op enforces this); read banners, never send commands.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: sqli-inference
3
+ description: Prove SQL injection non-destructively using boolean and error oracles — show the query is attacker-controllable without reading/altering data at scale.
4
+ when-to-use: a parameter feeds a search/filter/lookup that likely hits a database (?q=, ?id=, ?sort=, ?filter=), or an error response leaks SQL/driver text; and you want to demonstrate injection without destructive queries.
5
+ surface: web
6
+ severity: critical
7
+ tier: safe-active
8
+ ---
9
+
10
+ # SQL injection (safe, inference-based)
11
+
12
+ A non-destructive playbook for the `http` and `diff` ops. Goal: prove the query is
13
+ attacker-controllable via an ORACLE — the response itself tells you true/false or
14
+ errors — WITHOUT dumping data at scale or running any write/stacked query. Read-only
15
+ SELECT inference only; never `;DROP`, `UPDATE`, `DELETE`, time-bomb, or OR-1=1 that
16
+ mutates.
17
+
18
+ ## 1. Find the sink and baseline it
19
+ Fetch the parameter normally: `http {"path":"/api/search?q=widget"}`. Note the shape
20
+ of a normal result set.
21
+
22
+ ## 2. Error oracle
23
+ Send a single unbalanced quote: `http {"path":"/api/search?q='"}`. A 500 (or 200)
24
+ that leaks SQL/driver text (`syntax error`, `unterminated quoted string`,
25
+ `psycopg2`, `ORA-`, `SQLSTATE`) proves the input reaches the query unescaped.
26
+
27
+ ## 3. Boolean oracle (the clean proof) — use diff
28
+ Build a false/true pair that changes ONLY the injected condition, and compare with
29
+ the differential engine:
30
+ `diff {"requests":[
31
+ {"path":"/api/search?q=zzz'and'1'='2"},
32
+ {"path":"/api/search?q=zzz'or'1'='1"}]}`.
33
+ `content_varies: true` / `distinct_bodies == 2` — the false branch returns nothing,
34
+ the true branch returns rows the term alone could not match — is the injection,
35
+ proven by behavior, not by a stack trace.
36
+
37
+ ## 4. Distinguish real injection from correct behavior
38
+ A parametrized/prepared endpoint treats the payload as a literal string: the pair
39
+ returns the SAME result and no error (`content_varies: false`). That is NOT a
40
+ finding. Also rule out a WAF echoing the payload rather than the DB executing it.
41
+
42
+ ## Evidence & severity
43
+ Cite the error response and/or the diff evidence id showing the boolean flip, and
44
+ name the condition you controlled. Severity critical (injection → auth bypass / data
45
+ disclosure). Prove controllability with one boolean/error pair; do NOT enumerate
46
+ tables or exfiltrate rows — that is beyond a non-destructive proof.
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: ssrf-oob
3
+ description: Prove server-side request forgery (and other blind server-initiated requests) with an out-of-band callback — the target fetches a unique URL you mint, and the collaborator records the hit.
4
+ when-to-use: a parameter takes a URL or host, or a feature makes the server fetch something (webhooks, url=, image/pdf/preview generators, link unfurling, import-from-URL, XML with external entities), and you need to prove the server can be made to call out.
5
+ surface: web
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # SSRF / blind out-of-band interaction
11
+
12
+ A non-destructive playbook for the `http`, `oob_mint`, and `oob_poll` ops. Goal:
13
+ prove the target can be made to issue a server-side request to a destination YOU
14
+ choose. THE RULE: no OOB callback, no SSRF critical — a reflected error or a guess
15
+ is not proof; a logged hit on your collaborator is.
16
+
17
+ ## 1. Mint a collaborator callback
18
+ `oob_mint {}` → `{token, url}`. The url is unique to this attempt; a request that
19
+ reaches it is unambiguously caused by your payload.
20
+
21
+ ## 2. Find the sink and plant the URL
22
+ Identify where the server might fetch a URL: `?url=`, `?next=`, `?image=`,
23
+ `?webhook=`, import/preview/unfurl features, or an XML body (XXE external entity).
24
+ Plant the minted url there, via `http`:
25
+ `http {"path":"/fetch?url=<minted url, URL-encoded>"}`.
26
+
27
+ ## 3. Poll for the interaction
28
+ `oob_poll {"token":"<token>"}`. `interaction: true` (hits ≥ 1) proves the target
29
+ made a server-side request to your collaborator — blind SSRF (or blind XXE if the
30
+ sink was an XML entity) CONFIRMED. `interaction: false` is NOT proof: the payload
31
+ may not have fired, the sink may validate the host, or egress may be filtered — try
32
+ another sink or encoding, but do not report a finding without a hit.
33
+
34
+ ## 4. Escalate the impact (still read-only)
35
+ If a hit lands, note what it demonstrates: internal network reachability, the
36
+ ability to hit cloud metadata (`169.254.169.254`), or internal-only services. You
37
+ can show reachability without exfiltrating secrets; do not pivot into destructive
38
+ internal actions.
39
+
40
+ ## 5. Distinguish real SSRF from benign behavior
41
+ An endpoint that merely echoes or client-side-renders the URL (no server fetch)
42
+ produces no hit — not SSRF. A server that fetches only an allowlisted host, or
43
+ refuses your collaborator, is behaving.
44
+
45
+ ## Evidence & severity
46
+ Cite the oob_mint token, the request that planted the url, and the oob_poll
47
+ evidence id showing the hit. Severity high, critical if it reaches cloud metadata
48
+ or internal admin surfaces.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: state-change-exploitation
3
+ description: Prove real-world impact by actually changing server state with a mutating request (POST/PUT/PATCH/DELETE) and then reading it back — privilege escalation, mass-assignment, forced object creation/deletion, workflow/state-machine abuse — the exploit-tier step beyond read-only access-control findings.
4
+ when-to-use: a read-only probe (http GET / diff) has shown a request SHOULD be refused but the endpoint also accepts a state-changing method, you suspect missing function-level authorization on a write (set role, approve, publish, transfer), mass-assignment of a protected field, or an object you can create/delete/modify that you should not — and the engagement is at the exploit tier.
5
+ surface: web
6
+ severity: high
7
+ tier: exploit
8
+ ---
9
+
10
+ # State-change exploitation (mutating requests)
11
+
12
+ The exploit-tier counterpart to the read-only access-control checks. Read-only
13
+ probing shows what you can *see*; this shows what you can *do* — it proves impact
14
+ by actually mutating state and confirming the change stuck. It rides the same `http`
15
+ op, which unlocks the mutating methods (POST/PUT/PATCH/DELETE with a `body`) only at
16
+ the exploit tier with destructiveActions authorized.
17
+
18
+ Mutation is real and can be destructive. The whole discipline here is proving
19
+ impact while keeping the blast radius minimal, reversible, and inside scope.
20
+
21
+ ## 1. Decide it is worth mutating
22
+ You already have a reason: a GET/`diff` probe showed an endpoint that responds to a
23
+ write method, or a field/flag that looks server-trusted. Confirm the write path
24
+ exists first (an `OPTIONS`, a 405-vs-401 on the method, the client JS that calls it)
25
+ before sending the mutation. Don't fire blind writes to enumerate.
26
+
27
+ ## 2. Capture the before-state
28
+ Read the thing you are about to change: `http GET` the resource (or `diff` it across
29
+ identities). Record its current value. Without a before-state you cannot prove your
30
+ request — not some background process — caused the change.
31
+
32
+ ## 3. Send the smallest mutation that proves the point
33
+ Send ONE mutating request with a minimal `body`. Prefer a change that is:
34
+ - **reversible** — set a flag you can unset, create a record you can delete;
35
+ - **a throwaway marker** — a uniquely-named object / a nonce value you can recognize,
36
+ never real user data;
37
+ - **non-destructive where a create/update proves it** — do not delete or corrupt real
38
+ records when creating a marker makes the same point.
39
+ Example (self-service privilege escalation): `http {"method":"POST",
40
+ "path":"/api/account/role","body":{"role":"admin"}}`.
41
+
42
+ ## 4. Confirm the change stuck (the proof)
43
+ Read it back: `http GET` the same resource and show the value MOVED to what you set
44
+ (role `user` → `admin`). The before/after pair is the finding. A 200 on the write
45
+ alone is NOT proof — many apps return 200 and ignore the change.
46
+
47
+ ## 5. Undo it
48
+ Reverse what you did (unset the flag, delete the marker, restore the prior value) and
49
+ confirm the restore. Leave the target as you found it. Record what you changed and
50
+ that you reverted it.
51
+
52
+ ## 6. Distinguish a real finding from a refused mutation (precision)
53
+ Only a finding if the state ACTUALLY changed when it should have been refused. A write
54
+ that returns 400/401/403, or returns 200 but leaves the follow-up read UNCHANGED, is
55
+ the control WORKING — do not claim it. Compare with the properly-guarded twin if one
56
+ exists: if the secure endpoint rejects the same mutation and its state does not move,
57
+ that is the correct behavior, not a vulnerability.
58
+
59
+ ## Scope & safety
60
+ Mutations go ONLY to the primary origin (the adapter refuses a mutating request aimed
61
+ at any other host). Never chain a mutation into something irreversible on shared state,
62
+ never mass-mutate, and keep to one proving change per finding. If the only way to
63
+ "prove" impact is irreversible destruction of real data, STOP and report the capability
64
+ (the reachable write + missing authorization) with the before-state and the refused-
65
+ elsewhere control as evidence, rather than causing the damage.
66
+
67
+ ## Evidence & severity
68
+ Cite the exact mutating request (method, path, body), the before read, the after read
69
+ showing the changed state, and the undo. Severity tracks what the change grants:
70
+ privilege escalation / auth-boundary writes and destructive or financial actions are
71
+ high-to-critical; a low-impact or self-only change is lower. No before/after proof,
72
+ no state-change finding.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: stateful-authz
3
+ description: Establish real sessions (log in) and test access control across accounts — prove horizontal (same-role, other owner) and vertical (low→high privilege) authz breaks with authenticated sessions, not just anonymous requests.
4
+ when-to-use: the app has a login and the assessment provides credentials/identities, and you need to test what one authenticated account can reach that it should not — another user's data, an admin-only function, a tenant boundary.
5
+ surface: web
6
+ severity: high
7
+ tier: safe-active
8
+ ---
9
+
10
+ # Stateful authorization testing
11
+
12
+ A non-destructive playbook for the `login`, `http`, and `diff` ops. Where
13
+ `authz-differential` compares anonymous vs. a static identity, this one drives REAL
14
+ sessions so you can test authenticated boundaries. `login` is the only POST allowed
15
+ (authentication is non-destructive); everything you prove after is read-only.
16
+
17
+ ## 1. Establish the sessions
18
+ For each account the engagement gives you, log in and name the session:
19
+ `login {"path":"/login","identity":"user_alice","session":"alice"}` (the identity's
20
+ secret is used; you never see it) or with an explicit `body`. Confirm
21
+ `authenticated: true`. Repeat for a second account and, if available, an admin.
22
+
23
+ ## 2. Baseline each session's legitimate access
24
+ As each session, read what it SHOULD reach: `http {"path":"/account","identity":"alice"}`.
25
+ Note the owner/tenant/role the response reflects. This is the control.
26
+
27
+ ## 3. Test horizontal access (same role, other owner)
28
+ As `alice`, request `bob`'s object by id/handle:
29
+ `http {"path":"/api/orders/<bob's id>","identity":"alice"}`. Or compare directly:
30
+ `diff {"requests":[
31
+ {"path":"/account","identity":"alice"},
32
+ {"path":"/account","identity":"bob"}]}` to show each returns its own data — then
33
+ the real test: does alice's session read bob's record? A 200 with bob's data under
34
+ alice's session is a horizontal authz break (BOLA).
35
+
36
+ ## 4. Test vertical access (privilege escalation)
37
+ As a low-privilege session, call an admin/privileged endpoint
38
+ (`http {"path":"/admin","identity":"alice"}`). Success where a 403 is expected is a
39
+ vertical break. Compare the low-priv and admin sessions with `diff` to show the
40
+ boundary is (not) enforced.
41
+
42
+ ## 5. Distinguish real breaks from correct behavior
43
+ Only a finding if a session reaches data/actions for another principal or a higher
44
+ role. Each session seeing its own data, and a 401/403 on others', is correct. A
45
+ public resource that answers everyone is not an authz break.
46
+
47
+ ## Evidence & severity
48
+ Cite the login (session established), the baseline, and the crossing read (or the
49
+ diff evidence id). Horizontal/vertical authz breaks are high, critical at scale or
50
+ for admin takeover. Read the proof record only; never modify another account.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: subdomain-takeover
3
+ description: Discover subdomains of the verified root domain and identify dangling DNS records vulnerable to subdomain takeover.
4
+ when-to-use: starting recon on a domain, or whenever you have a verified root domain and want to expand the attack surface beyond the primary origin.
5
+ surface: recon
6
+ severity: high
7
+ ---
8
+
9
+ # Subdomain discovery & takeover
10
+
11
+ A read-only playbook using the recon ops (`crt`, `dns`) and the `http` host-pivot.
12
+ WAF-independent: these hit external data sources, not the protected origin.
13
+
14
+ ## 1. Enumerate subdomains
15
+ - `crt {"domain":"<root>"}` — mine certificate-transparency logs; returns hostnames
16
+ of the root, including staging/admin/internal hosts leaked in certs.
17
+ - `dns {"name":"<root>"}` — records for the root (NS/MX/TXT often reveal providers
18
+ and verification tokens worth noting).
19
+ Collect the union of hostnames under the verified root.
20
+
21
+ ## 2. Triage each subdomain
22
+ For interesting hosts (admin*, staging*, internal*, vpn*, dev*, test*):
23
+ - `dns {"name":"<sub>"}` — resolve it. Watch the result flags:
24
+ - `"dangling": true` — a CNAME whose target no longer resolves (NXDOMAIN).
25
+ - `"takeover_candidate": {...}` — the CNAME points at a takeover-prone provider
26
+ (S3, GitHub Pages, Heroku, Azure, Netlify, Fastly, etc.).
27
+
28
+ ## 3. Confirm a takeover candidate (do NOT claim one)
29
+ A dangling/takeover_candidate flag is a CANDIDATE, not a confirmed takeover. Pivot
30
+ the http op to the host to corroborate:
31
+ `http {"path":"/","host":"<sub>"}` (anonymous — the adapter refuses creds off the
32
+ origin host). A provider "no such bucket / app not found / project not found" page
33
+ on a host your target still has a live CNAME to = a confirmed, claimable takeover.
34
+
35
+ ## 4. Probe live subdomains
36
+ For subdomains that DO resolve and serve, pivot `http` and apply the web playbooks
37
+ (exposed paths, JS secrets, unauth APIs) — staging/dev hosts are frequently less
38
+ hardened than production and are a rich anonymous surface.
39
+
40
+ ## Evidence & severity
41
+ - Confirmed takeover (dangling CNAME + provider orphan page): high/critical — an
42
+ attacker can claim the host and serve content under the target's name.
43
+ - Leaked internal host now reachable: scope by what it exposes.
44
+ Cite the CNAME chain (from `dns`) and the corroborating `http` response. Stay within
45
+ the verified root domain and its subdomains.
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: content-discovery
3
+ description: Map a web app's reachable endpoints and paths, preferring the app's OWN disclosures (specs, robots, JS) over blind guessing, read-only and non-destructive.
4
+ when-to-use: starting a web assessment, or whenever you need to expand the known endpoint surface of an origin or a discovered host before deep-diving.
5
+ surface: web
6
+ severity: info
7
+ ---
8
+
9
+ # Content & endpoint discovery (evidence-driven)
10
+
11
+ A read-only playbook for the `http` op (GET/HEAD + `grep`). Goal: enumerate the
12
+ reachable endpoints/paths of a host, preferring what the app REVEALS about itself
13
+ over blind wordlist guessing. Reasoning-first and non-destructive: thoroughness
14
+ comes from reading the app's own disclosures, not from high-rate brute force.
15
+
16
+ ## 1. Harvest the app's own inventory FIRST (ground truth, not guessing)
17
+ These enumerate endpoints the app itself publishes:
18
+ - `http {"path":"/robots.txt"}` and `/sitemap.xml`, `/sitemap_index.xml` — often
19
+ list admin/internal paths the authors meant to hide from crawlers.
20
+ - `/.well-known/security.txt`, `/.well-known/` — contact + sometimes more.
21
+ - OpenAPI/Swagger specs: `/openapi.json`, `/swagger.json`, `/v2/api-docs`,
22
+ `/api-docs`, `/swagger-ui.html`, `/redoc`. A spec is the full API map — fetch it
23
+ and read every path/operation it declares.
24
+ - Response headers: follow `Link:` and `Location:` values you see.
25
+ - **JS endpoint mining** (high yield): fetch each linked script and use `grep` over
26
+ the full body to pull route strings, e.g.
27
+ `http {"path":"/static/app.js","grep":"[\"']/(api|v1|v2|graphql|admin|internal|auth)[^\"']*"}`.
28
+ Also mine `app.js.map` source maps the same way. This recovers the real API the
29
+ front end calls — usually far more than any wordlist.
30
+ - GraphQL: if a `/graphql`-style endpoint exists, use GET introspection to list its
31
+ operations (see the graphql-exposure playbook).
32
+ - Parse the HTML: `<a href>`, `<form action>`, `data-*` URLs, inline `fetch(...)`.
33
+
34
+ ## 2. Fingerprint-guided candidates
35
+ From the `Server`/`X-Powered-By` headers, framework cookies, and JS libraries you
36
+ detect, consider framework-specific surfaces (judgmentally, only those that match):
37
+ Spring `/actuator`, WordPress `/wp-json` `/wp-admin`, Laravel/node `/.env`,
38
+ Django `/admin`, Next.js `/_next`, `/telescope`, `/debug`, `/graphql`.
39
+
40
+ ## 3. Common high-signal names — a capped JUDGMENT AID, not a brute force
41
+ ONLY after steps 1-2, and prioritized by what you learned, probe a SMALL curated set
42
+ of commonly-exposed paths: `admin`, `login`, `api`, `backup`/`backups`, `config`,
43
+ `.env`, `.git/config`, `.git/HEAD`, `.DS_Store`, `debug`, `status`, `health`,
44
+ `metrics`, `swagger`, `dump.sql`, `old`, `test`, `phpinfo`.
45
+ POSTURE: this is read-only and MUST NOT become a high-rate wordlist blast (that is
46
+ out of scope and crosses the non-DoS line). Probe a handful, observe, and let what
47
+ you find guide the next requests. Reason about which names fit the detected stack
48
+ rather than trying everything.
49
+
50
+ ## 4. Confirm, then hand off
51
+ For each discovered endpoint record status, content-type, whether auth is required,
52
+ and whether it exposes data/functionality that should be protected. Feed the
53
+ discovered endpoints into the other playbooks: unauthenticated APIs, IDOR on
54
+ id-keyed routes, exposed paths/secrets, GraphQL exposure.
55
+
56
+ ## Posture
57
+ GET/HEAD only; never modify. Evidence (step 1) over guessing (step 3). Keep request
58
+ volume modest and purposeful — breadth via reading what the app discloses, not via
59
+ brute force.