blitzstrike 2.4.73 → 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 CHANGED
@@ -5,6 +5,49 @@ 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
+
36
+ ## [2.4.74] — 2026-09-17
37
+
38
+ ### Fixed — live_recon tech fingerprint false-positive (benchmark-driven)
39
+
40
+ - Tech detection now uses header + cookie signals FIRST (authoritative), then
41
+ body regex as fallback, via a new `detectTech(headers, body)`:
42
+ - `x-powered-by: Express` / `connect.sid` cookie → nodejs (was missed).
43
+ - `laravel_session` / `XSRF-TOKEN` cookie → laravel.
44
+ - `PHPSESSID` → php, `csrftoken`+`sessionid` → django, `rack.session` → rails.
45
+ - Removed the over-broad `_token` / `csrf-token` → laravel body signature (those
46
+ are generic CSRF tokens, present in Node/Express apps too) — this caused the
47
+ Hackbot Arena labs03 (Node/Express) to be mis-flagged as "laravel".
48
+ - `gunicorn` no longer implies "flask" (it's a generic WSGI server shared by
49
+ Flask/Django/FastAPI); only `werkzeug` maps to flask.
50
+
8
51
  ## [2.4.73] — 2026-09-17
9
52
 
10
53
  ### Changed — crack_hash wordlist support (benchmark-driven fix)
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\.is_authenticated|permission_required/, window: 3 },
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); }\\n`,
17006
- negative: `fn f() { println!("{}", user_input); }\\n`
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
  });
@@ -35884,6 +35994,59 @@ function extractVersion(headers, body) {
35884
35994
  }
35885
35995
  return out;
35886
35996
  }
35997
+ function detectTech(headers, body) {
35998
+ const techs = new Set;
35999
+ const low = (s) => (s ?? "").toLowerCase();
36000
+ const powered = low(headers["x-powered-by"]);
36001
+ const server = low(headers["server"]);
36002
+ const cookies = low(headers["set-cookie"]);
36003
+ const generator = low(headers["x-generator"]);
36004
+ if (/express|node\b|nodejs/i.test(powered))
36005
+ techs.add("nodejs");
36006
+ if (/^php\b|^php\//i.test(powered))
36007
+ techs.add("php");
36008
+ if (/asp\.net/i.test(powered) || (headers["x-aspnet-version"] ?? ""))
36009
+ techs.add("aspnet");
36010
+ if (/werkzeug/i.test(server))
36011
+ techs.add("flask");
36012
+ if (/drupal/i.test(generator))
36013
+ techs.add("drupal");
36014
+ if (/laravel_session|xsrf-token/i.test(cookies))
36015
+ techs.add("laravel");
36016
+ if (/phpsessid/i.test(cookies))
36017
+ techs.add("php");
36018
+ if (/connect\.sid/i.test(cookies))
36019
+ techs.add("nodejs");
36020
+ if (/csrftoken/i.test(cookies) && /sessionid/i.test(cookies))
36021
+ techs.add("django");
36022
+ if (/rack\.session/i.test(cookies))
36023
+ techs.add("rails");
36024
+ const bodySig = [
36025
+ [/wp-content|wp-includes|wp-json/i, "wordpress"],
36026
+ [/wp-login\.php|wp-admin/i, "wordpress"],
36027
+ [/powered by joomla|com_content/i, "joomla"],
36028
+ [/laravel_session|xsrf-token|\blaravel\b/i, "laravel"],
36029
+ [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
36030
+ [/angular|ng-version/i, "angular"],
36031
+ [/vue|__vue__|v-data/i, "vue"],
36032
+ [/django|csrftoken|__debug__/i, "django"],
36033
+ [/ruby on rails|rails/i, "rails"],
36034
+ [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
36035
+ [/phpBB|phpbb/i, "phpbb"],
36036
+ [/mybb|mybb/i, "mybb"],
36037
+ [/drupal|Drupal\.settings/i, "drupal"],
36038
+ [/magento|Mage\./i, "magento"],
36039
+ [/shopify|cdn\.shopify/i, "shopify"],
36040
+ [/grafana|kibana|elastic/i, "grafana-kibana"],
36041
+ [/node\.js|express/i, "nodejs"],
36042
+ [/flask|werkzeug/i, "flask"],
36043
+ [/spring|actuator/i, "spring"]
36044
+ ];
36045
+ for (const [re, t] of bodySig)
36046
+ if (re.test(body))
36047
+ techs.add(t);
36048
+ return [...techs];
36049
+ }
35887
36050
  function correlateIntel(techs, versionList, portList) {
35888
36051
  const tech = techs.map((t) => ({ tech: t, ...techCorrelation(t) }));
35889
36052
  const versions = versionList.map((v) => ({
@@ -35919,28 +36082,7 @@ async function liveRecon(target, includeActive = false) {
35919
36082
  body_size: root.body.length
35920
36083
  };
35921
36084
  const waf = detectWaf(root.headers, root.body);
35922
- const techSig = [
35923
- [/wp-content|wp-includes|wp-json/i, "wordpress"],
35924
- [/wp-login\.php|wp-admin/i, "wordpress"],
35925
- [/powered by joomla|com_content/i, "joomla"],
35926
- [/laravel|_token|csrf-token/i, "laravel"],
35927
- [/react|__NEXT_DATA__|next\/static/i, "nextjs"],
35928
- [/angular|ng-version/i, "angular"],
35929
- [/vue|__vue__|v-data/i, "vue"],
35930
- [/django|csrftoken|__debug__/i, "django"],
35931
- [/ruby on rails|rails/i, "rails"],
35932
- [/asp\.net|__VIEWSTATE|__EVENTVALIDATION/i, "aspnet"],
35933
- [/phpBB|phpbb/i, "phpbb"],
35934
- [/mybb|mybb/i, "mybb"],
35935
- [/drupal|Drupal\.settings/i, "drupal"],
35936
- [/magento|Mage\./i, "magento"],
35937
- [/shopify|cdn\.shopify/i, "shopify"],
35938
- [/grafana|kibana|elastic/i, "grafana-kibana"],
35939
- [/node\.js|express/i, "nodejs"],
35940
- [/flask|werkzeug/i, "flask"],
35941
- [/spring|actuator/i, "spring"]
35942
- ];
35943
- const techs = [...new Set(techSig.filter(([re]) => re.test(root.body)).map(([, t]) => t))];
36085
+ const techs = detectTech(root.headers, root.body);
35944
36086
  const versions = extractVersion(root.headers, root.body);
35945
36087
  const { links, scripts } = crawlLinks(base, root.body);
35946
36088
  const { robots, sitemap } = await fetchRobotsAndSitemap(base);
@@ -58281,7 +58423,15 @@ var ALIASES = {
58281
58423
  transmute: "rust_unsafe",
58282
58424
  "rust format string": "rust_format_string",
58283
58425
  "format string": "rust_format_string",
58284
- "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"
58285
58435
  };
58286
58436
  function normalize(term) {
58287
58437
  return term.trim().toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
@@ -58671,7 +58821,9 @@ var VRT_MAP = {
58671
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." },
58672
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." },
58673
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." },
58674
- 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." }
58675
58827
  };
58676
58828
  function severityCalibrate(input) {
58677
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.73",
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": {