blitzstrike 2.4.42 → 2.4.44
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 +23 -0
- package/dist/index.js +174 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,29 @@ 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.44] — 2026-09-14
|
|
9
|
+
|
|
10
|
+
### Added — JWT Forgeability Analyzer (identity token oracle)
|
|
11
|
+
|
|
12
|
+
- New `src/jwt-analyze.ts`: parse a JWT and enumerate forge vectors
|
|
13
|
+
DETERMINISTICALLY — alg:none, missing signature, RS256→HS256 key confusion,
|
|
14
|
+
kid/jku header injection, weak HS secret (brute-forced against a wordlist),
|
|
15
|
+
missing expiry, sensitive claims.
|
|
16
|
+
- New `jwt_analyze` tool + pure `decodeJwt` / `analyzeJwt` / `forgeAlgNone`
|
|
17
|
+
helpers. Confirmation is a forge-and-replay against the live verifier.
|
|
18
|
+
|
|
19
|
+
## [2.4.43] — 2026-09-14
|
|
20
|
+
|
|
21
|
+
### Added — Race Condition / TOCTOU Tester (concurrency oracle)
|
|
22
|
+
|
|
23
|
+
- New `src/race-test.ts`: detect TOCTOU races + idempotency violations — fire
|
|
24
|
+
a CONCURRENT BURST of N identical requests and count how many succeed. If
|
|
25
|
+
more than `expected_max` (default 1) succeed, the operation is not atomic
|
|
26
|
+
under concurrency (double-spend, duplicate orders, discount abuse).
|
|
27
|
+
- New `race_test` tool: success criterion is a caller-supplied regex (the
|
|
28
|
+
"marker" of a committed side effect). Pure `classifyRace` + `requestSignature`
|
|
29
|
+
for deterministic, testable logic.
|
|
30
|
+
|
|
8
31
|
## [2.4.42] — 2026-09-14
|
|
9
32
|
|
|
10
33
|
### Added — Duplicate Parameter Precedence Fuzzer (WAF-bypass oracle)
|
package/dist/index.js
CHANGED
|
@@ -57709,6 +57709,150 @@ async function paramPrecedenceScan(baseUrl, param, valueA, valueB, opts = {}) {
|
|
|
57709
57709
|
};
|
|
57710
57710
|
}
|
|
57711
57711
|
|
|
57712
|
+
// src/race-test.ts
|
|
57713
|
+
function classifyRace(input) {
|
|
57714
|
+
const race = input.successes > input.expected_max;
|
|
57715
|
+
return {
|
|
57716
|
+
race,
|
|
57717
|
+
verdict: race ? `race condition / idempotency violation — ${input.successes} of ${input.total} concurrent requests succeeded (expected ≤ ${input.expected_max}).` : `no race — ${input.successes} of ${input.total} succeeded (within expected ${input.expected_max}).`
|
|
57718
|
+
};
|
|
57719
|
+
}
|
|
57720
|
+
async function raceTest(input) {
|
|
57721
|
+
const method = input.method ?? "POST";
|
|
57722
|
+
const N = input.concurrent ?? 10;
|
|
57723
|
+
const expected = input.expected_max ?? 1;
|
|
57724
|
+
const successRe = new RegExp(input.success_pattern, "i");
|
|
57725
|
+
const timeoutMs = input.timeoutMs ?? 1e4;
|
|
57726
|
+
const headers = { "User-Agent": "Mozilla/5.0 (blitzstrike race probe)", ...input.headers ?? {} };
|
|
57727
|
+
const doFetch = async () => {
|
|
57728
|
+
try {
|
|
57729
|
+
const r = await fetch(input.url, { method, body: input.body, headers, redirect: "manual", signal: AbortSignal.timeout(timeoutMs) });
|
|
57730
|
+
return { status: r.status, body: await r.text() };
|
|
57731
|
+
} catch (e) {
|
|
57732
|
+
return { status: 0, body: "", error: String(e) };
|
|
57733
|
+
}
|
|
57734
|
+
};
|
|
57735
|
+
const results = await Promise.all(Array.from({ length: N }, () => doFetch()));
|
|
57736
|
+
const successes = results.filter((r) => r.status > 0 && successRe.test(r.body)).length;
|
|
57737
|
+
const errors = results.filter((r) => r.error).length;
|
|
57738
|
+
const verdict = classifyRace({ successes, total: N, expected_max: expected });
|
|
57739
|
+
return {
|
|
57740
|
+
url: input.url,
|
|
57741
|
+
method,
|
|
57742
|
+
concurrent_requests: N,
|
|
57743
|
+
expected_max: expected,
|
|
57744
|
+
concurrent_successes: successes,
|
|
57745
|
+
fetch_errors: errors,
|
|
57746
|
+
success_rate: Number((successes / N).toFixed(3)),
|
|
57747
|
+
race_detected: verdict.race,
|
|
57748
|
+
verdict: verdict.verdict,
|
|
57749
|
+
detail: verdict.race ? "multiple concurrent requests each passed the check before any committed the side effect (TOCTOU)." : "the operation appears atomic/idempotent under this concurrency (a tighter single-packet burst via HTTP/2 may still win; this is a parallel-request approximation)."
|
|
57750
|
+
};
|
|
57751
|
+
}
|
|
57752
|
+
|
|
57753
|
+
// src/jwt-analyze.ts
|
|
57754
|
+
import { createHmac } from "node:crypto";
|
|
57755
|
+
function b64urlDecode(seg) {
|
|
57756
|
+
return Buffer.from(seg.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
57757
|
+
}
|
|
57758
|
+
function decodeJwt(token) {
|
|
57759
|
+
const parts = String(token).trim().split(".");
|
|
57760
|
+
if (parts.length < 2)
|
|
57761
|
+
return null;
|
|
57762
|
+
try {
|
|
57763
|
+
const header = JSON.parse(b64urlDecode(parts[0]));
|
|
57764
|
+
const payload = JSON.parse(b64urlDecode(parts[1]));
|
|
57765
|
+
return { header, payload, signature: parts[2] ?? "", segments: parts.length };
|
|
57766
|
+
} catch {
|
|
57767
|
+
return null;
|
|
57768
|
+
}
|
|
57769
|
+
}
|
|
57770
|
+
function analyzeJwt(token, opts = {}) {
|
|
57771
|
+
const decoded = decodeJwt(token);
|
|
57772
|
+
if (!decoded)
|
|
57773
|
+
return { error: "not a parseable JWT", token_preview: String(token).slice(0, 24) };
|
|
57774
|
+
const { header, payload, signature, segments } = decoded;
|
|
57775
|
+
const alg = String(header.alg ?? "");
|
|
57776
|
+
const findings = [];
|
|
57777
|
+
if (/^none$/i.test(alg)) {
|
|
57778
|
+
findings.push({
|
|
57779
|
+
type: "alg_none",
|
|
57780
|
+
severity: "critical",
|
|
57781
|
+
detail: `the token declares alg="${alg}" — a verifier that trusts the alg header skips signature verification entirely.`,
|
|
57782
|
+
forge: "set header alg:none, edit payload (role:admin / another sub), drop the signature, keep the trailing dot."
|
|
57783
|
+
});
|
|
57784
|
+
}
|
|
57785
|
+
if (segments === 2) {
|
|
57786
|
+
findings.push({
|
|
57787
|
+
type: "no_signature",
|
|
57788
|
+
severity: "high",
|
|
57789
|
+
detail: "the token has no signature segment — if the server accepts it, the payload is directly editable.",
|
|
57790
|
+
forge: "edit the payload segment and replay without a signature."
|
|
57791
|
+
});
|
|
57792
|
+
}
|
|
57793
|
+
if (/^(RS|ES|PS)\d+$/i.test(alg)) {
|
|
57794
|
+
findings.push({
|
|
57795
|
+
type: "key_confusion",
|
|
57796
|
+
severity: "high",
|
|
57797
|
+
detail: `asymmetric alg ${alg} — if the verifier accepts HS256, the PUBLIC key can be reused as the HMAC secret (key confusion).`,
|
|
57798
|
+
forge: "re-sign an edited payload with HS256 using the RSA public key (from /jwks.json or /.well-known/jwks.json) as the HMAC secret."
|
|
57799
|
+
});
|
|
57800
|
+
}
|
|
57801
|
+
if (header.kid !== undefined) {
|
|
57802
|
+
findings.push({
|
|
57803
|
+
type: "kid_header",
|
|
57804
|
+
severity: "medium",
|
|
57805
|
+
detail: `kid header present ("${String(header.kid).slice(0, 40)}") — if the verifier loads the key from a file/DB/URL named by kid, this is a path-traversal / SQLi / SSRF vector.`,
|
|
57806
|
+
forge: 'try kid="../../../../dev/null" (empty key) or a URL you control.'
|
|
57807
|
+
});
|
|
57808
|
+
}
|
|
57809
|
+
if (header.jku !== undefined || header.x5u !== undefined) {
|
|
57810
|
+
findings.push({
|
|
57811
|
+
type: "remote_key_header",
|
|
57812
|
+
severity: "medium",
|
|
57813
|
+
detail: "jku/x5u header present — the verifier may fetch the verification key from a URL the token controls (SSRF / key substitution).",
|
|
57814
|
+
forge: "host a JWKS at a URL you control and point jku at it."
|
|
57815
|
+
});
|
|
57816
|
+
}
|
|
57817
|
+
if (/^HS\d+$/i.test(alg) && signature && opts.wordlist && opts.wordlist.length > 0) {
|
|
57818
|
+
const data = `${token.split(".")[0]}.${token.split(".")[1]}`;
|
|
57819
|
+
for (const w of opts.wordlist.slice(0, 1e4)) {
|
|
57820
|
+
const sig = createHmac("sha256", w).update(data).digest("base64url");
|
|
57821
|
+
if (sig === signature) {
|
|
57822
|
+
findings.push({
|
|
57823
|
+
type: "weak_secret",
|
|
57824
|
+
severity: "critical",
|
|
57825
|
+
detail: `the HS256 secret is a guessable value: "${w}" — the token is directly forgeable.`,
|
|
57826
|
+
forge: `sign any payload with HS256 using secret "${w}".`
|
|
57827
|
+
});
|
|
57828
|
+
break;
|
|
57829
|
+
}
|
|
57830
|
+
}
|
|
57831
|
+
}
|
|
57832
|
+
if (payload.exp === undefined && payload.iat === undefined) {
|
|
57833
|
+
findings.push({
|
|
57834
|
+
type: "no_expiry",
|
|
57835
|
+
severity: "low",
|
|
57836
|
+
detail: "no exp/iat claim — the token never expires (unbounded replay window)."
|
|
57837
|
+
});
|
|
57838
|
+
}
|
|
57839
|
+
const sensitiveKeys = Object.keys(payload).filter((k) => /role|admin|is_admin|permission|scope|privilege|sub|user_id|email|is_staff/i.test(k));
|
|
57840
|
+
const sensitiveClaims = {};
|
|
57841
|
+
for (const k of sensitiveKeys)
|
|
57842
|
+
sensitiveClaims[k] = payload[k];
|
|
57843
|
+
const forgeable = findings.some((f) => f.severity === "critical" || f.severity === "high");
|
|
57844
|
+
return {
|
|
57845
|
+
alg,
|
|
57846
|
+
segments,
|
|
57847
|
+
forgeable,
|
|
57848
|
+
findings,
|
|
57849
|
+
sensitive_claims: sensitiveClaims,
|
|
57850
|
+
header,
|
|
57851
|
+
payload,
|
|
57852
|
+
interpretation: "a finding is a forge VECTOR, not proof — confirm by forging + replaying against the live verifier before reporting."
|
|
57853
|
+
};
|
|
57854
|
+
}
|
|
57855
|
+
|
|
57712
57856
|
// src/enterprise-benchmark.ts
|
|
57713
57857
|
import { existsSync as existsSync11, readFileSync as readFileSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
57714
57858
|
import { join as join18, resolve } from "node:path";
|
|
@@ -58737,6 +58881,36 @@ ${statusLine}` : statusLine },
|
|
|
58737
58881
|
}, async ({ base_url, param, value_a, value_b }) => {
|
|
58738
58882
|
return { content: [{ type: "text", text: JSON.stringify(await paramPrecedenceScan(base_url, param, value_a, value_b)) }] };
|
|
58739
58883
|
});
|
|
58884
|
+
server.registerTool("race_test", {
|
|
58885
|
+
title: "Race condition / TOCTOU tester (concurrency oracle)",
|
|
58886
|
+
description: "LIVE (black-box): detect TOCTOU race conditions + idempotency violations — fire a sanity request, then a CONCURRENT BURST of N identical requests, and count how many succeed. If more than expected_max succeed, the operation is not atomic under concurrency (double-spend, duplicate orders, discount abuse, mass action). Deterministic: success is a caller-supplied regex.",
|
|
58887
|
+
inputSchema: {
|
|
58888
|
+
url: string2().describe("Full request URL"),
|
|
58889
|
+
method: string2().optional().describe("HTTP method (default POST)"),
|
|
58890
|
+
body: string2().optional().describe("Request body"),
|
|
58891
|
+
success_pattern: string2().describe("Regex that matches a 'success' response (the committed side effect)"),
|
|
58892
|
+
concurrent: number2().optional().describe("Number of concurrent requests (default 10)"),
|
|
58893
|
+
expected_max: number2().optional().describe("Max expected successes (default 1)")
|
|
58894
|
+
}
|
|
58895
|
+
}, async ({ url, method, body, success_pattern, concurrent, expected_max }) => {
|
|
58896
|
+
return { content: [{ type: "text", text: JSON.stringify(await raceTest({ url, method, body, success_pattern, concurrent, expected_max })) }] };
|
|
58897
|
+
});
|
|
58898
|
+
server.registerTool("jwt_analyze", {
|
|
58899
|
+
title: "JWT forgeability analyzer (identity token oracle)",
|
|
58900
|
+
description: "Parse a JWT and enumerate its forge vectors DETERMINISTICALLY — alg:none, missing signature, RS256→HS256 key confusion, kid/jku header injection, weak HS secret (brute-forced against a wordlist), missing expiry, sensitive claims. Returns a forgeable verdict + PoC hints. Confirmation is a forge-and-replay against the live verifier, not this hint.",
|
|
58901
|
+
inputSchema: {
|
|
58902
|
+
token: string2().describe("The JWT (eyJ...) to analyze"),
|
|
58903
|
+
wordlist: string2().optional().describe("JSON array of candidate HS secrets to brute-force")
|
|
58904
|
+
}
|
|
58905
|
+
}, async ({ token, wordlist }) => {
|
|
58906
|
+
let wl;
|
|
58907
|
+
try {
|
|
58908
|
+
wl = wordlist ? JSON.parse(wordlist) : undefined;
|
|
58909
|
+
} catch {
|
|
58910
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "invalid wordlist JSON" }) }] };
|
|
58911
|
+
}
|
|
58912
|
+
return { content: [{ type: "text", text: JSON.stringify(analyzeJwt(token, { wordlist: wl })) }] };
|
|
58913
|
+
});
|
|
58740
58914
|
server.registerTool("payload_lookup", {
|
|
58741
58915
|
title: "Look up exploit payloads",
|
|
58742
58916
|
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.44",
|
|
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": {
|