blitzstrike 2.4.74 → 2.4.75
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/CHANGELOG.md +28 -0
- package/dist/index.js +128 -8
- package/intelligence/cwe_map.json +12 -0
- package/intelligence/patterns.json +68 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to Blitz Strike are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [2.4.75] — 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Fixed — detection FP/FN remediation (Hackbot Arena benchmark-driven)
|
|
11
|
+
|
|
12
|
+
Four fixes driven by the 30-lab detection benchmark (TP/FP/FN):
|
|
13
|
+
|
|
14
|
+
1. **`detectRouteAuthz` decorator/middleware auth + public-route skip.**
|
|
15
|
+
Recognizes `require_auth` / `auth_required` / `jwt_required` / `token_required` /
|
|
16
|
+
`verify_token` / `current_user` / `before_request` as auth enforcement; skips
|
|
17
|
+
public-by-design routes (register/login/health/status/static/docs/...). Also
|
|
18
|
+
fixed a cross-framework FP: the express detector was matching Flask
|
|
19
|
+
`@app.get/post` decorators (no `@` exclusion) — added `(?<!@)` and expanded the
|
|
20
|
+
flask entry to `@app.(route|get|post|put|delete|patch)`. `missing_authz` FP
|
|
21
|
+
dropped ~23 → 19 labs.
|
|
22
|
+
2. **Node/Python sinks.** Added `subprocess.run/call/Popen/check_output`,
|
|
23
|
+
`os.system/popen`, `child_process.exec/spawn/execSync`, `sqlx::query(&format!`,
|
|
24
|
+
`sequelize.query`, `knex.raw`, `requests.get/post/put`, `urllib.request.urlopen`,
|
|
25
|
+
`httpx.get`, `render_template_string`/`Template` to the sink scanner — so Python
|
|
26
|
+
command injection (labs24), SSRF (labs07/10/15), and Node SQLi now register.
|
|
27
|
+
3. **Two new detectors.** `jwt_alg_confusion` (verifier accepting RS256 + HS256
|
|
28
|
+
with public-key HMAC — CWE-347, caught labs03) and `cache_deception`
|
|
29
|
+
(cookie-less static cache key over a session-authenticated backend — CWE-525,
|
|
30
|
+
caught labs01). Also enhanced `ssti` to catch Liquid `Template(var)` from stored
|
|
31
|
+
input (labs23). Both wired through cwe_map/patterns/severity/synonyms/harness.
|
|
32
|
+
4. **`hardcoded_secret` flag suppression.** Suppresses `FLAG{...}` literals, the
|
|
33
|
+
bare `FLAG` env marker, and quoted-prefix concatenations (`'usr_' + random`) so
|
|
34
|
+
seeded CTF flags and runtime-built values aren't reported as CWE-798.
|
|
35
|
+
|
|
8
36
|
## [2.4.74] — 2026-09-17
|
|
9
37
|
|
|
10
38
|
### Fixed — live_recon tech fingerprint false-positive (benchmark-driven)
|
package/dist/index.js
CHANGED
|
@@ -7228,7 +7228,30 @@ var init_scanner = __esm(() => {
|
|
|
7228
7228
|
"print ": "XSS (reflected output)",
|
|
7229
7229
|
"printf(": "XSS (reflected output)",
|
|
7230
7230
|
"header(": "Open redirect / header injection",
|
|
7231
|
-
"wp_redirect(": "Open redirect"
|
|
7231
|
+
"wp_redirect(": "Open redirect",
|
|
7232
|
+
"child_process.exec(": "RCE (command execution)",
|
|
7233
|
+
"child_process.spawn(": "RCE (command execution)",
|
|
7234
|
+
"child_process.execSync(": "RCE (command execution)",
|
|
7235
|
+
"child_process.execFile(": "RCE (command execution)",
|
|
7236
|
+
"sqlx::query(&format!": "SQL injection (runtime string-built query)",
|
|
7237
|
+
"sqlx::query_as(&format!": "SQL injection (runtime string-built query)",
|
|
7238
|
+
"sequelize.query(": "SQL injection (raw query)",
|
|
7239
|
+
"knex.raw(": "SQL injection (raw query)",
|
|
7240
|
+
"subprocess.run(": "RCE (command execution)",
|
|
7241
|
+
"subprocess.call(": "RCE (command execution)",
|
|
7242
|
+
"subprocess.Popen(": "RCE (command execution)",
|
|
7243
|
+
"subprocess.check_output(": "RCE (command execution)",
|
|
7244
|
+
"os.system(": "RCE (command execution)",
|
|
7245
|
+
"os.popen(": "RCE (command execution)",
|
|
7246
|
+
"requests.get(": "SSRF (unvalidated URL fetch)",
|
|
7247
|
+
"requests.post(": "SSRF (unvalidated URL fetch)",
|
|
7248
|
+
"requests.put(": "SSRF (unvalidated URL fetch)",
|
|
7249
|
+
"urllib.request.urlopen(": "SSRF (unvalidated URL fetch)",
|
|
7250
|
+
"urllib.urlopen(": "SSRF (unvalidated URL fetch)",
|
|
7251
|
+
"httpx.get(": "SSRF (unvalidated URL fetch)",
|
|
7252
|
+
"render_template_string(": "SSTI (server-side template injection)",
|
|
7253
|
+
"jinja2.Template(": "SSTI (server-side template injection)",
|
|
7254
|
+
"Template(": "SSTI (server-side template injection)"
|
|
7232
7255
|
};
|
|
7233
7256
|
AUTH_GATES = [
|
|
7234
7257
|
"check_ajax_referer",
|
|
@@ -15934,6 +15957,8 @@ function detectRouteAuthz(code, file, out) {
|
|
|
15934
15957
|
const ln = lineNoOf(code, m.index);
|
|
15935
15958
|
const window2 = ls.slice(Math.max(0, ln - d.window), Math.min(ls.length, ln + d.window + 1)).join(`
|
|
15936
15959
|
`);
|
|
15960
|
+
if (PUBLIC_ROUTE_RE.test(m[0] + " " + window2))
|
|
15961
|
+
continue;
|
|
15937
15962
|
if (d.auth.test(window2))
|
|
15938
15963
|
continue;
|
|
15939
15964
|
out.push({
|
|
@@ -15961,7 +15986,7 @@ function detectMissingAuthz(code, file) {
|
|
|
15961
15986
|
return true;
|
|
15962
15987
|
});
|
|
15963
15988
|
}
|
|
15964
|
-
var FRAMEWORKS, INDIRECT_AUTH, AUTH_RE, NONCE_RE, DANGEROUS_OP_RE, ROUTE_DETECTORS;
|
|
15989
|
+
var FRAMEWORKS, INDIRECT_AUTH, AUTH_RE, NONCE_RE, DANGEROUS_OP_RE, ROUTE_DETECTORS, PUBLIC_ROUTE_RE;
|
|
15965
15990
|
var init_framework_security = __esm(() => {
|
|
15966
15991
|
FRAMEWORKS = {
|
|
15967
15992
|
wordpress: {
|
|
@@ -16042,13 +16067,14 @@ var init_framework_security = __esm(() => {
|
|
|
16042
16067
|
ROUTE_DETECTORS = [
|
|
16043
16068
|
{ framework: "laravel", entry: /Route::(?:get|post|put|patch|delete|any|match|resource)\s*\(\s*['"][^'"]+['"]/g, auth: /->middleware\s*\(\s*['"]auth|middleware\s*\(\s*\[[^\]]*['"]auth|->can\s*\(|\$this->authorize|Gate::|authorize\s*\(|@can\b/, window: 2 },
|
|
16044
16069
|
{ framework: "django", entry: /@api_view\s*\(|def\s+\w+\s*\(\s*request\s*[,)]/g, auth: /login_required|permission_required|user_passes_test|LoginRequiredMixin|PermissionRequiredMixin|permission_classes|IsAuthenticated|has_permission/, window: 3 },
|
|
16045
|
-
{ framework: "flask", entry: /@(?:app|bp|blueprint)\.route\s*\(/g, auth: /login_required|current_user
|
|
16046
|
-
{ framework: "express", entry: /(?:app|router)\.(?:get|post|put|delete|patch|use)\s*\(\s*['"][^'"]+['"]/g, auth: /requireAuth|authenticate|passport\.authenticate|req\.user|isAuthenticated|ensureAuthenticated|requireLogin|\.authorize|authMiddleware/, window: 2 },
|
|
16070
|
+
{ framework: "flask", entry: /@(?:app|bp|blueprint)\.(?:route|get|post|put|delete|patch|before_request)\s*\(/g, auth: /login_required|require_auth|auth_required|jwt_required|token_required|current_user|is_authenticated|before_request|verify_token|permission_required/, window: 3 },
|
|
16071
|
+
{ framework: "express", entry: /(?<!@)(?:app|router)\.(?:get|post|put|delete|patch|use)\s*\(\s*['"][^'"]+['"]/g, auth: /requireAuth|require_auth|authenticate|passport\.authenticate|req\.user|isAuthenticated|ensureAuthenticated|requireLogin|\.authorize|authMiddleware|verifyToken|verify_token|jwt\.verify/, window: 2 },
|
|
16047
16072
|
{ framework: "spring", entry: /@(?:Get|Post|Put|Delete|Patch|Request)Mapping\s*\(/g, auth: /@PreAuthorize|@Secured|@RolesAllowed|hasRole|hasAuthority|isAuthenticated|SecurityContextHolder/, window: 2 },
|
|
16048
16073
|
{ framework: "symfony", entry: /#\[(?:Route|Get|Post|Put|Delete|Patch)\b|@Route\s*\(/g, auth: /is_granted|denyAccessUnlessGranted|->isGranted\s*\(|IsGranted|Voter/, window: 2 },
|
|
16049
16074
|
{ framework: "aspnet", entry: /\[Http(?:Get|Post|Put|Delete|Patch)\b/g, auth: /\[Authorize|User\.Identity|IsInRole|AuthorizeAsync|\[AllowAnonymous|IAuthorizationService/, window: 2 },
|
|
16050
16075
|
{ framework: "rails", entry: /class\s+\w+\s*<\s*ApplicationController/g, auth: /before_action\s+:authenticate|before_action\s+:authorize|authenticate_user!|authorize\s+|current_user|authenticate/, window: 6 }
|
|
16051
16076
|
];
|
|
16077
|
+
PUBLIC_ROUTE_RE = /['"`]\/?(?:(?:api|v1|v2|o|auth|adminapi)\/)?(?:register|login|logout|signup|signin|forgot-password|reset-password|health|healthz|ready|status|version|ping|static|public|assets|css|js|img|favicon|echo|config|docs|openapi|swagger|robots|sitemap|graphql|graphiql|query|upload|callback|webhook)[/'"?]|['"`]\/?['"`]$/i;
|
|
16052
16078
|
});
|
|
16053
16079
|
|
|
16054
16080
|
// src/complex-bugs.ts
|
|
@@ -16267,6 +16293,8 @@ function detectComplexBugs(code, file) {
|
|
|
16267
16293
|
detectMlSupplyChain(code, file, out);
|
|
16268
16294
|
detectRustUnsafe(code, file, out);
|
|
16269
16295
|
detectRustFormatString(code, file, out);
|
|
16296
|
+
detectJwtAlgConfusion(code, file, out);
|
|
16297
|
+
detectCacheDeception(code, file, out);
|
|
16270
16298
|
out.push(...detectWrongSanitizer(code, file));
|
|
16271
16299
|
out.push(...detectMissingAuthz(code, file));
|
|
16272
16300
|
const seen = new Set;
|
|
@@ -16427,6 +16455,21 @@ function detectSsti(code, file, out) {
|
|
|
16427
16455
|
detail: "User input reaches a template render with no sandbox — SSTI (`{{7*7}}`, `${7*7}`, `#{7*7}`) → RCE."
|
|
16428
16456
|
});
|
|
16429
16457
|
}
|
|
16458
|
+
if (/from\s+liquid\s+import\s+Template|liquid\.Template|import\s+liquid/.test(code)) {
|
|
16459
|
+
const tRe = /\bTemplate\s*\(\s*([a-z_][a-z0-9_]*)\s*\)/g;
|
|
16460
|
+
while ((m = tRe.exec(code)) !== null) {
|
|
16461
|
+
const ln = lineNo2(code, m.index);
|
|
16462
|
+
out.push({
|
|
16463
|
+
file,
|
|
16464
|
+
line: ln,
|
|
16465
|
+
type: "ssti",
|
|
16466
|
+
category: "SSTI (Liquid template source from stored input)",
|
|
16467
|
+
severity: "high",
|
|
16468
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16469
|
+
detail: "A Liquid template is compiled from a variable (`Template(var)`) with no template-syntax screening — profile/stored input becomes template source, so `{{ ... }}` tags execute server-side (SSTI → data exfiltration / RCE). Render with an explicit, static template and pass user values only as variables."
|
|
16470
|
+
});
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16430
16473
|
}
|
|
16431
16474
|
function detectFileUploadRce(code, file, out) {
|
|
16432
16475
|
code = stripCommentsOnly(code);
|
|
@@ -16525,6 +16568,10 @@ function detectHardcodedSecret(code, file, out) {
|
|
|
16525
16568
|
continue;
|
|
16526
16569
|
if (/\$[A-Za-z_]|[{}\[\]]|process\.env|getenv|os\.environ|config\s*\(|env\s*\(/.test(valueRaw))
|
|
16527
16570
|
continue;
|
|
16571
|
+
if (/^FLAG\{[a-z0-9_\-]+\}$/i.test(value) || /^FLAG$/i.test(value))
|
|
16572
|
+
continue;
|
|
16573
|
+
if (qm && /^["'][^"']+["']\s*\+/.test(valueRaw))
|
|
16574
|
+
continue;
|
|
16528
16575
|
const ln = lineNo2(code, m.index);
|
|
16529
16576
|
flaggedLines.add(ln);
|
|
16530
16577
|
let sev = "high";
|
|
@@ -16795,6 +16842,48 @@ function detectRustFormatString(code, file, out) {
|
|
|
16795
16842
|
});
|
|
16796
16843
|
}
|
|
16797
16844
|
}
|
|
16845
|
+
function detectJwtAlgConfusion(code, file, out) {
|
|
16846
|
+
const clean = stripCommentsOnly(code);
|
|
16847
|
+
const hasRs256 = /\bRS256\b/.test(clean);
|
|
16848
|
+
const hasHs256 = /\bHS256\b/.test(clean);
|
|
16849
|
+
if (!hasRs256 || !hasHs256)
|
|
16850
|
+
return;
|
|
16851
|
+
const hmacPublicKey = /createHmac\s*\(\s*['"]sha256['"]\s*,\s*(?:[A-Za-z_][\w.]*\.(?:publicKey|public_key|PUBLIC_KEY|verifying_key|verifyingKey)|publicKey|public_key|PUBLIC_KEY)/.test(clean) || /algorithms\s*=\s*\[[^\]]*['"]HS256['"]\s*,\s*['"]RS256['"]/.test(clean) || /algorithms\s*=\s*\[[^\]]*['"]RS256['"]\s*,\s*['"]HS256['"]/.test(clean);
|
|
16852
|
+
if (!hmacPublicKey)
|
|
16853
|
+
return;
|
|
16854
|
+
const ls = lines2(code);
|
|
16855
|
+
const ln = lineNo2(code, clean.search(/\bHS256\b/));
|
|
16856
|
+
out.push({
|
|
16857
|
+
file,
|
|
16858
|
+
line: ln,
|
|
16859
|
+
type: "jwt_alg_confusion",
|
|
16860
|
+
category: "JWT algorithm confusion (RS256 -> HS256)",
|
|
16861
|
+
severity: "critical",
|
|
16862
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16863
|
+
detail: "The JWT verifier dispatches on the token's alg header and accepts BOTH RS256 and HS256, with the HS256 HMAC secret derived from public/shared key material. An attacker sets alg=HS256 and signs with the public key to forge a valid token (CVE-2015-9235 family). Pin the algorithm server-side (allow only RS256) and never reuse asymmetric public keys as symmetric HMAC secrets."
|
|
16864
|
+
});
|
|
16865
|
+
}
|
|
16866
|
+
function detectCacheDeception(code, file, out) {
|
|
16867
|
+
const clean = stripCommentsOnly(code);
|
|
16868
|
+
const staticCache = /proxy_cache\b|proxy_cache_path\b|proxy_cache_key\b/.test(clean);
|
|
16869
|
+
if (!staticCache)
|
|
16870
|
+
return;
|
|
16871
|
+
const cacheKeyNoCookie = /proxy_cache_key\s+[^;]*\$scheme[^;]*\$request_uri[^;]*;/.test(clean) && !/proxy_cache_key\s+[^;]*\$cookie/.test(clean);
|
|
16872
|
+
const staticExt = /location\s+~?\*?\s+\\?\.[a-z|()]*(css|js|png|jpg|jpeg|gif|ico|woff|svg|map)/.test(clean) || /location\s+~[*\s]*\\\./.test(clean);
|
|
16873
|
+
if (!cacheKeyNoCookie || !staticExt)
|
|
16874
|
+
return;
|
|
16875
|
+
const ls = lines2(code);
|
|
16876
|
+
const ln = lineNo2(code, clean.search(/proxy_cache_key\b/));
|
|
16877
|
+
out.push({
|
|
16878
|
+
file,
|
|
16879
|
+
line: ln,
|
|
16880
|
+
type: "cache_deception",
|
|
16881
|
+
category: "Web cache deception (cookie-less static cache key)",
|
|
16882
|
+
severity: "high",
|
|
16883
|
+
evidence: (ls[ln - 1] ?? "").trim(),
|
|
16884
|
+
detail: "The reverse proxy caches static-extension URLs using a cache key that excludes the session cookie, while a backend route (often a greedy regex like /account(/.*)?) returns per-session private data for such URLs. An attacker can poison the cache with a victim's session response and re-fetch it anonymously. Include the cookie in the cache key, or don't cache cookie-authenticated responses."
|
|
16885
|
+
});
|
|
16886
|
+
}
|
|
16798
16887
|
var SOURCE_TOKENS3, MAGIC_METHODS, SECRET_FIELD_RE, SECRET_PLACEHOLDER_RE, ENTROPY_EXCLUDE_VAR;
|
|
16799
16888
|
var init_complex_bugs = __esm(() => {
|
|
16800
16889
|
init_security_state();
|
|
@@ -17002,8 +17091,29 @@ $r = openai.chat.completions.create(['messages' => [['role' => 'user', 'content'
|
|
|
17002
17091
|
id: "rust-format-string",
|
|
17003
17092
|
detector: "rust_format_string",
|
|
17004
17093
|
file: "main.rs",
|
|
17005
|
-
positive: `fn f() { println!(user_input); }
|
|
17006
|
-
|
|
17094
|
+
positive: `fn f() { println!(user_input); }
|
|
17095
|
+
`,
|
|
17096
|
+
negative: `fn f() { println!("{}", user_input); }
|
|
17097
|
+
`
|
|
17098
|
+
},
|
|
17099
|
+
{
|
|
17100
|
+
id: "jwt-alg-confusion",
|
|
17101
|
+
detector: "jwt_alg_confusion",
|
|
17102
|
+
file: "auth.js",
|
|
17103
|
+
positive: `function verify(t){ if(t.alg==='RS256'){ verifier.verify(KEYS.publicKey); } else if(t.alg==='HS256'){ crypto.createHmac('sha256', KEYS.publicKey).update(d).digest(); } }
|
|
17104
|
+
`,
|
|
17105
|
+
negative: `function verify(t){ if(t.alg==='RS256'){ verifier.verify(KEYS.publicKey); } else { throw new Error('unsupported alg'); } }
|
|
17106
|
+
`
|
|
17107
|
+
},
|
|
17108
|
+
{
|
|
17109
|
+
id: "cache-deception",
|
|
17110
|
+
detector: "cache_deception",
|
|
17111
|
+
file: "nginx.conf",
|
|
17112
|
+
positive: `location ~* \\.(css|js|png|jpg|gif|ico|svg)$ { proxy_cache ctf_cache; proxy_cache_valid 200 5m; }
|
|
17113
|
+
proxy_cache_key "$scheme$request_method$request_uri";
|
|
17114
|
+
`,
|
|
17115
|
+
negative: `location / { proxy_pass http://backend; }
|
|
17116
|
+
`
|
|
17007
17117
|
}
|
|
17008
17118
|
];
|
|
17009
17119
|
});
|
|
@@ -58313,7 +58423,15 @@ var ALIASES = {
|
|
|
58313
58423
|
transmute: "rust_unsafe",
|
|
58314
58424
|
"rust format string": "rust_format_string",
|
|
58315
58425
|
"format string": "rust_format_string",
|
|
58316
|
-
"format string injection": "rust_format_string"
|
|
58426
|
+
"format string injection": "rust_format_string",
|
|
58427
|
+
"jwt algorithm confusion": "jwt_alg_confusion",
|
|
58428
|
+
"jwt alg confusion": "jwt_alg_confusion",
|
|
58429
|
+
"alg confusion": "jwt_alg_confusion",
|
|
58430
|
+
"rs256 hs256": "jwt_alg_confusion",
|
|
58431
|
+
"web cache deception": "cache_deception",
|
|
58432
|
+
"cache deception": "cache_deception",
|
|
58433
|
+
"cache poisoning": "cache_deception",
|
|
58434
|
+
"cache key injection": "cache_deception"
|
|
58317
58435
|
};
|
|
58318
58436
|
function normalize(term) {
|
|
58319
58437
|
return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -58703,7 +58821,9 @@ var VRT_MAP = {
|
|
|
58703
58821
|
dependency_confusion: { vrt: "insecure_design > dependency_confusion", priority: "P2", severity: "high", rationale: "An internal package resolves from a squattable public registry → attacker-controlled code in the build." },
|
|
58704
58822
|
ml_supply_chain: { vrt: "injection > insecure_deserialization", priority: "P1", severity: "critical", rationale: "Untrusted model loading via pickle (torch/joblib/numpy) → arbitrary code execution on load." },
|
|
58705
58823
|
rust_unsafe: { vrt: "insecure_design > unsafe_memory_operations", priority: "P3", severity: "medium", rationale: "transmute/from_raw_parts/assume_init bypass the borrow checker — a hint that needs bounds verification; memory corruption if attacker-controlled." },
|
|
58706
|
-
rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." }
|
|
58824
|
+
rust_format_string: { vrt: "injection > format_string_injection", priority: "P2", severity: "high", rationale: "An attacker-controlled format string can read or corrupt memory via format specifiers." },
|
|
58825
|
+
jwt_alg_confusion: { vrt: "authentication > jwt_algorithm_confusion", priority: "P1", severity: "critical", rationale: "A verifier accepting HS256 with public-key HMAC lets an attacker forge admin tokens (CVE-2015-9235)." },
|
|
58826
|
+
cache_deception: { vrt: "cryptographic_failure > web_cache_deception", priority: "P2", severity: "high", rationale: "Cookie-less static cache keys leak per-session private responses to anonymous callers." }
|
|
58707
58827
|
};
|
|
58708
58828
|
function severityCalibrate(input) {
|
|
58709
58829
|
const type = input.type?.toLowerCase() ?? "";
|
|
@@ -330,6 +330,18 @@
|
|
|
330
330
|
"id": "rust_format_string",
|
|
331
331
|
"cwe": "CWE-134",
|
|
332
332
|
"name": "Use of Externally-Controlled Format String"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"id": "jwt_alg_confusion",
|
|
336
|
+
"cwe": "CWE-347",
|
|
337
|
+
"name": "Improper Verification of Cryptographic Signature",
|
|
338
|
+
"category": "authentication"
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"id": "cache_deception",
|
|
342
|
+
"cwe": "CWE-525",
|
|
343
|
+
"name": "Use of Web Browser Cache Containing Sensitive Information",
|
|
344
|
+
"category": "cryptographic_failure"
|
|
333
345
|
}
|
|
334
346
|
]
|
|
335
347
|
}
|
|
@@ -691,6 +691,74 @@
|
|
|
691
691
|
"any panic path that formats attacker input",
|
|
692
692
|
"logging/telemetry that echoes request data"
|
|
693
693
|
]
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
"id": "jwt_alg_confusion",
|
|
697
|
+
"name": "JWT Algorithm Confusion (RS256 -> HS256)",
|
|
698
|
+
"cwe": "CWE-347",
|
|
699
|
+
"severity": "critical",
|
|
700
|
+
"signature": "alg-dispatch verifier accepting RS256 AND HS256 with public/shared HMAC key",
|
|
701
|
+
"chains": [
|
|
702
|
+
"jwt"
|
|
703
|
+
],
|
|
704
|
+
"references": [
|
|
705
|
+
"CVE-2015-9235",
|
|
706
|
+
"OWASP ASVS V3.5.2"
|
|
707
|
+
],
|
|
708
|
+
"real_world": "A hand-written JWT verifier dispatches on the token's alg header and accepts BOTH RS256 (asymmetric) and HS256 (symmetric HMAC), with the HS256 secret derived from the RSA public key. Setting alg=HS256 and signing with the public key forges a valid token.",
|
|
709
|
+
"detection_signatures": [
|
|
710
|
+
"alg-dispatch verifier accepting RS256 AND HS256",
|
|
711
|
+
"createHmac('sha256', <publicKey>) alongside createVerify('RSA-SHA256')",
|
|
712
|
+
"pyjwt decode(..., algorithms=['HS256','RS256'])"
|
|
713
|
+
],
|
|
714
|
+
"chain_templates": [
|
|
715
|
+
"jwt_alg_confusion_forgery",
|
|
716
|
+
"jwt_algorithm_confusion"
|
|
717
|
+
],
|
|
718
|
+
"bypass_techniques": [
|
|
719
|
+
"public-key-to-HMAC confusion",
|
|
720
|
+
"kid/jku header injection to point the verifier at attacker keys",
|
|
721
|
+
"alg:none / empty signature when the verifier also accepts it"
|
|
722
|
+
],
|
|
723
|
+
"crown_jewels": [
|
|
724
|
+
"the RSA public key (published at /.well-known/jwks.json)",
|
|
725
|
+
"admin role claims overridden by token claims",
|
|
726
|
+
"treasury/admin endpoints gated only by a role claim"
|
|
727
|
+
]
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
"id": "cache_deception",
|
|
731
|
+
"name": "Web Cache Deception",
|
|
732
|
+
"cwe": "CWE-525",
|
|
733
|
+
"severity": "high",
|
|
734
|
+
"signature": "reverse-proxy caches static-extension URLs with a cookie-less cache key over a session-authenticated backend",
|
|
735
|
+
"chains": [
|
|
736
|
+
"cache_deception"
|
|
737
|
+
],
|
|
738
|
+
"references": [
|
|
739
|
+
"CWE-525",
|
|
740
|
+
"Omer Gil — Web Cache Deception Attack",
|
|
741
|
+
"OWASP WSTG-ATHN-03"
|
|
742
|
+
],
|
|
743
|
+
"real_world": "A reverse proxy caches static-extension URLs (.css/.js/.png) with a cache key that omits the session cookie, while a backend route (often a greedy regex like /account(/.*)?) serves per-session private data for those URLs. An attacker submits a victim-visited URL, the bot's private response is cached, then re-fetched anonymously.",
|
|
744
|
+
"detection_signatures": [
|
|
745
|
+
"proxy_cache_key without $cookie over a session-authenticated backend",
|
|
746
|
+
"location ~* \\.(css|js|png|...) with proxy_cache",
|
|
747
|
+
"greedy backend route regex + static-extension passthrough"
|
|
748
|
+
],
|
|
749
|
+
"chain_templates": [
|
|
750
|
+
"cache_poisoning_xss"
|
|
751
|
+
],
|
|
752
|
+
"bypass_techniques": [
|
|
753
|
+
"append a static extension to an authenticated path",
|
|
754
|
+
"path delimiters (; / ..) that still match the route regex",
|
|
755
|
+
"cache-key normalization differences (trailing slash, percent-encoding)"
|
|
756
|
+
],
|
|
757
|
+
"crown_jewels": [
|
|
758
|
+
"per-session private profile/account JSON",
|
|
759
|
+
"api_key / session tokens in the cached body",
|
|
760
|
+
"any victim-session data cached under a static-extension URL"
|
|
761
|
+
]
|
|
694
762
|
}
|
|
695
763
|
]
|
|
696
764
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.75",
|
|
4
4
|
"description": "Blitz Strike — a universal MCP security-audit toolbelt. BLITZ sweeps the attack surface, EAGLE-EYE traces source-to-sink, STRIKE verifies live. 57 attack chains, 130-tool catalog, intelligence data layer. One server, every agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|