fingerprint-mcp 0.1.0
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/LICENSE +663 -0
- package/README.ar.md +767 -0
- package/README.bn.md +767 -0
- package/README.bs.md +767 -0
- package/README.da.md +767 -0
- package/README.de.md +770 -0
- package/README.el.md +767 -0
- package/README.es.md +769 -0
- package/README.fr.md +769 -0
- package/README.hi.md +767 -0
- package/README.it.md +767 -0
- package/README.ja.md +767 -0
- package/README.ko.md +772 -0
- package/README.md +767 -0
- package/README.no.md +767 -0
- package/README.pl.md +767 -0
- package/README.pt-BR.md +767 -0
- package/README.ru.md +767 -0
- package/README.th.md +767 -0
- package/README.tr.md +767 -0
- package/README.uk.md +767 -0
- package/README.vi.md +767 -0
- package/README.zh-TW.md +768 -0
- package/README.zh.md +768 -0
- package/dist/app/index.d.ts +3 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +614 -0
- package/dist/app/index.js.map +1 -0
- package/dist/composite/analyze.d.ts +3 -0
- package/dist/composite/analyze.d.ts.map +1 -0
- package/dist/composite/analyze.js +59 -0
- package/dist/composite/analyze.js.map +1 -0
- package/dist/composite/correlate.d.ts +3 -0
- package/dist/composite/correlate.d.ts.map +1 -0
- package/dist/composite/correlate.js +184 -0
- package/dist/composite/correlate.js.map +1 -0
- package/dist/composite/enumerate.d.ts +3 -0
- package/dist/composite/enumerate.d.ts.map +1 -0
- package/dist/composite/enumerate.js +94 -0
- package/dist/composite/enumerate.js.map +1 -0
- package/dist/composite/helpers.d.ts +22 -0
- package/dist/composite/helpers.d.ts.map +1 -0
- package/dist/composite/helpers.js +111 -0
- package/dist/composite/helpers.js.map +1 -0
- package/dist/composite/index.d.ts +3 -0
- package/dist/composite/index.d.ts.map +1 -0
- package/dist/composite/index.js +29 -0
- package/dist/composite/index.js.map +1 -0
- package/dist/composite/meta.d.ts +3 -0
- package/dist/composite/meta.d.ts.map +1 -0
- package/dist/composite/meta.js +23 -0
- package/dist/composite/meta.js.map +1 -0
- package/dist/composite/osint.d.ts +3 -0
- package/dist/composite/osint.d.ts.map +1 -0
- package/dist/composite/osint.js +40 -0
- package/dist/composite/osint.js.map +1 -0
- package/dist/composite/recon.d.ts +3 -0
- package/dist/composite/recon.d.ts.map +1 -0
- package/dist/composite/recon.js +131 -0
- package/dist/composite/recon.js.map +1 -0
- package/dist/composite/scan-dns.d.ts +3 -0
- package/dist/composite/scan-dns.d.ts.map +1 -0
- package/dist/composite/scan-dns.js +44 -0
- package/dist/composite/scan-dns.js.map +1 -0
- package/dist/composite/scan-http.d.ts +3 -0
- package/dist/composite/scan-http.d.ts.map +1 -0
- package/dist/composite/scan-http.js +68 -0
- package/dist/composite/scan-http.js.map +1 -0
- package/dist/composite/scan-paths.d.ts +3 -0
- package/dist/composite/scan-paths.d.ts.map +1 -0
- package/dist/composite/scan-paths.js +32 -0
- package/dist/composite/scan-paths.js.map +1 -0
- package/dist/composite/scan-ports.d.ts +3 -0
- package/dist/composite/scan-ports.d.ts.map +1 -0
- package/dist/composite/scan-ports.js +79 -0
- package/dist/composite/scan-ports.js.map +1 -0
- package/dist/composite/scan-services.d.ts +3 -0
- package/dist/composite/scan-services.d.ts.map +1 -0
- package/dist/composite/scan-services.js +111 -0
- package/dist/composite/scan-services.js.map +1 -0
- package/dist/composite/scan-tls.d.ts +3 -0
- package/dist/composite/scan-tls.d.ts.map +1 -0
- package/dist/composite/scan-tls.js +32 -0
- package/dist/composite/scan-tls.js.map +1 -0
- package/dist/composite/scan-waf.d.ts +3 -0
- package/dist/composite/scan-waf.d.ts.map +1 -0
- package/dist/composite/scan-waf.js +35 -0
- package/dist/composite/scan-waf.js.map +1 -0
- package/dist/correlation/index.d.ts +3 -0
- package/dist/correlation/index.d.ts.map +1 -0
- package/dist/correlation/index.js +1044 -0
- package/dist/correlation/index.js.map +1 -0
- package/dist/data/analytics-patterns.d.ts +21 -0
- package/dist/data/analytics-patterns.d.ts.map +1 -0
- package/dist/data/analytics-patterns.js +449 -0
- package/dist/data/analytics-patterns.js.map +1 -0
- package/dist/data/app-signatures.d.ts +52 -0
- package/dist/data/app-signatures.d.ts.map +1 -0
- package/dist/data/app-signatures.js +1143 -0
- package/dist/data/app-signatures.js.map +1 -0
- package/dist/data/c2-signatures.d.ts +54 -0
- package/dist/data/c2-signatures.d.ts.map +1 -0
- package/dist/data/c2-signatures.js +256 -0
- package/dist/data/c2-signatures.js.map +1 -0
- package/dist/data/cloud-ranges.d.ts +34 -0
- package/dist/data/cloud-ranges.d.ts.map +1 -0
- package/dist/data/cloud-ranges.js +628 -0
- package/dist/data/cloud-ranges.js.map +1 -0
- package/dist/data/cookie-patterns.d.ts +28 -0
- package/dist/data/cookie-patterns.d.ts.map +1 -0
- package/dist/data/cookie-patterns.js +225 -0
- package/dist/data/cookie-patterns.js.map +1 -0
- package/dist/data/error-signatures.d.ts +19 -0
- package/dist/data/error-signatures.d.ts.map +1 -0
- package/dist/data/error-signatures.js +321 -0
- package/dist/data/error-signatures.js.map +1 -0
- package/dist/data/favicon-hashes.d.ts +25 -0
- package/dist/data/favicon-hashes.d.ts.map +1 -0
- package/dist/data/favicon-hashes.js +249 -0
- package/dist/data/favicon-hashes.js.map +1 -0
- package/dist/data/h2-signatures.d.ts +50 -0
- package/dist/data/h2-signatures.d.ts.map +1 -0
- package/dist/data/h2-signatures.js +211 -0
- package/dist/data/h2-signatures.js.map +1 -0
- package/dist/data/header-order.d.ts +38 -0
- package/dist/data/header-order.d.ts.map +1 -0
- package/dist/data/header-order.js +185 -0
- package/dist/data/header-order.js.map +1 -0
- package/dist/data/jarm-signatures.d.ts +25 -0
- package/dist/data/jarm-signatures.d.ts.map +1 -0
- package/dist/data/jarm-signatures.js +213 -0
- package/dist/data/jarm-signatures.js.map +1 -0
- package/dist/data/saas-patterns.d.ts +23 -0
- package/dist/data/saas-patterns.d.ts.map +1 -0
- package/dist/data/saas-patterns.js +139 -0
- package/dist/data/saas-patterns.js.map +1 -0
- package/dist/data/sensitive-paths.d.ts +12 -0
- package/dist/data/sensitive-paths.d.ts.map +1 -0
- package/dist/data/sensitive-paths.js +1539 -0
- package/dist/data/sensitive-paths.js.map +1 -0
- package/dist/data/service-banners.d.ts +59 -0
- package/dist/data/service-banners.d.ts.map +1 -0
- package/dist/data/service-banners.js +323 -0
- package/dist/data/service-banners.js.map +1 -0
- package/dist/data/smtp-signatures.d.ts +12 -0
- package/dist/data/smtp-signatures.d.ts.map +1 -0
- package/dist/data/smtp-signatures.js +350 -0
- package/dist/data/smtp-signatures.js.map +1 -0
- package/dist/data/takeover-patterns.d.ts +37 -0
- package/dist/data/takeover-patterns.d.ts.map +1 -0
- package/dist/data/takeover-patterns.js +249 -0
- package/dist/data/takeover-patterns.js.map +1 -0
- package/dist/data/tech-patterns.d.ts +44 -0
- package/dist/data/tech-patterns.d.ts.map +1 -0
- package/dist/data/tech-patterns.js +690 -0
- package/dist/data/tech-patterns.js.map +1 -0
- package/dist/data/waf-signatures.d.ts +21 -0
- package/dist/data/waf-signatures.d.ts.map +1 -0
- package/dist/data/waf-signatures.js +318 -0
- package/dist/data/waf-signatures.js.map +1 -0
- package/dist/dns/index.d.ts +3 -0
- package/dist/dns/index.d.ts.map +1 -0
- package/dist/dns/index.js +1067 -0
- package/dist/dns/index.js.map +1 -0
- package/dist/enum/index.d.ts +3 -0
- package/dist/enum/index.d.ts.map +1 -0
- package/dist/enum/index.js +818 -0
- package/dist/enum/index.js.map +1 -0
- package/dist/h2/index.d.ts +3 -0
- package/dist/h2/index.d.ts.map +1 -0
- package/dist/h2/index.js +414 -0
- package/dist/h2/index.js.map +1 -0
- package/dist/http/index.d.ts +3 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +2444 -0
- package/dist/http/index.js.map +1 -0
- package/dist/identify/index.d.ts +3 -0
- package/dist/identify/index.d.ts.map +1 -0
- package/dist/identify/index.js +447 -0
- package/dist/identify/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/infra/index.d.ts +3 -0
- package/dist/infra/index.d.ts.map +1 -0
- package/dist/infra/index.js +989 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/iot/index.d.ts +3 -0
- package/dist/iot/index.d.ts.map +1 -0
- package/dist/iot/index.js +610 -0
- package/dist/iot/index.js.map +1 -0
- package/dist/meta/index.d.ts +3 -0
- package/dist/meta/index.d.ts.map +1 -0
- package/dist/meta/index.js +442 -0
- package/dist/meta/index.js.map +1 -0
- package/dist/osint/index.d.ts +3 -0
- package/dist/osint/index.d.ts.map +1 -0
- package/dist/osint/index.js +687 -0
- package/dist/osint/index.js.map +1 -0
- package/dist/passive/index.d.ts +3 -0
- package/dist/passive/index.d.ts.map +1 -0
- package/dist/passive/index.js +944 -0
- package/dist/passive/index.js.map +1 -0
- package/dist/path/index.d.ts +3 -0
- package/dist/path/index.d.ts.map +1 -0
- package/dist/path/index.js +878 -0
- package/dist/path/index.js.map +1 -0
- package/dist/protocol/mcp-server.d.ts +4 -0
- package/dist/protocol/mcp-server.d.ts.map +1 -0
- package/dist/protocol/mcp-server.js +32 -0
- package/dist/protocol/mcp-server.js.map +1 -0
- package/dist/protocol/tools.d.ts +3 -0
- package/dist/protocol/tools.d.ts.map +1 -0
- package/dist/protocol/tools.js +5 -0
- package/dist/protocol/tools.js.map +1 -0
- package/dist/service/index.d.ts +3 -0
- package/dist/service/index.d.ts.map +1 -0
- package/dist/service/index.js +1053 -0
- package/dist/service/index.js.map +1 -0
- package/dist/smtp/index.d.ts +3 -0
- package/dist/smtp/index.d.ts.map +1 -0
- package/dist/smtp/index.js +437 -0
- package/dist/smtp/index.js.map +1 -0
- package/dist/ssh/index.d.ts +3 -0
- package/dist/ssh/index.d.ts.map +1 -0
- package/dist/ssh/index.js +676 -0
- package/dist/ssh/index.js.map +1 -0
- package/dist/tcp/index.d.ts +3 -0
- package/dist/tcp/index.d.ts.map +1 -0
- package/dist/tcp/index.js +369 -0
- package/dist/tcp/index.js.map +1 -0
- package/dist/timing/index.d.ts +3 -0
- package/dist/timing/index.d.ts.map +1 -0
- package/dist/timing/index.js +425 -0
- package/dist/timing/index.js.map +1 -0
- package/dist/tls/index.d.ts +3 -0
- package/dist/tls/index.d.ts.map +1 -0
- package/dist/tls/index.js +1332 -0
- package/dist/tls/index.js.map +1 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cache.d.ts +11 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +35 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/murmurhash3.d.ts +3 -0
- package/dist/utils/murmurhash3.d.ts.map +1 -0
- package/dist/utils/murmurhash3.js +57 -0
- package/dist/utils/murmurhash3.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +10 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +35 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/require-key.d.ts +2 -0
- package/dist/utils/require-key.d.ts.map +1 -0
- package/dist/utils/require-key.js +8 -0
- package/dist/utils/require-key.js.map +1 -0
- package/dist/waf/index.d.ts +3 -0
- package/dist/waf/index.d.ts.map +1 -0
- package/dist/waf/index.js +767 -0
- package/dist/waf/index.js.map +1 -0
- package/dist/web/index.d.ts +3 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +1366 -0
- package/dist/web/index.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolve4, resolveCname } from "node:dns/promises";
|
|
3
|
+
import { json } from "../types/index.js";
|
|
4
|
+
import { RateLimiter } from "../utils/rate-limiter.js";
|
|
5
|
+
import { TTLCache } from "../utils/cache.js";
|
|
6
|
+
// ─── Rate Limiter & Cache ───
|
|
7
|
+
const limiter = new RateLimiter(500); // slower for WAF — avoid triggering blocks
|
|
8
|
+
const cache = new TTLCache(300_000);
|
|
9
|
+
const WAF_HEADER_SIGNATURES = {
|
|
10
|
+
"cf-ray": { name: "Cloudflare", vendor: "Cloudflare, Inc." },
|
|
11
|
+
"x-sucuri-id": { name: "Sucuri WAF", vendor: "Sucuri (GoDaddy)" },
|
|
12
|
+
"x-amzn-requestid": { name: "AWS WAF", vendor: "Amazon Web Services" },
|
|
13
|
+
"x-akamai-transformed": { name: "Akamai", vendor: "Akamai Technologies" },
|
|
14
|
+
"x-sigsci-tags": { name: "Signal Sciences", vendor: "Signal Sciences (Fastly)" },
|
|
15
|
+
"x-sigsci-decisionscore": { name: "Signal Sciences", vendor: "Signal Sciences (Fastly)" },
|
|
16
|
+
"x-sigsci-requestid": { name: "Signal Sciences", vendor: "Signal Sciences (Fastly)" },
|
|
17
|
+
"x-wallarm-waf-check": { name: "Wallarm", vendor: "Wallarm" },
|
|
18
|
+
};
|
|
19
|
+
const WAF_SERVER_SIGNATURES = {
|
|
20
|
+
cloudflare: { name: "Cloudflare", vendor: "Cloudflare, Inc." },
|
|
21
|
+
akamaighost: { name: "Akamai", vendor: "Akamai Technologies" },
|
|
22
|
+
"ddos-guard": { name: "DDoS-Guard", vendor: "DDoS-Guard" },
|
|
23
|
+
"nginx-wallarm": { name: "Wallarm", vendor: "Wallarm" },
|
|
24
|
+
};
|
|
25
|
+
const WAF_COOKIE_SIGNATURES = {
|
|
26
|
+
"cf-chl-bypass": { name: "Cloudflare", vendor: "Cloudflare, Inc." },
|
|
27
|
+
___utmvc: { name: "Imperva Incapsula", vendor: "Imperva" },
|
|
28
|
+
fortiwafsid: { name: "FortiWeb", vendor: "Fortinet" },
|
|
29
|
+
barra_counter_session: { name: "Barracuda WAF", vendor: "Barracuda Networks" },
|
|
30
|
+
};
|
|
31
|
+
const WAF_BODY_SIGNATURES = [
|
|
32
|
+
{ pattern: /Attention Required.*Cloudflare|cf-error-details|Ray ID:/i, name: "Cloudflare", vendor: "Cloudflare, Inc." },
|
|
33
|
+
{ pattern: /Request blocked|automated access.*denied.*AWS/i, name: "AWS WAF", vendor: "Amazon Web Services" },
|
|
34
|
+
{ pattern: /ModSecurity|Mod_Security|mod_security module/i, name: "ModSecurity", vendor: "Trustwave / OWASP" },
|
|
35
|
+
{ pattern: /Incapsula incident|Powered By Incapsula|_Incapsula_Resource/i, name: "Imperva Incapsula", vendor: "Imperva" },
|
|
36
|
+
{ pattern: /Reference #18\.[0-9a-f]+|AkamaiGHost/i, name: "Akamai", vendor: "Akamai Technologies" },
|
|
37
|
+
{ pattern: /The requested URL was rejected|Your support ID is:/i, name: "F5 BIG-IP ASM", vendor: "F5 Networks" },
|
|
38
|
+
{ pattern: /Sucuri Website Firewall|Access Denied.*Sucuri/i, name: "Sucuri WAF", vendor: "Sucuri (GoDaddy)" },
|
|
39
|
+
{ pattern: /Barracuda Networks|You have been blocked.*barracuda/i, name: "Barracuda WAF", vendor: "Barracuda Networks" },
|
|
40
|
+
{ pattern: /DDoS-Guard|ddos-guard\.net/i, name: "DDoS-Guard", vendor: "DDoS-Guard" },
|
|
41
|
+
{ pattern: /Generated by Wordfence|wordfence\.com|wfwaf-authcookie-/i, name: "Wordfence", vendor: "Defiant Inc." },
|
|
42
|
+
{ pattern: /FortiWeb|fortigate|\.fgd_icon/i, name: "FortiWeb", vendor: "Fortinet" },
|
|
43
|
+
{ pattern: /wallarm|nginx-wallarm/i, name: "Wallarm", vendor: "Wallarm" },
|
|
44
|
+
{ pattern: /Signal Sciences/i, name: "Signal Sciences", vendor: "Signal Sciences (Fastly)" },
|
|
45
|
+
];
|
|
46
|
+
// ─── CDN Signature Maps ───
|
|
47
|
+
const CDN_HEADER_SIGNATURES = {
|
|
48
|
+
"cf-ray": "Cloudflare",
|
|
49
|
+
"x-amz-cf-id": "Amazon CloudFront",
|
|
50
|
+
"x-akamai-transformed": "Akamai",
|
|
51
|
+
"x-fastly-request-id": "Fastly",
|
|
52
|
+
};
|
|
53
|
+
const CDN_SERVER_SIGNATURES = {
|
|
54
|
+
cloudflare: "Cloudflare",
|
|
55
|
+
amazons3: "Amazon S3",
|
|
56
|
+
akamaihost: "Akamai",
|
|
57
|
+
akamainetworkhttpmonitor: "Akamai",
|
|
58
|
+
varnish: "Varnish (generic CDN cache)",
|
|
59
|
+
};
|
|
60
|
+
const CDN_CNAME_PATTERNS = {
|
|
61
|
+
"cloudfront.net": "Amazon CloudFront",
|
|
62
|
+
"edgekey.net": "Akamai",
|
|
63
|
+
"edgesuite.net": "Akamai",
|
|
64
|
+
"akamaiedge.net": "Akamai",
|
|
65
|
+
"fastly.net": "Fastly",
|
|
66
|
+
"cloudflare.net": "Cloudflare",
|
|
67
|
+
"azureedge.net": "Azure CDN",
|
|
68
|
+
"stackpathdns.com": "StackPath",
|
|
69
|
+
"impervadns.net": "Imperva",
|
|
70
|
+
"sucuri.net": "Sucuri",
|
|
71
|
+
};
|
|
72
|
+
// ─── Helpers ───
|
|
73
|
+
function parseCookies(headers) {
|
|
74
|
+
const cookies = new Map();
|
|
75
|
+
const setCookie = headers.getSetCookie?.() ?? [];
|
|
76
|
+
for (const raw of setCookie) {
|
|
77
|
+
const name = raw.split("=")[0]?.trim();
|
|
78
|
+
if (name)
|
|
79
|
+
cookies.set(name.toLowerCase(), raw);
|
|
80
|
+
}
|
|
81
|
+
return cookies;
|
|
82
|
+
}
|
|
83
|
+
function extractHostname(url) {
|
|
84
|
+
try {
|
|
85
|
+
return new URL(url).hostname;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return url;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function safeFetch(url, options) {
|
|
92
|
+
try {
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
95
|
+
const res = await fetch(url, {
|
|
96
|
+
...options,
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
redirect: "follow",
|
|
99
|
+
});
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function safeDnsResolve4(hostname) {
|
|
108
|
+
try {
|
|
109
|
+
return await resolve4(hostname);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function safeDnsResolveCname(hostname) {
|
|
116
|
+
try {
|
|
117
|
+
return await resolveCname(hostname);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const ATTACK_PAYLOADS = [
|
|
124
|
+
{
|
|
125
|
+
type: "xss",
|
|
126
|
+
label: "XSS (reflected)",
|
|
127
|
+
build: (base) => ({
|
|
128
|
+
url: `${base}?q=${encodeURIComponent("<script>alert(1)</script>")}`,
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "sqli",
|
|
133
|
+
label: "SQL Injection",
|
|
134
|
+
build: (base) => ({
|
|
135
|
+
url: `${base}?id=${encodeURIComponent("' OR '1'='1")}`,
|
|
136
|
+
}),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "path_traversal",
|
|
140
|
+
label: "Path Traversal",
|
|
141
|
+
build: (base) => {
|
|
142
|
+
const u = new URL(base);
|
|
143
|
+
u.pathname = "/../../../etc/passwd";
|
|
144
|
+
return { url: u.toString() };
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
type: "scanner_ua",
|
|
149
|
+
label: "Scanner User-Agent",
|
|
150
|
+
build: (base) => ({
|
|
151
|
+
url: base,
|
|
152
|
+
headers: { "User-Agent": "sqlmap/1.0" },
|
|
153
|
+
}),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: "cmdi",
|
|
157
|
+
label: "Command Injection",
|
|
158
|
+
build: (base) => ({
|
|
159
|
+
url: `${base}?cmd=${encodeURIComponent("; cat /etc/passwd")}`,
|
|
160
|
+
}),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
// ─── Tool 1: waf_detect ───
|
|
164
|
+
const wafDetect = {
|
|
165
|
+
name: "waf_detect",
|
|
166
|
+
description: "Identify Web Application Firewalls by probing with common attack payloads (XSS, SQLi, path traversal, scanner UA, command injection) " +
|
|
167
|
+
"and matching block responses against known WAF signatures. Detects Cloudflare, AWS WAF, ModSecurity, Imperva, Akamai, F5 ASM, Fortinet, " +
|
|
168
|
+
"Sucuri, Barracuda, Wallarm, Signal Sciences, DDoS-Guard, Wordfence, and more.",
|
|
169
|
+
schema: {
|
|
170
|
+
url: z.string().url().describe("URL to probe for WAF detection"),
|
|
171
|
+
},
|
|
172
|
+
async execute(args) {
|
|
173
|
+
const url = args.url;
|
|
174
|
+
const cacheKey = `waf_detect:${url}`;
|
|
175
|
+
const cached = cache.get(cacheKey);
|
|
176
|
+
if (cached)
|
|
177
|
+
return json(cached);
|
|
178
|
+
await limiter.acquire();
|
|
179
|
+
const detections = new Map();
|
|
180
|
+
// Send each attack payload and analyze responses
|
|
181
|
+
for (const payload of ATTACK_PAYLOADS) {
|
|
182
|
+
const { url: probeUrl, headers: extraHeaders } = payload.build(url);
|
|
183
|
+
const res = await safeFetch(probeUrl, {
|
|
184
|
+
headers: {
|
|
185
|
+
"User-Agent": extraHeaders?.["User-Agent"] ??
|
|
186
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
187
|
+
Accept: "text/html,application/xhtml+xml,*/*",
|
|
188
|
+
...extraHeaders,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (!res)
|
|
192
|
+
continue;
|
|
193
|
+
const status = res.status;
|
|
194
|
+
const body = await res.text().catch(() => "");
|
|
195
|
+
const cookies = parseCookies(res.headers);
|
|
196
|
+
// Check response headers
|
|
197
|
+
for (const [header, sig] of Object.entries(WAF_HEADER_SIGNATURES)) {
|
|
198
|
+
if (res.headers.has(header)) {
|
|
199
|
+
if (!detections.has(sig.name)) {
|
|
200
|
+
detections.set(sig.name, {
|
|
201
|
+
name: sig.name,
|
|
202
|
+
vendor: sig.vendor,
|
|
203
|
+
confidence: 0.8,
|
|
204
|
+
indicators: [],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
detections.get(sig.name).indicators.push(`Header '${header}' present (${payload.type})`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Check Server header
|
|
211
|
+
const server = res.headers.get("server")?.toLowerCase() ?? "";
|
|
212
|
+
for (const [pattern, sig] of Object.entries(WAF_SERVER_SIGNATURES)) {
|
|
213
|
+
if (server.includes(pattern)) {
|
|
214
|
+
if (!detections.has(sig.name)) {
|
|
215
|
+
detections.set(sig.name, {
|
|
216
|
+
name: sig.name,
|
|
217
|
+
vendor: sig.vendor,
|
|
218
|
+
confidence: 0.85,
|
|
219
|
+
indicators: [],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
detections.get(sig.name).indicators.push(`Server header matches '${pattern}' (${payload.type})`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Check cookies
|
|
226
|
+
for (const [cookiePattern, sig] of Object.entries(WAF_COOKIE_SIGNATURES)) {
|
|
227
|
+
for (const [cookieName] of cookies) {
|
|
228
|
+
if (cookieName.includes(cookiePattern.toLowerCase())) {
|
|
229
|
+
if (!detections.has(sig.name)) {
|
|
230
|
+
detections.set(sig.name, {
|
|
231
|
+
name: sig.name,
|
|
232
|
+
vendor: sig.vendor,
|
|
233
|
+
confidence: 0.85,
|
|
234
|
+
indicators: [],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
detections.get(sig.name).indicators.push(`Cookie '${cookieName}' matches '${cookiePattern}' (${payload.type})`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// Check for incap_ses_* cookie pattern (Imperva)
|
|
242
|
+
for (const [cookieName] of cookies) {
|
|
243
|
+
if (/^incap_ses_/i.test(cookieName)) {
|
|
244
|
+
if (!detections.has("Imperva Incapsula")) {
|
|
245
|
+
detections.set("Imperva Incapsula", {
|
|
246
|
+
name: "Imperva Incapsula",
|
|
247
|
+
vendor: "Imperva",
|
|
248
|
+
confidence: 0.9,
|
|
249
|
+
indicators: [],
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
detections.get("Imperva Incapsula").indicators.push(`Cookie '${cookieName}' matches Imperva pattern (${payload.type})`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Check for TS* cookie prefix (F5 ASM)
|
|
256
|
+
for (const [cookieName] of cookies) {
|
|
257
|
+
if (/^TS[0-9a-f]{6,8}$/i.test(cookieName)) {
|
|
258
|
+
if (!detections.has("F5 BIG-IP ASM")) {
|
|
259
|
+
detections.set("F5 BIG-IP ASM", {
|
|
260
|
+
name: "F5 BIG-IP ASM",
|
|
261
|
+
vendor: "F5 Networks",
|
|
262
|
+
confidence: 0.85,
|
|
263
|
+
indicators: [],
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
detections.get("F5 BIG-IP ASM").indicators.push(`Cookie '${cookieName}' matches F5 TS* pattern (${payload.type})`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// Check for wfwaf-authcookie pattern (Wordfence)
|
|
270
|
+
for (const [cookieName] of cookies) {
|
|
271
|
+
if (/wfwaf-authcookie/i.test(cookieName)) {
|
|
272
|
+
if (!detections.has("Wordfence")) {
|
|
273
|
+
detections.set("Wordfence", {
|
|
274
|
+
name: "Wordfence",
|
|
275
|
+
vendor: "Defiant Inc.",
|
|
276
|
+
confidence: 0.9,
|
|
277
|
+
indicators: [],
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
detections.get("Wordfence").indicators.push(`Cookie '${cookieName}' matches Wordfence pattern (${payload.type})`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// Check body content
|
|
284
|
+
if (body && (status === 403 || status === 406 || status === 503)) {
|
|
285
|
+
for (const sig of WAF_BODY_SIGNATURES) {
|
|
286
|
+
if (sig.pattern.test(body)) {
|
|
287
|
+
if (!detections.has(sig.name)) {
|
|
288
|
+
detections.set(sig.name, {
|
|
289
|
+
name: sig.name,
|
|
290
|
+
vendor: sig.vendor,
|
|
291
|
+
confidence: 0.9,
|
|
292
|
+
indicators: [],
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
detections.get(sig.name).indicators.push(`Body matches '${sig.name}' pattern (status ${status}, ${payload.type})`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
await limiter.acquire();
|
|
300
|
+
}
|
|
301
|
+
// Deduplicate indicators per detection
|
|
302
|
+
for (const [, det] of detections) {
|
|
303
|
+
det.indicators = [...new Set(det.indicators)];
|
|
304
|
+
// Increase confidence based on number of indicators
|
|
305
|
+
det.confidence = Math.min(1.0, det.confidence + det.indicators.length * 0.02);
|
|
306
|
+
}
|
|
307
|
+
const result = {
|
|
308
|
+
url,
|
|
309
|
+
wafs_detected: Array.from(detections.values()),
|
|
310
|
+
total_detections: detections.size,
|
|
311
|
+
payloads_tested: ATTACK_PAYLOADS.length,
|
|
312
|
+
};
|
|
313
|
+
cache.set(cacheKey, result);
|
|
314
|
+
return json(result);
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
// ─── Tool 2: waf_cdn_detect ───
|
|
318
|
+
const wafCdnDetect = {
|
|
319
|
+
name: "waf_cdn_detect",
|
|
320
|
+
description: "Detect CDN provider for a given URL by analyzing response headers (cf-ray, x-amz-cf-id, x-akamai-transformed, x-fastly-request-id), " +
|
|
321
|
+
"Server header, Via header, DNS resolution (anycast detection via multiple IPs), and CNAME chain analysis " +
|
|
322
|
+
"(cloudfront.net, edgekey.net, fastly.net, cloudflare.net).",
|
|
323
|
+
schema: {
|
|
324
|
+
url: z.string().url().describe("URL to detect CDN provider"),
|
|
325
|
+
},
|
|
326
|
+
async execute(args) {
|
|
327
|
+
const url = args.url;
|
|
328
|
+
const cacheKey = `waf_cdn_detect:${url}`;
|
|
329
|
+
const cached = cache.get(cacheKey);
|
|
330
|
+
if (cached)
|
|
331
|
+
return json(cached);
|
|
332
|
+
await limiter.acquire();
|
|
333
|
+
const hostname = extractHostname(url);
|
|
334
|
+
const cdnIndicators = [];
|
|
335
|
+
// 1. HTTP response header analysis
|
|
336
|
+
const res = await safeFetch(url, {
|
|
337
|
+
headers: {
|
|
338
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
339
|
+
Accept: "text/html,application/xhtml+xml,*/*",
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
if (res) {
|
|
343
|
+
// Check CDN-specific headers
|
|
344
|
+
for (const [header, provider] of Object.entries(CDN_HEADER_SIGNATURES)) {
|
|
345
|
+
if (res.headers.has(header)) {
|
|
346
|
+
cdnIndicators.push({ provider, source: `Header: ${header}` });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Check x-cache header
|
|
350
|
+
const xCache = res.headers.get("x-cache");
|
|
351
|
+
if (xCache && /HIT|MISS/i.test(xCache)) {
|
|
352
|
+
cdnIndicators.push({ provider: "CDN (generic)", source: `x-cache: ${xCache}` });
|
|
353
|
+
}
|
|
354
|
+
// Check Server header
|
|
355
|
+
const server = res.headers.get("server")?.toLowerCase() ?? "";
|
|
356
|
+
for (const [pattern, provider] of Object.entries(CDN_SERVER_SIGNATURES)) {
|
|
357
|
+
if (server.includes(pattern)) {
|
|
358
|
+
cdnIndicators.push({ provider, source: `Server: ${server}` });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Check Via header
|
|
362
|
+
const via = res.headers.get("via");
|
|
363
|
+
if (via) {
|
|
364
|
+
cdnIndicators.push({ provider: "CDN (via header)", source: `Via: ${via}` });
|
|
365
|
+
if (/cloudfront/i.test(via))
|
|
366
|
+
cdnIndicators.push({ provider: "Amazon CloudFront", source: `Via contains cloudfront` });
|
|
367
|
+
if (/varnish/i.test(via))
|
|
368
|
+
cdnIndicators.push({ provider: "Varnish CDN", source: `Via contains varnish` });
|
|
369
|
+
if (/akamai/i.test(via))
|
|
370
|
+
cdnIndicators.push({ provider: "Akamai", source: `Via contains akamai` });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// 2. DNS resolution — multiple IPs = anycast = likely CDN
|
|
374
|
+
const ips = await safeDnsResolve4(hostname);
|
|
375
|
+
const isAnycast = ips.length > 2;
|
|
376
|
+
// 3. CNAME chain analysis
|
|
377
|
+
const cnames = await safeDnsResolveCname(hostname);
|
|
378
|
+
for (const cname of cnames) {
|
|
379
|
+
const cnameLower = cname.toLowerCase();
|
|
380
|
+
for (const [pattern, provider] of Object.entries(CDN_CNAME_PATTERNS)) {
|
|
381
|
+
if (cnameLower.includes(pattern)) {
|
|
382
|
+
cdnIndicators.push({ provider, source: `CNAME: ${cname}` });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Deduplicate by provider
|
|
387
|
+
const providerMap = new Map();
|
|
388
|
+
for (const ind of cdnIndicators) {
|
|
389
|
+
if (!providerMap.has(ind.provider))
|
|
390
|
+
providerMap.set(ind.provider, []);
|
|
391
|
+
providerMap.get(ind.provider).push(ind.source);
|
|
392
|
+
}
|
|
393
|
+
const detectedCdns = Array.from(providerMap.entries()).map(([provider, sources]) => ({
|
|
394
|
+
provider,
|
|
395
|
+
sources: [...new Set(sources)],
|
|
396
|
+
confidence: Math.min(1.0, 0.6 + sources.length * 0.1),
|
|
397
|
+
}));
|
|
398
|
+
const result = {
|
|
399
|
+
url,
|
|
400
|
+
hostname,
|
|
401
|
+
cdns_detected: detectedCdns,
|
|
402
|
+
dns: {
|
|
403
|
+
a_records: ips,
|
|
404
|
+
cname_records: cnames,
|
|
405
|
+
is_anycast: isAnycast,
|
|
406
|
+
ip_count: ips.length,
|
|
407
|
+
},
|
|
408
|
+
behind_cdn: detectedCdns.length > 0 || isAnycast,
|
|
409
|
+
};
|
|
410
|
+
cache.set(cacheKey, result);
|
|
411
|
+
return json(result);
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
// ─── Tool 3: waf_origin ───
|
|
415
|
+
const wafOrigin = {
|
|
416
|
+
name: "waf_origin",
|
|
417
|
+
description: "Attempt to discover the origin IP behind a CDN-protected domain. Techniques: subdomain DNS enumeration " +
|
|
418
|
+
"(mail, ftp, direct, origin, cpanel, webmail, smtp subdomains), certificate SAN cross-reference via crt.sh, " +
|
|
419
|
+
"HTML/JS source analysis for hardcoded IPs, and content comparison to verify discovered IPs serve the same site.",
|
|
420
|
+
schema: {
|
|
421
|
+
domain: z.string().describe("Domain to find origin IP behind CDN"),
|
|
422
|
+
},
|
|
423
|
+
async execute(args) {
|
|
424
|
+
const domain = args.domain;
|
|
425
|
+
const cacheKey = `waf_origin:${domain}`;
|
|
426
|
+
const cached = cache.get(cacheKey);
|
|
427
|
+
if (cached)
|
|
428
|
+
return json(cached);
|
|
429
|
+
await limiter.acquire();
|
|
430
|
+
const candidateIps = [];
|
|
431
|
+
// 1. Subdomain DNS enumeration — these often bypass CDN
|
|
432
|
+
const subdomains = [
|
|
433
|
+
"mail",
|
|
434
|
+
"ftp",
|
|
435
|
+
"direct",
|
|
436
|
+
"origin",
|
|
437
|
+
"cpanel",
|
|
438
|
+
"webmail",
|
|
439
|
+
"smtp",
|
|
440
|
+
"admin",
|
|
441
|
+
"dev",
|
|
442
|
+
"staging",
|
|
443
|
+
"api",
|
|
444
|
+
];
|
|
445
|
+
for (const sub of subdomains) {
|
|
446
|
+
const fqdn = `${sub}.${domain}`;
|
|
447
|
+
const ips = await safeDnsResolve4(fqdn);
|
|
448
|
+
for (const ip of ips) {
|
|
449
|
+
candidateIps.push({ ip, source: `DNS A record: ${fqdn}` });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Resolve the main domain IPs for comparison
|
|
453
|
+
const mainIps = await safeDnsResolve4(domain);
|
|
454
|
+
// 2. Certificate SAN cross-reference via crt.sh
|
|
455
|
+
const certIps = [];
|
|
456
|
+
try {
|
|
457
|
+
await limiter.acquire();
|
|
458
|
+
const crtRes = await safeFetch(`https://crt.sh/?q=${encodeURIComponent(domain)}&output=json`, {
|
|
459
|
+
headers: { Accept: "application/json" },
|
|
460
|
+
});
|
|
461
|
+
if (crtRes && crtRes.ok) {
|
|
462
|
+
const crtData = (await crtRes.json());
|
|
463
|
+
// Collect unique hostnames from certificate SANs
|
|
464
|
+
const certHostnames = new Set();
|
|
465
|
+
for (const entry of crtData.slice(0, 50)) {
|
|
466
|
+
const names = entry.name_value?.split("\n") ?? [];
|
|
467
|
+
for (const name of names) {
|
|
468
|
+
const trimmed = name.trim().replace(/^\*\./, "");
|
|
469
|
+
if (trimmed &&
|
|
470
|
+
trimmed !== domain &&
|
|
471
|
+
!trimmed.startsWith("*") &&
|
|
472
|
+
trimmed.endsWith(domain)) {
|
|
473
|
+
certHostnames.add(trimmed);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Resolve a subset of discovered hostnames
|
|
478
|
+
let resolved = 0;
|
|
479
|
+
for (const certHost of certHostnames) {
|
|
480
|
+
if (resolved >= 10)
|
|
481
|
+
break;
|
|
482
|
+
const ips = await safeDnsResolve4(certHost);
|
|
483
|
+
for (const ip of ips) {
|
|
484
|
+
candidateIps.push({ ip, source: `crt.sh SAN: ${certHost}` });
|
|
485
|
+
}
|
|
486
|
+
resolved++;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch {
|
|
491
|
+
// crt.sh may be unavailable
|
|
492
|
+
}
|
|
493
|
+
// 3. HTML/JS source analysis — look for hardcoded IPs
|
|
494
|
+
try {
|
|
495
|
+
await limiter.acquire();
|
|
496
|
+
const pageRes = await safeFetch(`https://${domain}`, {
|
|
497
|
+
headers: {
|
|
498
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
499
|
+
Accept: "text/html,*/*",
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
if (pageRes && pageRes.ok) {
|
|
503
|
+
const html = await pageRes.text();
|
|
504
|
+
// Match IPv4 addresses in HTML/JS content
|
|
505
|
+
const ipv4Regex = /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g;
|
|
506
|
+
let match;
|
|
507
|
+
const foundInSource = new Set();
|
|
508
|
+
while ((match = ipv4Regex.exec(html)) !== null) {
|
|
509
|
+
const ip = match[1];
|
|
510
|
+
// Filter out common non-routable and version-like patterns
|
|
511
|
+
if (!ip.startsWith("0.") &&
|
|
512
|
+
!ip.startsWith("127.") &&
|
|
513
|
+
!ip.startsWith("255.") &&
|
|
514
|
+
!ip.startsWith("10.") &&
|
|
515
|
+
!ip.startsWith("192.168.") &&
|
|
516
|
+
!/^172\.(1[6-9]|2\d|3[01])\./.test(ip) &&
|
|
517
|
+
// Skip version-like patterns (e.g., 1.0.0.0, 2.3.1.4)
|
|
518
|
+
!/^\d+\.\d+\.\d+\.\d+$/.test(ip) === false) {
|
|
519
|
+
foundInSource.add(ip);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
for (const ip of foundInSource) {
|
|
523
|
+
candidateIps.push({ ip, source: "Hardcoded IP in HTML/JS source" });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
// Page fetch may fail
|
|
529
|
+
}
|
|
530
|
+
// 4. Filter candidates — remove IPs that match the CDN-fronted IPs
|
|
531
|
+
const mainIpSet = new Set(mainIps);
|
|
532
|
+
const uniqueCandidates = new Map();
|
|
533
|
+
for (const { ip, source } of candidateIps) {
|
|
534
|
+
if (!mainIpSet.has(ip)) {
|
|
535
|
+
if (!uniqueCandidates.has(ip))
|
|
536
|
+
uniqueCandidates.set(ip, []);
|
|
537
|
+
uniqueCandidates.get(ip).push(source);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// 5. Content verification — check if candidate IPs serve the same content
|
|
541
|
+
const verified = [];
|
|
542
|
+
let checked = 0;
|
|
543
|
+
for (const [ip, sources] of uniqueCandidates) {
|
|
544
|
+
if (checked >= 5)
|
|
545
|
+
break; // Limit verification requests
|
|
546
|
+
let servesSameContent = false;
|
|
547
|
+
try {
|
|
548
|
+
await limiter.acquire();
|
|
549
|
+
const verifyRes = await safeFetch(`http://${ip}`, {
|
|
550
|
+
headers: {
|
|
551
|
+
Host: domain,
|
|
552
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
if (verifyRes && (verifyRes.ok || verifyRes.status === 301 || verifyRes.status === 302)) {
|
|
556
|
+
const body = await verifyRes.text().catch(() => "");
|
|
557
|
+
// Simple heuristic: check if the domain name appears in the response
|
|
558
|
+
servesSameContent = body.toLowerCase().includes(domain.toLowerCase());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
// Connection to IP may fail
|
|
563
|
+
}
|
|
564
|
+
verified.push({
|
|
565
|
+
ip,
|
|
566
|
+
sources: [...new Set(sources)],
|
|
567
|
+
serves_same_content: servesSameContent,
|
|
568
|
+
});
|
|
569
|
+
checked++;
|
|
570
|
+
}
|
|
571
|
+
// Remaining unverified candidates
|
|
572
|
+
for (const [ip, sources] of uniqueCandidates) {
|
|
573
|
+
if (!verified.some((v) => v.ip === ip)) {
|
|
574
|
+
verified.push({
|
|
575
|
+
ip,
|
|
576
|
+
sources: [...new Set(sources)],
|
|
577
|
+
serves_same_content: false,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const result = {
|
|
582
|
+
domain,
|
|
583
|
+
main_domain_ips: mainIps,
|
|
584
|
+
origin_candidates: verified.sort((a, b) => a.serves_same_content === b.serves_same_content ? 0 : a.serves_same_content ? -1 : 1),
|
|
585
|
+
total_candidates: verified.length,
|
|
586
|
+
likely_origins: verified.filter((v) => v.serves_same_content).map((v) => v.ip),
|
|
587
|
+
};
|
|
588
|
+
cache.set(cacheKey, result);
|
|
589
|
+
return json(result);
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
// ─── Tool 4: waf_fingerprint ───
|
|
593
|
+
const wafFingerprint = {
|
|
594
|
+
name: "waf_fingerprint",
|
|
595
|
+
description: "Deep WAF technology fingerprinting from block page analysis. Sends requests that trigger WAF blocks (XSS, SQLi, path traversal, " +
|
|
596
|
+
"command injection), parses block page HTML for vendor indicators (support IDs, reference numbers, custom error pages), " +
|
|
597
|
+
"determines WAF mode (block, detect-only, or CAPTCHA challenge), and maps WAF rule coverage across payload types.",
|
|
598
|
+
schema: {
|
|
599
|
+
url: z.string().url().describe("URL to fingerprint WAF technology"),
|
|
600
|
+
},
|
|
601
|
+
async execute(args) {
|
|
602
|
+
const url = args.url;
|
|
603
|
+
const cacheKey = `waf_fingerprint:${url}`;
|
|
604
|
+
const cached = cache.get(cacheKey);
|
|
605
|
+
if (cached)
|
|
606
|
+
return json(cached);
|
|
607
|
+
await limiter.acquire();
|
|
608
|
+
// First, get a baseline response
|
|
609
|
+
const baselineRes = await safeFetch(url, {
|
|
610
|
+
headers: {
|
|
611
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
612
|
+
Accept: "text/html,*/*",
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
const baselineStatus = baselineRes?.status ?? 0;
|
|
616
|
+
const baselineBody = baselineRes ? await baselineRes.text().catch(() => "") : "";
|
|
617
|
+
// Test each payload type
|
|
618
|
+
const payloadResults = [];
|
|
619
|
+
for (const payload of ATTACK_PAYLOADS) {
|
|
620
|
+
await limiter.acquire();
|
|
621
|
+
const { url: probeUrl, headers: extraHeaders } = payload.build(url);
|
|
622
|
+
const res = await safeFetch(probeUrl, {
|
|
623
|
+
headers: {
|
|
624
|
+
"User-Agent": extraHeaders?.["User-Agent"] ??
|
|
625
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
626
|
+
Accept: "text/html,*/*",
|
|
627
|
+
...extraHeaders,
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
if (!res) {
|
|
631
|
+
payloadResults.push({
|
|
632
|
+
type: payload.type,
|
|
633
|
+
label: payload.label,
|
|
634
|
+
status: 0,
|
|
635
|
+
blocked: false,
|
|
636
|
+
mode: "passthrough",
|
|
637
|
+
waf_indicators: ["Connection failed"],
|
|
638
|
+
});
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const status = res.status;
|
|
642
|
+
const body = await res.text().catch(() => "");
|
|
643
|
+
const indicators = [];
|
|
644
|
+
// Detect WAF indicators in block page
|
|
645
|
+
for (const sig of WAF_BODY_SIGNATURES) {
|
|
646
|
+
if (sig.pattern.test(body)) {
|
|
647
|
+
indicators.push(`Body: ${sig.name}`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
// Check for support/reference IDs in block page
|
|
651
|
+
const supportIdPatterns = [
|
|
652
|
+
{ pattern: /Ray ID:\s*([0-9a-f]+)/i, label: "Cloudflare Ray ID" },
|
|
653
|
+
{ pattern: /Reference #(\S+)/i, label: "Akamai Reference" },
|
|
654
|
+
{ pattern: /Your support ID is:\s*(\S+)/i, label: "F5 Support ID" },
|
|
655
|
+
{ pattern: /Incident ID:\s*(\S+)/i, label: "Incapsula Incident ID" },
|
|
656
|
+
{ pattern: /Block ID:\s*(\S+)/i, label: "Block ID" },
|
|
657
|
+
{ pattern: /Request ID:\s*(\S+)/i, label: "Request ID" },
|
|
658
|
+
{ pattern: /Event ID:\s*(\S+)/i, label: "Event ID" },
|
|
659
|
+
];
|
|
660
|
+
for (const { pattern, label } of supportIdPatterns) {
|
|
661
|
+
const m = body.match(pattern);
|
|
662
|
+
if (m) {
|
|
663
|
+
indicators.push(`${label}: ${m[1]}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// Check for custom error page elements
|
|
667
|
+
if (/<title>/i.test(body)) {
|
|
668
|
+
const titleMatch = body.match(/<title[^>]*>([^<]*)<\/title>/i);
|
|
669
|
+
if (titleMatch && titleMatch[1]) {
|
|
670
|
+
indicators.push(`Block page title: ${titleMatch[1].trim()}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// Determine WAF mode
|
|
674
|
+
let mode;
|
|
675
|
+
if (status === 403 || status === 406 || status === 503) {
|
|
676
|
+
// Check for CAPTCHA challenge
|
|
677
|
+
if (/captcha|challenge|hcaptcha|recaptcha|turnstile|cf-chl-widget/i.test(body)) {
|
|
678
|
+
mode = "challenge";
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
mode = "block";
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else if (status === 200) {
|
|
685
|
+
// Check for warning headers (detect-only mode)
|
|
686
|
+
const wafWarningHeaders = [
|
|
687
|
+
"x-sigsci-tags",
|
|
688
|
+
"x-wallarm-waf-check",
|
|
689
|
+
"x-waf-status",
|
|
690
|
+
];
|
|
691
|
+
const hasWarning = wafWarningHeaders.some((h) => res.headers.has(h));
|
|
692
|
+
if (hasWarning || indicators.length > 0) {
|
|
693
|
+
mode = "detect_only";
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
mode = "passthrough";
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
mode = status >= 400 ? "block" : "passthrough";
|
|
701
|
+
}
|
|
702
|
+
const blocked = mode === "block" || mode === "challenge";
|
|
703
|
+
payloadResults.push({
|
|
704
|
+
type: payload.type,
|
|
705
|
+
label: payload.label,
|
|
706
|
+
status,
|
|
707
|
+
blocked,
|
|
708
|
+
mode,
|
|
709
|
+
waf_indicators: indicators,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
// Aggregate WAF identification from all probe results
|
|
713
|
+
const allIndicators = payloadResults.flatMap((r) => r.waf_indicators);
|
|
714
|
+
const wafNames = new Set();
|
|
715
|
+
for (const ind of allIndicators) {
|
|
716
|
+
for (const sig of WAF_BODY_SIGNATURES) {
|
|
717
|
+
if (ind.includes(sig.name)) {
|
|
718
|
+
wafNames.add(sig.name);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
// Calculate coverage rating
|
|
723
|
+
const blockedCount = payloadResults.filter((r) => r.blocked).length;
|
|
724
|
+
const totalPayloads = payloadResults.length;
|
|
725
|
+
const coveragePercent = Math.round((blockedCount / totalPayloads) * 100);
|
|
726
|
+
let coverageRating;
|
|
727
|
+
if (coveragePercent >= 80)
|
|
728
|
+
coverageRating = "comprehensive";
|
|
729
|
+
else if (coveragePercent >= 60)
|
|
730
|
+
coverageRating = "good";
|
|
731
|
+
else if (coveragePercent >= 40)
|
|
732
|
+
coverageRating = "moderate";
|
|
733
|
+
else if (coveragePercent > 0)
|
|
734
|
+
coverageRating = "weak";
|
|
735
|
+
else
|
|
736
|
+
coverageRating = "none";
|
|
737
|
+
const result = {
|
|
738
|
+
url,
|
|
739
|
+
baseline_status: baselineStatus,
|
|
740
|
+
waf_identified: Array.from(wafNames),
|
|
741
|
+
waf_mode: payloadResults.some((r) => r.mode === "challenge")
|
|
742
|
+
? "challenge"
|
|
743
|
+
: payloadResults.some((r) => r.mode === "block")
|
|
744
|
+
? "block"
|
|
745
|
+
: payloadResults.some((r) => r.mode === "detect_only")
|
|
746
|
+
? "detect_only"
|
|
747
|
+
: "none",
|
|
748
|
+
coverage: {
|
|
749
|
+
payloads_tested: totalPayloads,
|
|
750
|
+
payloads_blocked: blockedCount,
|
|
751
|
+
coverage_percent: coveragePercent,
|
|
752
|
+
rating: coverageRating,
|
|
753
|
+
},
|
|
754
|
+
payload_results: payloadResults,
|
|
755
|
+
};
|
|
756
|
+
cache.set(cacheKey, result);
|
|
757
|
+
return json(result);
|
|
758
|
+
},
|
|
759
|
+
};
|
|
760
|
+
// ─── Export All WAF Tools ───
|
|
761
|
+
export const wafTools = [
|
|
762
|
+
wafDetect,
|
|
763
|
+
wafCdnDetect,
|
|
764
|
+
wafOrigin,
|
|
765
|
+
wafFingerprint,
|
|
766
|
+
];
|
|
767
|
+
//# sourceMappingURL=index.js.map
|