blitzstrike 2.4.100 → 2.4.102

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 CHANGED
@@ -5,6 +5,31 @@ 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.102] — 2026-09-17
9
+
10
+ ### Added — `broken_link_scan` (broken-link hijacking live detector)
11
+
12
+ - New `broken_link_scan(url)` tool: crawls the target's outbound references
13
+ (script/link/img/src + inline JS URLs), then checks each referenced hostname's
14
+ DNS + RDAP registration status. Unregistered (RDAP 404) or in-drop-cycle
15
+ (pendingDelete/redemptionPeriod) domains are flagged CLAIMABLE — register the
16
+ expired domain and inherit the trust of every page that still loads it.
17
+ Verdict is derived live from DNS + RDAP, never a static list. Distinct from
18
+ `subdomain_takeover` (dangling CNAME); this is a fully-expired registrable
19
+ domain. Verified live: example.com → registered (not claimable), a
20
+ nonexistent domain → RDAP 404 → claimable.
21
+
22
+ ## [2.4.101] — 2026-09-17
23
+
24
+ ### Added — three new hunting classes (techniques + attack vectors)
25
+
26
+ - Added `broken_link_hijacking` (expired-domain takeover), `padding_oracle`
27
+ (CBC PKCS#7 cookie/token decryption), and `dns_rebinding` (hostname
28
+ re-resolution to internal IP) to the technique base (53 → 56 classes) and the
29
+ attack-vector taxonomy (588 → 591 vectors). Each carries summary, objectives,
30
+ how-to-test steps, concrete techniques, and verify (marker + negative
31
+ control) — matching the existing per-class methodology shape.
32
+
8
33
  ## [2.4.100] — 2026-09-17
9
34
 
10
35
  ### Fixed — data-file resolution was `process.cwd()`-dependent (broke under npx)
package/dist/index.js CHANGED
@@ -59295,6 +59295,163 @@ function dataRefresh() {
59295
59295
  init_verify_harness();
59296
59296
  init_variants();
59297
59297
 
59298
+ // src/broken-link.ts
59299
+ import { resolve4 as resolve43 } from "node:dns/promises";
59300
+ function hostFromUrl(u) {
59301
+ try {
59302
+ const h = new URL(u.startsWith("//") ? `https:${u}` : u).hostname.toLowerCase();
59303
+ return h || null;
59304
+ } catch {
59305
+ return null;
59306
+ }
59307
+ }
59308
+ var ALWAYS_ALIVE = new Set([
59309
+ "googleapis.com",
59310
+ "gstatic.com",
59311
+ "google.com",
59312
+ "googletagmanager.com",
59313
+ "google-analytics.com",
59314
+ "cloudflare.com",
59315
+ "cloudflareinsights.com",
59316
+ "jsdelivr.net",
59317
+ "unpkg.com",
59318
+ "cdnjs.cloudflare.com",
59319
+ "bootstrapcdn.com",
59320
+ "jquery.com",
59321
+ "fontawesome.com",
59322
+ "stackpathcdn.com",
59323
+ "w3.org",
59324
+ "schema.org",
59325
+ "github.com",
59326
+ "github.io",
59327
+ "npmjs.com",
59328
+ "nodejs.org",
59329
+ "microsoft.com",
59330
+ "azureedge.net",
59331
+ "azure.com",
59332
+ "akamai.net",
59333
+ "akamaized.net",
59334
+ "amazonaws.com",
59335
+ "amazon.com",
59336
+ "cloudfront.net",
59337
+ "youtube.com",
59338
+ "youtu.be",
59339
+ "instagram.com",
59340
+ "facebook.com",
59341
+ "fbcdn.net",
59342
+ "twitter.com",
59343
+ "x.com",
59344
+ "tiktok.com",
59345
+ "pinterest.com",
59346
+ "linkedin.com",
59347
+ "apple.com",
59348
+ "mozilla.org",
59349
+ "gravatar.com",
59350
+ "twimg.com",
59351
+ "g.co",
59352
+ "goo.gl",
59353
+ "bit.ly",
59354
+ "tinyurl.com",
59355
+ "paypal.com",
59356
+ "stripe.com",
59357
+ "shopify.com",
59358
+ "wordpress.org",
59359
+ "wp.com"
59360
+ ]);
59361
+ function extractOutboundHosts(html, baseHost) {
59362
+ const hosts = new Set;
59363
+ for (const m of html.matchAll(/(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
59364
+ const h = hostFromUrl(m[1]);
59365
+ if (h)
59366
+ hosts.add(h);
59367
+ }
59368
+ for (const m of html.matchAll(/https?:\/\/([a-z0-9.-]+)/gi)) {
59369
+ hosts.add(m[1].toLowerCase());
59370
+ }
59371
+ const base = baseHost.toLowerCase().replace(/^www\./, "");
59372
+ const out = [...hosts].filter((h) => {
59373
+ const lh = h.toLowerCase().replace(/^www\./, "");
59374
+ if (lh === base || lh.endsWith(`.${base}`))
59375
+ return false;
59376
+ if (ALWAYS_ALIVE.has(lh))
59377
+ return false;
59378
+ for (const s of ALWAYS_ALIVE)
59379
+ if (lh.endsWith(`.${s}`))
59380
+ return false;
59381
+ return true;
59382
+ });
59383
+ return [...new Set(out)].sort();
59384
+ }
59385
+ async function checkDomainStatus(domain) {
59386
+ let resolved = true;
59387
+ try {
59388
+ await resolve43(domain);
59389
+ } catch {
59390
+ resolved = false;
59391
+ }
59392
+ try {
59393
+ const res = await fetch(`https://rdap.org/domain/${domain}`, {
59394
+ signal: AbortSignal.timeout(7000),
59395
+ headers: { accept: "application/json" }
59396
+ });
59397
+ if (res.status === 404) {
59398
+ return { status: "unregistered", claimable: true, evidence: "RDAP 404 — domain not registered, registrable now" };
59399
+ }
59400
+ const data = await res.json();
59401
+ const statuses = data.status ?? [];
59402
+ if (statuses.includes("pendingDelete") || statuses.includes("redemptionPeriod")) {
59403
+ return { status: statuses.join(","), claimable: true, evidence: `RDAP ${statuses.join(",")} — in the drop cycle, registrable soon` };
59404
+ }
59405
+ if (statuses.length > 0) {
59406
+ return { status: statuses.join(","), claimable: false, evidence: `RDAP ${statuses.join(",")} — still registered${resolved ? "" : " (held, no DNS)"}` };
59407
+ }
59408
+ return { status: resolved ? "resolved" : "nxdomain", claimable: false, evidence: "RDAP returned no status (inconclusive)" };
59409
+ } catch {
59410
+ return { status: resolved ? "resolved" : "nxdomain", claimable: false, evidence: resolved ? "RDAP lookup failed — cannot confirm registration status" : "NXDOMAIN + RDAP lookup failed (inconclusive)" };
59411
+ }
59412
+ }
59413
+ async function brokenLinkScan(url, maxHosts = 30) {
59414
+ const base = url.startsWith("http") ? url : `https://${url}`;
59415
+ const baseHost = (() => {
59416
+ try {
59417
+ return new URL(base).hostname;
59418
+ } catch {
59419
+ return url;
59420
+ }
59421
+ })();
59422
+ let html = "";
59423
+ try {
59424
+ const res = await fetch(base, { signal: AbortSignal.timeout(15000), redirect: "follow", headers: researchHeaders() });
59425
+ html = (await res.text()).slice(0, 1e6);
59426
+ } catch {
59427
+ return { target: base, base_host: baseHost, outbound_hosts: 0, checked: 0, claimable: [], inconclusive: [] };
59428
+ }
59429
+ const hosts = extractOutboundHosts(html, baseHost).slice(0, maxHosts);
59430
+ const claimable = [];
59431
+ const inconclusive = [];
59432
+ for (let i = 0;i < hosts.length; i += 5) {
59433
+ const batch = hosts.slice(i, i + 5);
59434
+ const results = await Promise.all(batch.map(async (h) => {
59435
+ const r = await checkDomainStatus(h);
59436
+ return { domain: h, ...r };
59437
+ }));
59438
+ for (const r of results) {
59439
+ if (r.claimable)
59440
+ claimable.push({ domain: r.domain, status: r.status, claimable: true, evidence: r.evidence, source: "outbound-reference" });
59441
+ else if (r.status === "nxdomain")
59442
+ inconclusive.push(r.domain);
59443
+ }
59444
+ }
59445
+ return {
59446
+ target: base,
59447
+ base_host: baseHost,
59448
+ outbound_hosts: hosts.length,
59449
+ checked: hosts.length,
59450
+ claimable,
59451
+ inconclusive
59452
+ };
59453
+ }
59454
+
59298
59455
  // src/benchmark.ts
59299
59456
  import { readFileSync as readFileSync22, readdirSync as readdirSync5, writeFileSync as writeFileSync6, rmSync as rmSync3, mkdirSync as mkdirSync7 } from "node:fs";
59300
59457
  import { join as join22 } from "node:path";
@@ -62890,6 +63047,15 @@ ${statusLine}` : statusLine },
62890
63047
  }, async () => {
62891
63048
  return { content: [{ type: "text", text: JSON.stringify(watchlist()) }] };
62892
63049
  });
63050
+ server.registerTool("broken_link_scan", {
63051
+ title: "Broken-link hijacking / expired-domain takeover (live detector)",
63052
+ description: "Detect expired-domain takeover (broken-link hijacking): crawl the target's outbound references (script/link/img/src + inline JS URLs), then check each referenced hostname's DNS + RDAP registration status. A domain that is unregistered (RDAP 404) or in the drop cycle (pendingDelete/redemptionPeriod) is CLAIMABLE — register it and inherit the trust of every page that still loads it. Distinct from subdomain_takeover (a dangling CNAME); this is a fully-expired registrable domain. Verdict is derived live from DNS + RDAP, never a static list.",
63053
+ inputSchema: {
63054
+ url: string2().describe("Target URL (https://target.com) — crawls outbound references")
63055
+ }
63056
+ }, async ({ url }) => {
63057
+ return { content: [{ type: "text", text: JSON.stringify(await brokenLinkScan(url)) }] };
63058
+ });
62893
63059
  server.registerTool("auto_poc", {
62894
63060
  title: "Auto-PoC — generate a copy-paste-ready reproduction recipe",
62895
63061
  description: "Turn a finding into a concrete reproduction recipe (the 'reproducible' gate question made executable). Given a vuln class + target + endpoint + param, emit the exact request (with a MARKER), a NEGATIVE CONTROL, and the expected differential that PROVES the bug — plus a Patchstack-style title, derived severity, and the fix. Covers sql_injection, missing_authz/idor, ssrf, xss, ssti, command_injection, file_upload, mass_assignment, jwt_alg_confusion, crlf, open_redirect, xxe, prototype_pollution, deserialization, hardcoded_secret, cache_deception, cors_misconfiguration, subdomain_takeover. Pass type='list' to enumerate supported classes.",
@@ -63043,7 +63209,7 @@ ${statusLine}` : statusLine },
63043
63209
  });
63044
63210
  server.registerTool("technique_lookup", {
63045
63211
  title: "Detailed bug-hunting technique base",
63046
- description: "INTEL: return the DETAILED methodology + tricks for one vulnerability class (52 classes: ssrf, request smuggling, cache poisoning, race conditions, ssti, deserialization, prototype pollution, mass assignment, sql/nosql injection, xss, cors, csrf, jwt, oauth, host header, open redirect, subdomain takeover, idor, graphql, websocket, file upload, path traversal/LFI, command injection, xxe, crlf, ldap, xpath, xslt, ssi, xssi, email, formula/CSV, orm, rsql, unicode, dangling markup, account takeover, 2fa bypass, rate-limit, password reset, captcha, registration, payment bypass, timing, uuid, reverse tabnabbing, iframe traps, cookies, postMessage, ...). Each returns summary, objectives, how_to_test steps, concrete techniques, and verify (marker + negative control). Load before testing a class to hunt it in detail.",
63212
+ description: "INTEL: return the DETAILED methodology + tricks for one vulnerability class (56 classes: ssrf, request smuggling, cache poisoning, race conditions, ssti, deserialization, prototype pollution, mass assignment, sql/nosql injection, xss, cors, csrf, jwt, oauth, host header, open redirect, subdomain takeover, broken link hijacking, dns rebinding, padding oracle, idor, graphql, websocket, file upload, path traversal/LFI, command injection, xxe, crlf, ldap, xpath, xslt, ssi, xssi, email, formula/CSV, orm, rsql, unicode, dangling markup, account takeover, 2fa bypass, rate-limit, password reset, captcha, registration, payment bypass, timing, uuid, reverse tabnabbing, iframe traps, cookies, postMessage, ...). Each returns summary, objectives, how_to_test steps, concrete techniques, and verify (marker + negative control). Load before testing a class to hunt it in detail.",
63047
63213
  inputSchema: {
63048
63214
  cls: string2().optional().describe("Optional class id or name (e.g. 'ssrf', 'request smuggling', 'ssti').")
63049
63215
  }