blitzstrike 1.0.59 → 1.0.60

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,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
+ ## [1.0.60] — 2026-09-13
9
+
10
+ ### Added — deep taint-flow + CVE pattern coverage (CVE-2026-60137)
11
+
12
+ Investigated the WordPress core chain CVE-2026-60137 (`author__not_in` SQLi) /
13
+ CVE-2026-63030 (REST API batch route confusion → RCE). The SQLi base was a
14
+ **type-confusion sanitizer bypass**: `absint` guarded by `if ( is_array() )` +
15
+ a `(array)` cast + `implode` that re-interpolates the raw string. The engine
16
+ now detects it:
17
+
18
+ - **Method-call resolution** (`calleeName`) — `$obj->method()` now resolves to
19
+ its method definition for interprocedural taint, not the object variable.
20
+ - **Built-in taint propagation** — `implode`/`join`/`sprintf`/`str_replace` and
21
+ other array→string/format built-ins now carry taint through to the sink.
22
+ - **Cast handling** — `(array)`/`(string)` casts propagate taint (a string can
23
+ still carry a payload); `(int)`/`(float)` casts act as an int_cast sanitizer.
24
+ - **`wp_parse_id_list`** added as an integer-cast sanitizer.
25
+
26
+ The conditional-flow merge already models the `is_array` guard correctly, so the
27
+ engine distinguishes the bypassable sanitizer (vulnerable) from the unconditional
28
+ one (fixed). Added two corpus fixtures (`wp-author-not-in-vuln`/`-fixed`).
29
+
30
+ ### Not yet detected
31
+
32
+ - **CVE-2026-63030** (REST API batch endpoint route confusion) is a routing /
33
+ authorization-logic bug, not a source→sink taint flow — out of scope for the
34
+ current taint engine.
35
+
8
36
  ## [1.0.59] — 2026-09-13
9
37
 
10
38
  ### Added — enterprise-grade detection + benchmark
@@ -0,0 +1,21 @@
1
+ - id: wp-author-not-in-fixed
2
+ - language: php
3
+ - vulnerable: false
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ class WP_Query {
8
+ public function get_posts($qv) {
9
+ $where = "";
10
+ if ( ! empty( $qv["author__not_in"] ) ) {
11
+ $ids = wp_parse_id_list( $qv["author__not_in"] );
12
+ $author__not_in = implode( ",", $ids );
13
+ $where .= " AND post_author NOT IN ($author__not_in) ";
14
+ }
15
+ return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
16
+ }
17
+ }
18
+ $q = $_GET;
19
+ $w = new WP_Query();
20
+ $w->get_posts($q);
21
+ ```
@@ -0,0 +1,23 @@
1
+ - id: wp-author-not-in-vuln
2
+ - language: php
3
+ - vulnerable: true
4
+ - sink_type: sql_execution
5
+ ```php
6
+ <?php
7
+ class WP_Query {
8
+ public function get_posts($qv) {
9
+ $where = "";
10
+ if ( ! empty( $qv["author__not_in"] ) ) {
11
+ if ( is_array( $qv["author__not_in"] ) ) {
12
+ $qv["author__not_in"] = array_map( "absint", $qv["author__not_in"] );
13
+ }
14
+ $author__not_in = implode( ",", (array) $qv["author__not_in"] );
15
+ $where .= " AND post_author NOT IN ($author__not_in) ";
16
+ }
17
+ return $wpdb->query("SELECT * FROM posts WHERE 1=1 $where");
18
+ }
19
+ }
20
+ $q = $_GET;
21
+ $w = new WP_Query();
22
+ $w->get_posts($q);
23
+ ```
package/dist/index.js CHANGED
@@ -9190,6 +9190,7 @@ var init_dataflow = __esm(() => {
9190
9190
  escapeshellcmd: { id: "shell_escape", label: "Shell command escape", neutralizes: ["command_execution"] },
9191
9191
  intval: { id: "int_cast", label: "Integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9192
9192
  absint: { id: "int_cast", label: "Absolute integer cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9193
+ wp_parse_id_list: { id: "int_cast", label: "WordPress ID list cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] },
9193
9194
  "preg_replace.*FILTER": { id: "regex_filter", label: "Regex filter", neutralizes: ["html_render"] }
9194
9195
  };
9195
9196
  });
@@ -17104,6 +17105,15 @@ function rootVarName2(node) {
17104
17105
  return rootVarName2(node.what);
17105
17106
  return null;
17106
17107
  }
17108
+ function calleeName(what) {
17109
+ if (!isNode3(what))
17110
+ return null;
17111
+ if (what.kind === "name")
17112
+ return typeof what.name === "string" ? what.name : what.name?.name ?? null;
17113
+ if (what.kind === "propertylookup" || what.kind === "staticlookup")
17114
+ return what.offset?.name ?? what.offset?.value ?? null;
17115
+ return null;
17116
+ }
17107
17117
  function sourceOf2(node) {
17108
17118
  if (!isNode3(node))
17109
17119
  return null;
@@ -17268,6 +17278,14 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
17268
17278
  const src = sourceOf2(node);
17269
17279
  if (src)
17270
17280
  return { tainted: true, sources: [src], sanitizers: [], sourceLine: node.loc?.start?.line };
17281
+ if (node.kind === "cast") {
17282
+ const inner = analyzeExpr(node.expr, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
17283
+ const castType = String(node.type ?? "");
17284
+ if (/^(int|integer|bool|boolean|float|double)$/i.test(castType) && inner.tainted) {
17285
+ return { ...inner, sanitizers: [...inner.sanitizers, { id: "int_cast", label: "Numeric cast", neutralizes: ["sql_execution", "command_execution", "file_inclusion"] }] };
17286
+ }
17287
+ return inner;
17288
+ }
17271
17289
  if (node.kind === "assign") {
17272
17290
  const byref = node.operator === "=&";
17273
17291
  const rhs = analyzeExpr(node.right, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
@@ -17315,8 +17333,11 @@ function analyzeExpr(node, env, summary, ctx, taintedParam, props, activeAuth, s
17315
17333
  analyzeExpr(a, env, summary, ctx, taintedParam, props, activeAuth, summaries, validating);
17316
17334
  return taintedArgs.length > 0 ? mergeTaint(...taintedArgs) : CLEAN;
17317
17335
  }
17318
- const fnName = rootVarName2(node.what);
17336
+ const fnName = calleeName(node.what);
17319
17337
  if (fnName) {
17338
+ if (TAINT_PROPAGATING_BUILTINS.has(fnName) && taintedArgs.length > 0) {
17339
+ return mergeTaint(...taintedArgs);
17340
+ }
17320
17341
  const san = findSanitizers(fnName + "(");
17321
17342
  if (san.length > 0) {
17322
17343
  if (taintedArgs.length > 0) {
@@ -17575,7 +17596,7 @@ function analyzeDataFlow2(path) {
17575
17596
  const seen = new Set;
17576
17597
  const mainCalls = topStmts.flatMap((s) => findKind2(s, "call"));
17577
17598
  for (const call of mainCalls) {
17578
- const fnName = rootVarName2(call.what);
17599
+ const fnName = calleeName(call.what);
17579
17600
  if (!fnName || sinkClassOf2(call))
17580
17601
  continue;
17581
17602
  const target = resolveFuncId(fnName, summaries);
@@ -17662,7 +17683,7 @@ function analyzeDataFlow2(path) {
17662
17683
  setCache("eagle2", cacheCode, result);
17663
17684
  return result;
17664
17685
  }
17665
- var import_php_parser3, parser3, CLEAN, VALIDATION_GATES;
17686
+ var import_php_parser3, parser3, TAINT_PROPAGATING_BUILTINS, CLEAN, VALIDATION_GATES;
17666
17687
  var init_eagle2 = __esm(() => {
17667
17688
  init_dataflow();
17668
17689
  init_scanner();
@@ -17673,6 +17694,33 @@ var init_eagle2 = __esm(() => {
17673
17694
  parser: { extractDoc: false, php7: true },
17674
17695
  ast: { withPositions: true, withSource: false }
17675
17696
  });
17697
+ TAINT_PROPAGATING_BUILTINS = new Set([
17698
+ "implode",
17699
+ "join",
17700
+ "sprintf",
17701
+ "vsprintf",
17702
+ "str_replace",
17703
+ "str_ireplace",
17704
+ "substr",
17705
+ "mb_substr",
17706
+ "substr_replace",
17707
+ "trim",
17708
+ "ltrim",
17709
+ "rtrim",
17710
+ "strtolower",
17711
+ "strtoupper",
17712
+ "ucfirst",
17713
+ "lcfirst",
17714
+ "urldecode",
17715
+ "rawurldecode",
17716
+ "base64_decode",
17717
+ "html_entity_decode",
17718
+ "stripslashes",
17719
+ "addslashes",
17720
+ "strval",
17721
+ "preg_quote",
17722
+ "nl2br"
17723
+ ]);
17676
17724
  CLEAN = { tainted: false, sources: [], sanitizers: [] };
17677
17725
  VALIDATION_GATES = ["in_array", "preg_match", "ctype_digit", "ctype_alnum", "is_numeric"];
17678
17726
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
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": {