blitzstrike 2.4.41 → 2.4.42
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 +14 -0
- package/dist/index.js +99 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ 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.42] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — Duplicate Parameter Precedence Fuzzer (WAF-bypass oracle)
|
|
11
|
+
|
|
12
|
+
- New `src/param-precedence.ts`: determine how the app resolves duplicate
|
|
13
|
+
parameters + alternate delimiters (`;` `,` `%26` `%3b` `|` `[]` `%00` `+`),
|
|
14
|
+
and classify first-wins / last-wins / mixed precedence.
|
|
15
|
+
- New `param_precedence` tool: baseline A/B + differential per variant, with a
|
|
16
|
+
DERIVED waf_bypass_hint (inject via the WAF/app disagreement).
|
|
17
|
+
- System prompt: added a "LIVE PENTEST + TARGETING" directive wiring the new
|
|
18
|
+
black-box tools (cache_gap / blind_oracle / param_precedence) and source
|
|
19
|
+
targeting tools (action_surface / security_state / framework_knowledge /
|
|
20
|
+
variant_scan / patch_analyze) into a coherent, evidence-first workflow.
|
|
21
|
+
|
|
8
22
|
## [2.4.41] — 2026-09-14
|
|
9
23
|
|
|
10
24
|
### Added — Blind Differential Oracle (calibration-based sink detection)
|
package/dist/index.js
CHANGED
|
@@ -57623,6 +57623,92 @@ async function blindOracle(baseUrl, param, value, opts = {}) {
|
|
|
57623
57623
|
};
|
|
57624
57624
|
}
|
|
57625
57625
|
|
|
57626
|
+
// src/param-precedence.ts
|
|
57627
|
+
function generatePrecedenceVariants(param, a, b) {
|
|
57628
|
+
const enc = encodeURIComponent;
|
|
57629
|
+
const variants = [
|
|
57630
|
+
{ name: "duplicate_amp", qs: `${enc(param)}=${enc(a)}&${enc(param)}=${enc(b)}` },
|
|
57631
|
+
{ name: "semicolon", qs: `${enc(param)}=${enc(a)};${enc(param)}=${enc(b)}` },
|
|
57632
|
+
{ name: "comma", qs: `${enc(param)}=${enc(a)},${enc(param)}=${enc(b)}` },
|
|
57633
|
+
{ name: "encoded_amp", qs: `${enc(param)}=${enc(a)}%26${enc(param)}=${enc(b)}` },
|
|
57634
|
+
{ name: "encoded_semicolon", qs: `${enc(param)}=${enc(a)}%3b${enc(param)}=${enc(b)}` },
|
|
57635
|
+
{ name: "pipe", qs: `${enc(param)}=${enc(a)}|${enc(param)}=${enc(b)}` },
|
|
57636
|
+
{ name: "array", qs: `${enc(param)}[]=${enc(a)}&${enc(param)}[]=${enc(b)}` },
|
|
57637
|
+
{ name: "null_byte", qs: `${enc(param)}=${enc(a)}%00${enc(b)}` },
|
|
57638
|
+
{ name: "space", qs: `${enc(param)}=${enc(a)}+${enc(b)}` }
|
|
57639
|
+
];
|
|
57640
|
+
return variants;
|
|
57641
|
+
}
|
|
57642
|
+
function classifyPrecedence(input) {
|
|
57643
|
+
const h = input.bodyHash;
|
|
57644
|
+
if (h === input.hashA) {
|
|
57645
|
+
return { winner: "A", split: true, detail: "value A won (the first/leftmost value) — first-wins precedence for this delimiter." };
|
|
57646
|
+
}
|
|
57647
|
+
if (h === input.hashB) {
|
|
57648
|
+
return { winner: "B", split: true, detail: "value B won (the last/rightmost value) — last-wins precedence for this delimiter." };
|
|
57649
|
+
}
|
|
57650
|
+
return { winner: "neither", split: false, detail: "neither A nor B matched — the delimiter may be literal (not a parameter separator) or the values were joined." };
|
|
57651
|
+
}
|
|
57652
|
+
async function paramPrecedenceScan(baseUrl, param, valueA, valueB, opts = {}) {
|
|
57653
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
57654
|
+
const headers = { "User-Agent": "Mozilla/5.0 (blitzstrike precedence probe)" };
|
|
57655
|
+
const timeoutMs = opts.timeoutMs ?? 8000;
|
|
57656
|
+
const enc = encodeURIComponent;
|
|
57657
|
+
const fetchHash = async (qs) => {
|
|
57658
|
+
try {
|
|
57659
|
+
const r = await fetch(`${base}?${qs}`, { headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
|
|
57660
|
+
const body = await r.text();
|
|
57661
|
+
return { status: r.status, hash: sha256(body) };
|
|
57662
|
+
} catch (e) {
|
|
57663
|
+
return { status: 0, hash: "", error: String(e) };
|
|
57664
|
+
}
|
|
57665
|
+
};
|
|
57666
|
+
const baseA = await fetchHash(`${enc(param)}=${enc(valueA)}`);
|
|
57667
|
+
const baseB = await fetchHash(`${enc(param)}=${enc(valueB)}`);
|
|
57668
|
+
if (baseA.error || baseB.error) {
|
|
57669
|
+
return { error: "baseline fetch failed", base_url: base, param, detail: baseA.error ?? baseB.error };
|
|
57670
|
+
}
|
|
57671
|
+
if (baseA.hash === baseB.hash) {
|
|
57672
|
+
return { error: "baseline A and B produce identical responses — pick two values with a distinguishable effect", base_url: base, param };
|
|
57673
|
+
}
|
|
57674
|
+
const variants = generatePrecedenceVariants(param, valueA, valueB);
|
|
57675
|
+
const results = [];
|
|
57676
|
+
let firstWins = 0;
|
|
57677
|
+
let lastWins = 0;
|
|
57678
|
+
for (const v of variants) {
|
|
57679
|
+
const r = await fetchHash(v.qs);
|
|
57680
|
+
if (r.error) {
|
|
57681
|
+
results.push({ name: v.name, qs: v.qs, status: 0, body_hash: "", winner: "neither", split: false, detail: "fetch error/timeout" });
|
|
57682
|
+
continue;
|
|
57683
|
+
}
|
|
57684
|
+
const cls = classifyPrecedence({ hashA: baseA.hash, hashB: baseB.hash, bodyHash: r.hash, name: v.name });
|
|
57685
|
+
if (cls.winner === "A")
|
|
57686
|
+
firstWins++;
|
|
57687
|
+
else if (cls.winner === "B")
|
|
57688
|
+
lastWins++;
|
|
57689
|
+
results.push({ name: v.name, qs: v.qs, status: r.status, body_hash: r.hash.slice(0, 12), winner: cls.winner, split: cls.split, detail: cls.detail });
|
|
57690
|
+
}
|
|
57691
|
+
let precedence = "ambiguous";
|
|
57692
|
+
if (firstWins > 0 && lastWins === 0)
|
|
57693
|
+
precedence = "first_wins";
|
|
57694
|
+
else if (lastWins > 0 && firstWins === 0)
|
|
57695
|
+
precedence = "last_wins";
|
|
57696
|
+
else if (firstWins > 0 && lastWins > 0)
|
|
57697
|
+
precedence = "mixed_by_delimiter";
|
|
57698
|
+
return {
|
|
57699
|
+
base_url: base,
|
|
57700
|
+
param,
|
|
57701
|
+
value_a: valueA,
|
|
57702
|
+
value_b: valueB,
|
|
57703
|
+
precedence,
|
|
57704
|
+
first_wins_count: firstWins,
|
|
57705
|
+
last_wins_count: lastWins,
|
|
57706
|
+
variants_tested: results.length,
|
|
57707
|
+
results,
|
|
57708
|
+
waf_bypass_hint: precedence === "first_wins" ? "the app uses the FIRST duplicate value — a WAF that inspects the LAST value is bypassed with ?param=benign¶m=payload." : precedence === "last_wins" ? "the app uses the LAST duplicate value — a WAF that inspects the FIRST value is bypassed with ?param=payload¶m=benign." : "precedence is mixed by delimiter — map which delimiter the WAF validates vs which the app binds, then inject via the disagreement."
|
|
57709
|
+
};
|
|
57710
|
+
}
|
|
57711
|
+
|
|
57626
57712
|
// src/enterprise-benchmark.ts
|
|
57627
57713
|
import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
57628
57714
|
import { join as join18, resolve } from "node:path";
|
|
@@ -57862,8 +57948,7 @@ function createServer() {
|
|
|
57862
57948
|
` + `- Keep trying every lead AND every chain step until each is verified/executed or blocked, THEN generate_report. Never deliver a bare surface map, and never stop at 'found X' when X enables more.
|
|
57863
57949
|
|
|
57864
57950
|
` + "TWO PATHS:\\n" + "- Source path on disk -> blitz_scan / taint_file / eagle_eye2 (static).\\n" + "- Live URL -> live_recon / active_scan / strike_verify (web-hunting).\\n\\n" + `DEEPEN A FINDING: attack_plan(target, tech, params) to get the prioritized vector plan (decide what to test next with data); list_chains (escalation), chain_links (compose), technique_lookup(cls), payload_lookup, bypass_lookup (blocked). Phase doctrine: read_skill(bs-orchestrate-engagement | bs-web-hunting | bs-source-audit | bs-verify-finding). Campaign plan: read_playbook(target-type). On any failure call retry_guidance(signal) / model_fallback(signal) before giving up.
|
|
57865
|
-
` +
|
|
57866
|
-
` + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
|
|
57951
|
+
` + "KNOWLEDGE BASE (discover, don't guess): skillLookup(topic) / listSkills for the 40 attack-path + hunting skills (ap-*, hp-*, bs-*); list_attack_vectors / attack_vectors(cat) for the 34-category vector taxonomy; taxonomy(kind) for OWASP/CWE/ASVS/API-top-10; read_playbook(type) for the 17 campaign playbooks; list_manuals / read_tool_manual for 317 tool references; list_tools / run_catalog_tool for the 141-tool catalog. Every tool, skill, and data layer is reachable from these. CROSS-TARGET INTELLIGENCE: strike_resolve auto-records confirmed/false_positive verdicts into the empirical ledger; query it with intelligence(action=hit_rates/top_vectors) so a new target on a known tech inherits historical hit-rates (attack_plan already blends them into success_probability). LIVE PENTEST + TARGETING (black-box, WAF-safe): cache_gap(base_url, endpoint) fuzzes path-normalization + static-extension variants for cache deception/poisoning; blind_oracle(base_url, param, value) detects which SINK a param reaches (sql/ssti/xss) via benign arithmetic (id=1+0, test${7*7}) WITHOUT a malicious payload; param_precedence(base_url, param, A, B) maps duplicate-parameter precedence (first/last-wins) for WAF-bypass. SOURCE TARGETING: action_surface(code) maps every wp_ajax_* action to its vuln class + DERIVED priority (what to look for WHERE); security_state(sanitizers, sink) gives a wrong-context sanitization verdict; framework_knowledge(framework) returns auth/nonce primitives for 9 frameworks; variant_scan(root, signature) mass-sweeps an install base for ONE bug family; patch_analyze(old, new) REVERSES a security patch into the 0-day, and its mine_signature feeds variant_scan to sweep unpatched copies. All of these are DETECTION HINTS — verify each live (strike_verify / browser_validate) before it enters the report.\n" + `PRECISION ANALYSIS — chrome-devtools MCP is MANDATORY, not optional:
|
|
57867
57952
|
` + `- When a lead needs DOM/JS precision — DOM-XSS sink execution, AJAX endpoint + payload interception, JS runtime errors, redirect chains, SSO/token flow, or anything a static HTML glance cannot prove — call drive_devtools(target). It spawns chrome-devtools-mcp and drives a REAL headless browser (navigate -> list_network_requests -> list_console_messages -> evaluate_script), giving reproducible evidence instead of a guess.
|
|
57868
57953
|
` + `- Before calling it, check_mcp() to confirm the browser MCP is available; if not, blitzstrike install-tools or npm i -g chrome-devtools-mcp. Never substitute eyeballing minified JS or a static sink guess when a live browser can PROVE it. This is the precision + consistency layer.
|
|
57869
57954
|
` + "EXTERNAL MCP SERVERS (verified, connect them — NOT one-shot CLIs): chrome-devtools-mcp (29 tools: list_network_requests/get_network_request for raw HTTP interception, list_console_messages for JS errors + DOM-XSS, evaluate_script to run JS, take_screenshot) -> stdio chrome-devtools-mcp --headless --chromeArg=--no-sandbox --chromeArg=--disable-gpu (as root/container this is MANDATORY or the browser crashes; add --proxyServer http://127.0.0.1:8080 to route through Burp, --acceptInsecureCerts for bad TLS). burp-suite-mcp (proxy history + active scan + repeater) -> NOT npm: build the JAR (git clone https://github.com/PortSwigger/mcp-server.git && cd mcp-server && ./gradlew embedProxyJar), load it in Burp Extensions, then connect the client to http://127.0.0.1:9876 (SSE) or the stdio proxy java -jar mcp-proxy-all.jar --sse-url http://127.0.0.1:9876."
|
|
@@ -58640,6 +58725,18 @@ ${statusLine}` : statusLine },
|
|
|
58640
58725
|
}
|
|
58641
58726
|
return { content: [{ type: "text", text: JSON.stringify(await blindOracle(base_url, param, value, { sinks: s })) }] };
|
|
58642
58727
|
});
|
|
58728
|
+
server.registerTool("param_precedence", {
|
|
58729
|
+
title: "Duplicate parameter precedence fuzzer (WAF-bypass oracle)",
|
|
58730
|
+
description: "LIVE (black-box): determine how the app resolves duplicate parameters + alternate delimiters (; , %26 %3b | [] %00 + #). Reveals the WAF-bypass vector — if the WAF validates one value and the app binds another, inject via the disagreement. Deterministic: baseline A/B + differential per variant.",
|
|
58731
|
+
inputSchema: {
|
|
58732
|
+
base_url: string2().describe("Target base URL"),
|
|
58733
|
+
param: string2().describe("Parameter name (e.g. id)"),
|
|
58734
|
+
value_a: string2().describe("First value (e.g. 1)"),
|
|
58735
|
+
value_b: string2().describe("Second value (e.g. 2)")
|
|
58736
|
+
}
|
|
58737
|
+
}, async ({ base_url, param, value_a, value_b }) => {
|
|
58738
|
+
return { content: [{ type: "text", text: JSON.stringify(await paramPrecedenceScan(base_url, param, value_a, value_b)) }] };
|
|
58739
|
+
});
|
|
58643
58740
|
server.registerTool("payload_lookup", {
|
|
58644
58741
|
title: "Look up exploit payloads",
|
|
58645
58742
|
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.
|
|
3
|
+
"version": "2.4.42",
|
|
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": {
|