blitzstrike 1.0.62 → 1.0.63
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 +17 -0
- package/bench/enterprise/php-complex/plugin.php +34 -0
- package/dist/index.js +142 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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
|
+
## [1.0.63] — 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added — complex-bug detection (`src/complex-bugs.ts`)
|
|
11
|
+
|
|
12
|
+
Three "hard" vulnerability classes beyond source→sink taint:
|
|
13
|
+
|
|
14
|
+
- **Deserialization → POP gadget chain** — `unserialize()` of attacker-controlled
|
|
15
|
+
data when a magic method (`__destruct`/`__wakeup`/`__toString`/`__invoke`/`__call`)
|
|
16
|
+
is present in the file (object injection → RCE pivot).
|
|
17
|
+
- **Type juggling** — loose comparison (`==`/`!=`) of user input against a
|
|
18
|
+
hash/secret (PHP `0e…` collision auth bypass); strict `===` is ignored.
|
|
19
|
+
- **Mass assignment** — `extract()` of request data without `EXTR_SKIP`, or
|
|
20
|
+
`parse_str()` of user input (variable injection).
|
|
21
|
+
|
|
22
|
+
Wired into the `@source` agent as `vulnerability` graph nodes. Added a
|
|
23
|
+
`php-complex` fixture to the enterprise benchmark.
|
|
24
|
+
|
|
8
25
|
## [1.0.62] — 2026-09-13
|
|
9
26
|
|
|
10
27
|
### Added — route-confusion & dispatch-abuse detection
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
/**
|
|
3
|
+
* Plugin helper (simplified) with multiple complex-bug patterns.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 1. Deserialization → object injection (magic method present)
|
|
7
|
+
function restore_session($payload) {
|
|
8
|
+
$data = $_POST['data'];
|
|
9
|
+
$obj = unserialize($data);
|
|
10
|
+
return $obj;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class CacheManager {
|
|
14
|
+
public function __destruct() {
|
|
15
|
+
// Magic method — a POP gadget can pivot object injection to RCE here.
|
|
16
|
+
system("rm -rf /tmp/cache");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 2. Type juggling — loose comparison of user input against a hash
|
|
21
|
+
function verify_signature() {
|
|
22
|
+
$sig = $_GET['sig'];
|
|
23
|
+
$secret = get_option('signing_secret');
|
|
24
|
+
if ($sig == $secret) { // <-- loose == , 0e... collision bypass
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Mass assignment — extract() of request data without EXTR_SKIP
|
|
31
|
+
function bootstrap_options() {
|
|
32
|
+
extract($_REQUEST); // <-- variable injection
|
|
33
|
+
return $admin; // $admin is attacker-controlled
|
|
34
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -37186,7 +37186,7 @@ function detectRouteConfusion(code, file) {
|
|
|
37186
37186
|
}
|
|
37187
37187
|
var SOURCE_TOKENS, DYNAMIC_DISPATCH_RE, DYNAMIC_METHOD_RE, BATCH_LOOP_RE, ROUTE_MATCH_RE, NORMALIZERS;
|
|
37188
37188
|
var init_route_confusion = __esm(() => {
|
|
37189
|
-
SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
37189
|
+
SOURCE_TOKENS = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post)|getParameter\(|req\.(query|body|params)/;
|
|
37190
37190
|
DYNAMIC_DISPATCH_RE = /\b(call_user_func|call_user_func_array|forward|dispatch|resolve_route|invoke)\s*\(\s*(\$|\[)/gi;
|
|
37191
37191
|
DYNAMIC_METHOD_RE = /(->|::)\s*\$[A-Za-z_]/g;
|
|
37192
37192
|
BATCH_LOOP_RE = /\bforeach\s*\([^)]*(requests|batch|sub_requests|operations)\b/gi;
|
|
@@ -37194,6 +37194,124 @@ var init_route_confusion = __esm(() => {
|
|
|
37194
37194
|
NORMALIZERS = /\b(strtolower|strtoupper|rtrim|urldecode|rawurldecode|parse_url|normalize|canonical)\b/i;
|
|
37195
37195
|
});
|
|
37196
37196
|
|
|
37197
|
+
// src/complex-bugs.ts
|
|
37198
|
+
function lines2(code) {
|
|
37199
|
+
return code.split(`
|
|
37200
|
+
`);
|
|
37201
|
+
}
|
|
37202
|
+
function lineNo2(code, pos) {
|
|
37203
|
+
let n = 1;
|
|
37204
|
+
for (let i = 0;i < pos; i++)
|
|
37205
|
+
if (code.charCodeAt(i) === 10)
|
|
37206
|
+
n++;
|
|
37207
|
+
return n;
|
|
37208
|
+
}
|
|
37209
|
+
function detectDeserialization(code, file, out) {
|
|
37210
|
+
if (!MAGIC_METHODS.test(code))
|
|
37211
|
+
return;
|
|
37212
|
+
const ls = lines2(code);
|
|
37213
|
+
const re = /\b(unserialize|maybe_unserialize)\s*\(/g;
|
|
37214
|
+
let m;
|
|
37215
|
+
while ((m = re.exec(code)) !== null) {
|
|
37216
|
+
const ln = lineNo2(code, m.index);
|
|
37217
|
+
const l = ls[ln - 1] ?? "";
|
|
37218
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37219
|
+
`);
|
|
37220
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
37221
|
+
continue;
|
|
37222
|
+
out.push({
|
|
37223
|
+
file,
|
|
37224
|
+
line: ln,
|
|
37225
|
+
type: "deserialization",
|
|
37226
|
+
category: "Deserialization → POP gadget chain",
|
|
37227
|
+
severity: "high",
|
|
37228
|
+
evidence: l.trim(),
|
|
37229
|
+
detail: `\`${m[1]}()\` of attacker-controlled data AND a magic method is present — object injection can pivot to RCE via the gadget chain.`
|
|
37230
|
+
});
|
|
37231
|
+
}
|
|
37232
|
+
}
|
|
37233
|
+
function detectTypeJuggling(code, file, out) {
|
|
37234
|
+
const ls = lines2(code);
|
|
37235
|
+
const re = /(?<![=!<>])(==|!=)(?!=)/g;
|
|
37236
|
+
let m;
|
|
37237
|
+
while ((m = re.exec(code)) !== null) {
|
|
37238
|
+
const ln = lineNo2(code, m.index);
|
|
37239
|
+
const l = ls[ln - 1] ?? "";
|
|
37240
|
+
const scope = ls.slice(Math.max(0, ln - 4), ln).join(`
|
|
37241
|
+
`);
|
|
37242
|
+
const hasSource = SOURCE_TOKENS2.test(l) || SOURCE_TOKENS2.test(scope);
|
|
37243
|
+
const hasHash = /\b(md5|sha1|sha256|hash|crc32|crypt|password_verify|strcmp|hash_equals|secret|token|nonce)\s*\(?/i.test(l) || /\b(secret|token|nonce|hash)\b/i.test(l);
|
|
37244
|
+
if (!hasSource || !hasHash)
|
|
37245
|
+
continue;
|
|
37246
|
+
out.push({
|
|
37247
|
+
file,
|
|
37248
|
+
line: ln,
|
|
37249
|
+
type: "type_juggling",
|
|
37250
|
+
category: "Type juggling (loose comparison)",
|
|
37251
|
+
severity: "medium",
|
|
37252
|
+
evidence: l.trim(),
|
|
37253
|
+
detail: 'Loose comparison of user input against a hash/secret — PHP type juggling (`0e…`, `"str" == 0`) may bypass the check. Use strict `===`.'
|
|
37254
|
+
});
|
|
37255
|
+
}
|
|
37256
|
+
}
|
|
37257
|
+
function detectMassAssignment(code, file, out) {
|
|
37258
|
+
const ls = lines2(code);
|
|
37259
|
+
const extractRe = /\bextract\s*\(\s*[^)]*(REQUEST|POST|GET|COOKIE|FILES|\$_(REQUEST|POST|GET|COOKIE|FILES))/g;
|
|
37260
|
+
let m;
|
|
37261
|
+
while ((m = extractRe.exec(code)) !== null) {
|
|
37262
|
+
const ln = lineNo2(code, m.index);
|
|
37263
|
+
const l = ls[ln - 1] ?? "";
|
|
37264
|
+
if (/EXTR_SKIP|EXTR_PREFIX_ALL/.test(l))
|
|
37265
|
+
continue;
|
|
37266
|
+
out.push({
|
|
37267
|
+
file,
|
|
37268
|
+
line: ln,
|
|
37269
|
+
type: "mass_assignment",
|
|
37270
|
+
category: "Mass assignment (variable injection)",
|
|
37271
|
+
severity: "high",
|
|
37272
|
+
evidence: l.trim(),
|
|
37273
|
+
detail: "`extract()` of request data without EXTR_SKIP — attacker can overwrite arbitrary variables."
|
|
37274
|
+
});
|
|
37275
|
+
}
|
|
37276
|
+
const parseRe = /\bparse_str\s*\(/g;
|
|
37277
|
+
while ((m = parseRe.exec(code)) !== null) {
|
|
37278
|
+
const ln = lineNo2(code, m.index);
|
|
37279
|
+
const l = ls[ln - 1] ?? "";
|
|
37280
|
+
const scope = ls.slice(Math.max(0, ln - 3), ln).join(`
|
|
37281
|
+
`);
|
|
37282
|
+
if (!SOURCE_TOKENS2.test(l) && !SOURCE_TOKENS2.test(scope))
|
|
37283
|
+
continue;
|
|
37284
|
+
out.push({
|
|
37285
|
+
file,
|
|
37286
|
+
line: ln,
|
|
37287
|
+
type: "mass_assignment",
|
|
37288
|
+
category: "Mass assignment (variable injection)",
|
|
37289
|
+
severity: "medium",
|
|
37290
|
+
evidence: l.trim(),
|
|
37291
|
+
detail: "`parse_str()` of user input into the symbol table — variable injection."
|
|
37292
|
+
});
|
|
37293
|
+
}
|
|
37294
|
+
}
|
|
37295
|
+
function detectComplexBugs(code, file) {
|
|
37296
|
+
const out = [];
|
|
37297
|
+
detectDeserialization(code, file, out);
|
|
37298
|
+
detectTypeJuggling(code, file, out);
|
|
37299
|
+
detectMassAssignment(code, file, out);
|
|
37300
|
+
const seen = new Set;
|
|
37301
|
+
return out.filter((f) => {
|
|
37302
|
+
const key = `${f.type}:${f.line}`;
|
|
37303
|
+
if (seen.has(key))
|
|
37304
|
+
return false;
|
|
37305
|
+
seen.add(key);
|
|
37306
|
+
return true;
|
|
37307
|
+
});
|
|
37308
|
+
}
|
|
37309
|
+
var SOURCE_TOKENS2, MAGIC_METHODS;
|
|
37310
|
+
var init_complex_bugs = __esm(() => {
|
|
37311
|
+
SOURCE_TOKENS2 = /\$_(GET|POST|REQUEST|COOKIE|FILES|SERVER)\b|\$request->|\$req->|request\.(get|input|query|post|data)|getParameter\(|req\.(query|body|params)/;
|
|
37312
|
+
MAGIC_METHODS = /\b(__destruct|__wakeup|__toString|__invoke|__call|__get|__set|__isset|__unset|__sleep)\s*\(/;
|
|
37313
|
+
});
|
|
37314
|
+
|
|
37197
37315
|
// src/agents.ts
|
|
37198
37316
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
37199
37317
|
function emptyGraph() {
|
|
@@ -37218,6 +37336,7 @@ var init_agents = __esm(() => {
|
|
|
37218
37336
|
init_eagle2();
|
|
37219
37337
|
init_universal_taint();
|
|
37220
37338
|
init_route_confusion();
|
|
37339
|
+
init_complex_bugs();
|
|
37221
37340
|
init_adapters();
|
|
37222
37341
|
init_active();
|
|
37223
37342
|
init_strike();
|
|
@@ -37324,6 +37443,21 @@ var init_agents = __esm(() => {
|
|
|
37324
37443
|
graph = upsertNode(graph, nodeId, "route", { file: f, line: rc.line, type: rc.type, hypothesis: hyp.id });
|
|
37325
37444
|
graph = addEdge2(graph, tid, nodeId, "routes_to");
|
|
37326
37445
|
}
|
|
37446
|
+
for (const cb of detectComplexBugs(code, f).slice(0, 50)) {
|
|
37447
|
+
const hyp = makeHypothesis({
|
|
37448
|
+
type: cb.category,
|
|
37449
|
+
target: `${f}:${cb.line}`,
|
|
37450
|
+
evidence: cb.detail,
|
|
37451
|
+
confidence: cb.severity === "high" ? 0.7 : 0.55,
|
|
37452
|
+
required_validation: "confirm the payload reaches the sink and is exploitable",
|
|
37453
|
+
potential_impact: cb.type
|
|
37454
|
+
});
|
|
37455
|
+
hypotheses.push(hyp);
|
|
37456
|
+
findings += 1;
|
|
37457
|
+
const nodeId = `complex:${f}:${cb.line}:${cb.type}`;
|
|
37458
|
+
graph = upsertNode(graph, nodeId, "vulnerability", { file: f, line: cb.line, type: cb.type, hypothesis: hyp.id });
|
|
37459
|
+
graph = addEdge2(graph, tid, nodeId, "vulnerable_to");
|
|
37460
|
+
}
|
|
37327
37461
|
}
|
|
37328
37462
|
return { ok: true, output: { files: files.length, findings }, hypotheses, graph };
|
|
37329
37463
|
}
|
|
@@ -38267,6 +38401,13 @@ var init_enterprise_benchmark = __esm(() => {
|
|
|
38267
38401
|
expected_vulns: 1,
|
|
38268
38402
|
expected_sinks: ["Route confusion"],
|
|
38269
38403
|
safe_tokens: []
|
|
38404
|
+
},
|
|
38405
|
+
{
|
|
38406
|
+
id: "php-complex",
|
|
38407
|
+
dir: join17(ROOT10, "php-complex"),
|
|
38408
|
+
expected_vulns: 4,
|
|
38409
|
+
expected_sinks: ["Deserialization", "Type juggling", "Mass assignment"],
|
|
38410
|
+
safe_tokens: []
|
|
38270
38411
|
}
|
|
38271
38412
|
];
|
|
38272
38413
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blitzstrike",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.63",
|
|
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": {
|