blitzstrike 2.4.40 → 2.4.41

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,17 @@ 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.41] — 2026-09-14
9
+
10
+ ### Added — Blind Differential Oracle (calibration-based sink detection)
11
+
12
+ - New `src/blind-oracle.ts`: detect which sink a request parameter reaches
13
+ WITHOUT a malicious payload — calibration + differential. Arithmetic
14
+ identities (`id=1` vs `id=1+0`/`id=2-1`) reveal SQL/expression context; SSTI
15
+ arithmetic (`test${7*7}`) reveals template engines; a unique marker reveals
16
+ XSS reflection. Benign probes (numbers + operators), deterministic inference.
17
+ - New `blind_oracle` tool: WAF-safe blind detection for sql/ssti/xss.
18
+
8
19
  ## [2.4.40] — 2026-09-14
9
20
 
10
21
  ### Added — Web Cache Key Normalization Gap Fuzzer (live, black-box)
package/dist/index.js CHANGED
@@ -57506,6 +57506,123 @@ async function cacheGapScan(baseUrl, endpoint, opts = {}) {
57506
57506
  };
57507
57507
  }
57508
57508
 
57509
+ // src/blind-oracle.ts
57510
+ function arithmeticProbes(value) {
57511
+ const n = Number(value);
57512
+ if (Number.isNaN(n) || value.trim() === "")
57513
+ return [];
57514
+ return [
57515
+ { sink: "sql_arithmetic", name: "plus_zero", payload: `${n}+0` },
57516
+ { sink: "sql_arithmetic", name: "minus_zero", payload: `${n}-0` },
57517
+ { sink: "sql_arithmetic", name: "times_one", payload: `${n}*1` },
57518
+ { sink: "sql_coercion", name: "leading_zero", payload: `0${value}` },
57519
+ { sink: "sql_coercion", name: "float_form", payload: `${n}.0` }
57520
+ ];
57521
+ }
57522
+ function sstiProbes(value) {
57523
+ return [
57524
+ { sink: "ssti", name: "dollar_math", payload: `${value}\${7*7}` },
57525
+ { sink: "ssti", name: "hash_math", payload: `${value}#{7*7}` },
57526
+ { sink: "ssti", name: "mustache_math", payload: `${value}{{7*7}}` }
57527
+ ];
57528
+ }
57529
+ function xssProbe(value, marker) {
57530
+ return { sink: "xss", name: "marker", payload: `${value}${marker}` };
57531
+ }
57532
+ function classifyBlindProbe(input) {
57533
+ const same = input.bodyHash === input.baselineHash;
57534
+ let signal = false;
57535
+ let reason = "";
57536
+ if (input.sink === "sql_arithmetic" && same) {
57537
+ signal = true;
57538
+ reason = "arithmetic identity returned the SAME result as the bare value — the parameter is evaluated arithmetically (SQL/expression context).";
57539
+ } else if (input.sink === "sql_coercion" && same) {
57540
+ signal = true;
57541
+ reason = "type-coerced variant returned the SAME result — the parameter is normalized numerically (SQL typed column).";
57542
+ } else if (input.sink === "ssti" && !same && /49/.test(input.body)) {
57543
+ signal = true;
57544
+ reason = "the SSTI arithmetic (7*7) rendered as 49 in the response — the parameter reaches a template engine (SSTI).";
57545
+ } else if (input.sink === "xss" && input.marker && input.body.includes(input.marker)) {
57546
+ signal = true;
57547
+ reason = "the unique marker was reflected in the response — the parameter reaches HTML output (reflected).";
57548
+ }
57549
+ return { same_as_baseline: same, signal, reason };
57550
+ }
57551
+ async function blindOracle(baseUrl, param, value, opts = {}) {
57552
+ const base = baseUrl.replace(/\/+$/, "");
57553
+ const sinks = opts.sinks ?? ["sql", "ssti", "xss"];
57554
+ const marker = `zz${Math.random().toString(36).slice(2, 10)}zz`;
57555
+ const headers = { "User-Agent": "Mozilla/5.0 (blitzstrike oracle probe)" };
57556
+ const timeoutMs = opts.timeoutMs ?? 8000;
57557
+ const build = (payload) => `${base}?${encodeURIComponent(param)}=${encodeURIComponent(payload)}`;
57558
+ let baselineHash = "";
57559
+ let baselineSize = 0;
57560
+ let baselineStatus = 0;
57561
+ try {
57562
+ const r = await fetch(build(value), { headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
57563
+ baselineStatus = r.status;
57564
+ const body = await r.text();
57565
+ baselineHash = sha256(body);
57566
+ baselineSize = body.length;
57567
+ } catch (e) {
57568
+ return { error: "baseline fetch failed", base_url: base, param, detail: String(e) };
57569
+ }
57570
+ const probes = [];
57571
+ if (sinks.includes("sql"))
57572
+ probes.push(...arithmeticProbes(value));
57573
+ if (sinks.includes("ssti"))
57574
+ probes.push(...sstiProbes(value));
57575
+ if (sinks.includes("xss"))
57576
+ probes.push(xssProbe(value, marker));
57577
+ const results = [];
57578
+ for (const p of probes) {
57579
+ const t0 = Date.now();
57580
+ try {
57581
+ const r = await fetch(build(p.payload), { headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
57582
+ const timing = Date.now() - t0;
57583
+ const status = r.status;
57584
+ const body = await r.text();
57585
+ const bodyHash = sha256(body);
57586
+ const cls = classifyBlindProbe({
57587
+ baselineHash,
57588
+ baselineSize,
57589
+ bodyHash,
57590
+ size: body.length,
57591
+ sink: p.sink,
57592
+ name: p.name,
57593
+ body,
57594
+ marker
57595
+ });
57596
+ results.push({
57597
+ sink: p.sink,
57598
+ name: p.name,
57599
+ payload: p.payload,
57600
+ status,
57601
+ size: body.length,
57602
+ timing_ms: timing,
57603
+ body_hash: bodyHash.slice(0, 12),
57604
+ same_as_baseline: cls.same_as_baseline,
57605
+ signal: cls.signal,
57606
+ reason: cls.reason
57607
+ });
57608
+ } catch {
57609
+ results.push({ sink: p.sink, name: p.name, payload: p.payload, status: 0, size: 0, timing_ms: 0, body_hash: "", same_as_baseline: false, signal: false, reason: "fetch error/timeout" });
57610
+ }
57611
+ }
57612
+ const signals = results.filter((r) => r.signal === true);
57613
+ return {
57614
+ base_url: base,
57615
+ param,
57616
+ value,
57617
+ baseline: { status: baselineStatus, size: baselineSize, body_hash: baselineHash.slice(0, 16) },
57618
+ probes_tested: results.length,
57619
+ signals_found: signals.length,
57620
+ signals,
57621
+ all: results,
57622
+ interpretation: "A signal means the parameter reaches that sink (evidence: the differential). Verify live before reporting — the oracle is a detection hint, not proof."
57623
+ };
57624
+ }
57625
+
57509
57626
  // src/enterprise-benchmark.ts
57510
57627
  import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
57511
57628
  import { join as join18, resolve } from "node:path";
@@ -58505,6 +58622,24 @@ ${statusLine}` : statusLine },
58505
58622
  }, async ({ base_url, endpoint, max_variants }) => {
58506
58623
  return { content: [{ type: "text", text: JSON.stringify(await cacheGapScan(base_url, endpoint, { maxVariants: max_variants })) }] };
58507
58624
  });
58625
+ server.registerTool("blind_oracle", {
58626
+ title: "Blind differential oracle (calibration-based sink detection)",
58627
+ description: "LIVE (black-box, WAF-safe): detect which sink a request parameter reaches WITHOUT a malicious payload — calibration + differential. Arithmetic identities (id=1 vs id=1+0 / id=2-1) reveal SQL/expression context; SSTI arithmetic (test${7*7}) reveals template engines; a unique marker reveals XSS reflection. Benign probes (numbers + operators, no quotes/commands), deterministic byte-level inference.",
58628
+ inputSchema: {
58629
+ base_url: string2().describe("Target base URL"),
58630
+ param: string2().describe("Parameter name to probe (e.g. id)"),
58631
+ value: string2().describe("Baseline value for the parameter (e.g. 1)"),
58632
+ sinks: string2().optional().describe("JSON array of sinks to probe (default ['sql','ssti','xss'])")
58633
+ }
58634
+ }, async ({ base_url, param, value, sinks }) => {
58635
+ let s;
58636
+ try {
58637
+ s = sinks ? JSON.parse(sinks) : undefined;
58638
+ } catch {
58639
+ return { content: [{ type: "text", text: JSON.stringify({ error: "invalid sinks JSON" }) }] };
58640
+ }
58641
+ return { content: [{ type: "text", text: JSON.stringify(await blindOracle(base_url, param, value, { sinks: s })) }] };
58642
+ });
58508
58643
  server.registerTool("payload_lookup", {
58509
58644
  title: "Look up exploit payloads",
58510
58645
  description: "INTEL: find a payload collection (66 categories from PayloadsAllTheThings).",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.40",
3
+ "version": "2.4.41",
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": {