@murk-security/runner-darwin-x64 0.0.0-stage → 0.7.1
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.
- package/README.md +2 -2
- package/bin/harness +0 -0
- package/package.json +15 -4
- package/runners/basic_harness_run.py +1276 -0
- package/runners/checklists/anonymous.json +24 -0
- package/runners/cve_feed.json +53 -0
- package/runners/prod_env.py +1686 -0
- package/runners/profile.py +307 -0
- package/runners/recon.py +396 -0
- package/runners/remediate.py +429 -0
- package/runners/report.py +365 -0
- package/runners/skills/api/idor-enumeration/SKILL.md +42 -0
- package/runners/skills/exploit/authz-differential/SKILL.md +62 -0
- package/runners/skills/exploit/business-logic/SKILL.md +53 -0
- package/runners/skills/exploit/client-side-exploitation/SKILL.md +63 -0
- package/runners/skills/exploit/client-side-rendering/SKILL.md +57 -0
- package/runners/skills/exploit/credential-chaining/SKILL.md +56 -0
- package/runners/skills/exploit/known-cve-validation/SKILL.md +46 -0
- package/runners/skills/exploit/path-traversal/SKILL.md +44 -0
- package/runners/skills/exploit/reflected-xss/SKILL.md +43 -0
- package/runners/skills/exploit/service-exploitation/SKILL.md +64 -0
- package/runners/skills/exploit/service-probing/SKILL.md +51 -0
- package/runners/skills/exploit/sqli-inference/SKILL.md +46 -0
- package/runners/skills/exploit/ssrf-oob/SKILL.md +48 -0
- package/runners/skills/exploit/state-change-exploitation/SKILL.md +72 -0
- package/runners/skills/exploit/stateful-authz/SKILL.md +50 -0
- package/runners/skills/recon/subdomain-takeover/SKILL.md +45 -0
- package/runners/skills/web/content-discovery/SKILL.md +59 -0
- package/runners/skills/web/graphql-exposure/SKILL.md +40 -0
- 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 `<xss7f3>` (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.
|