s3db.js 13.6.0 → 14.0.2
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/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DnsStage
|
|
3
|
+
*
|
|
4
|
+
* DNS enumeration stage:
|
|
5
|
+
* - A, AAAA, NS, MX, TXT records
|
|
6
|
+
* - Reverse DNS lookups
|
|
7
|
+
* - Error tracking per record type
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import dns from 'dns/promises';
|
|
11
|
+
|
|
12
|
+
export class DnsStage {
|
|
13
|
+
constructor(plugin) {
|
|
14
|
+
this.plugin = plugin;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async execute(target) {
|
|
18
|
+
const result = {
|
|
19
|
+
status: 'ok',
|
|
20
|
+
records: {},
|
|
21
|
+
errors: {}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const lookups = await Promise.allSettled([
|
|
26
|
+
dns.lookup(target.host, { all: true }),
|
|
27
|
+
dns.resolve4(target.host),
|
|
28
|
+
dns.resolve6(target.host).catch(() => []),
|
|
29
|
+
dns.resolveNs(target.host).catch(() => []),
|
|
30
|
+
dns.resolveMx(target.host).catch(() => []),
|
|
31
|
+
dns.resolveTxt(target.host).catch(() => [])
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const [lookupAll, aRecords, aaaaRecords, nsRecords, mxRecords, txtRecords] = lookups;
|
|
35
|
+
|
|
36
|
+
if (lookupAll.status === 'fulfilled') {
|
|
37
|
+
result.records.lookup = lookupAll.value;
|
|
38
|
+
} else {
|
|
39
|
+
result.errors.lookup = lookupAll.reason?.message;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
result.records.a = aRecords.status === 'fulfilled' ? aRecords.value : [];
|
|
43
|
+
if (aRecords.status === 'rejected') {
|
|
44
|
+
result.errors.a = aRecords.reason?.message;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
result.records.aaaa = aaaaRecords.status === 'fulfilled' ? aaaaRecords.value : [];
|
|
48
|
+
if (aaaaRecords.status === 'rejected') {
|
|
49
|
+
result.errors.aaaa = aaaaRecords.reason?.message;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result.records.ns = nsRecords.status === 'fulfilled' ? nsRecords.value : [];
|
|
53
|
+
if (nsRecords.status === 'rejected') {
|
|
54
|
+
result.errors.ns = nsRecords.reason?.message;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
result.records.mx = mxRecords.status === 'fulfilled' ? mxRecords.value : [];
|
|
58
|
+
if (mxRecords.status === 'rejected') {
|
|
59
|
+
result.errors.mx = mxRecords.reason?.message;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
result.records.txt = txtRecords.status === 'fulfilled' ? txtRecords.value : [];
|
|
63
|
+
if (txtRecords.status === 'rejected') {
|
|
64
|
+
result.errors.txt = txtRecords.reason?.message;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const allIps = [
|
|
68
|
+
...(result.records.a || []),
|
|
69
|
+
...(result.records.aaaa || [])
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
if (allIps.length > 0) {
|
|
73
|
+
const reverseLookups = await Promise.allSettled(
|
|
74
|
+
allIps.map(async (ip) => {
|
|
75
|
+
try {
|
|
76
|
+
const hosts = await dns.reverse(ip);
|
|
77
|
+
return { ip, hosts };
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return { ip, hosts: [], error };
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
result.records.reverse = {};
|
|
85
|
+
for (const entry of reverseLookups) {
|
|
86
|
+
if (entry.status === 'fulfilled') {
|
|
87
|
+
const { ip, hosts, error } = entry.value;
|
|
88
|
+
result.records.reverse[ip] = hosts;
|
|
89
|
+
if (error) {
|
|
90
|
+
result.errors[`reverse:${ip}`] = error?.message;
|
|
91
|
+
}
|
|
92
|
+
} else if (entry.reason?.ip) {
|
|
93
|
+
result.records.reverse[entry.reason.ip] = [];
|
|
94
|
+
result.errors[`reverse:${entry.reason.ip}`] = entry.reason.error?.message;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
result.records.reverse = {};
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
result.status = 'error';
|
|
102
|
+
result.message = error?.message || 'DNS lookup failed';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DNSDumpster Stage
|
|
3
|
+
*
|
|
4
|
+
* DNS Intelligence via dnsdumpster.com web scraping
|
|
5
|
+
*
|
|
6
|
+
* Discovers:
|
|
7
|
+
* - DNS records (A, AAAA, MX, TXT, NS)
|
|
8
|
+
* - Subdomains
|
|
9
|
+
* - Related domains
|
|
10
|
+
* - Network map data
|
|
11
|
+
*
|
|
12
|
+
* Uses 100% free web scraping (no API key required)
|
|
13
|
+
* - dnsdumpster.com (unlimited, requires CSRF token handling)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export class DNSDumpsterStage {
|
|
17
|
+
constructor(plugin) {
|
|
18
|
+
this.plugin = plugin;
|
|
19
|
+
this.commandRunner = plugin.commandRunner;
|
|
20
|
+
this.config = plugin.config;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute DNSDumpster lookup
|
|
25
|
+
* @param {Object} target - Target object with host property
|
|
26
|
+
* @param {Object} options - DNSDumpster options
|
|
27
|
+
* @returns {Promise<Object>} DNSDumpster results
|
|
28
|
+
*/
|
|
29
|
+
async execute(target, options = {}) {
|
|
30
|
+
const result = {
|
|
31
|
+
status: 'ok',
|
|
32
|
+
host: target.host,
|
|
33
|
+
dnsRecords: {
|
|
34
|
+
A: [],
|
|
35
|
+
AAAA: [],
|
|
36
|
+
MX: [],
|
|
37
|
+
TXT: [],
|
|
38
|
+
NS: []
|
|
39
|
+
},
|
|
40
|
+
subdomains: [],
|
|
41
|
+
relatedDomains: [],
|
|
42
|
+
errors: {}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Track individual tool results for artifact persistence
|
|
46
|
+
const individual = {
|
|
47
|
+
dnsdumpster: { status: 'ok', data: null, raw: null },
|
|
48
|
+
dig: { status: 'ok', records: {} }
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// DNSDumpster requires two-step process:
|
|
53
|
+
// 1. GET to obtain CSRF token
|
|
54
|
+
// 2. POST with token to get results
|
|
55
|
+
|
|
56
|
+
const baseUrl = 'https://dnsdumpster.com/';
|
|
57
|
+
|
|
58
|
+
// Step 1: Get CSRF token
|
|
59
|
+
const [csrfToken, cookie] = await this.getCsrfToken(baseUrl, options);
|
|
60
|
+
|
|
61
|
+
if (!csrfToken) {
|
|
62
|
+
result.status = 'error';
|
|
63
|
+
result.errors.csrf = 'Failed to obtain CSRF token from DNSDumpster';
|
|
64
|
+
individual.dnsdumpster.status = 'error';
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
_individual: individual,
|
|
68
|
+
_aggregated: result,
|
|
69
|
+
...result
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Step 2: Submit query
|
|
74
|
+
const data = await this.submitQuery(baseUrl, target.host, csrfToken, cookie, options);
|
|
75
|
+
|
|
76
|
+
if (!data) {
|
|
77
|
+
result.status = 'error';
|
|
78
|
+
result.errors.query = 'Failed to retrieve data from DNSDumpster';
|
|
79
|
+
individual.dnsdumpster.status = 'error';
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
_individual: individual,
|
|
83
|
+
_aggregated: result,
|
|
84
|
+
...result
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Save raw HTML if persistRawOutput is enabled
|
|
89
|
+
if (this.config?.storage?.persistRawOutput) {
|
|
90
|
+
individual.dnsdumpster.raw = data.substring(0, 50000); // Truncate to 50KB
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Step 3: Parse HTML response
|
|
94
|
+
const parsed = this.parseHtmlResponse(data);
|
|
95
|
+
|
|
96
|
+
result.dnsRecords = parsed.dnsRecords;
|
|
97
|
+
result.subdomains = parsed.subdomains;
|
|
98
|
+
result.relatedDomains = parsed.relatedDomains;
|
|
99
|
+
|
|
100
|
+
// Store parsed data in individual results
|
|
101
|
+
individual.dnsdumpster.data = parsed;
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
result.status = 'error';
|
|
105
|
+
result.errors.general = error.message;
|
|
106
|
+
individual.dnsdumpster.status = 'error';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fallback to dig if DNSDumpster fails
|
|
110
|
+
if (result.status === 'error' && options.fallbackToDig !== false) {
|
|
111
|
+
const digResults = await this.fallbackDigLookup(target.host);
|
|
112
|
+
result.dnsRecords = digResults.dnsRecords;
|
|
113
|
+
result.status = 'ok_fallback';
|
|
114
|
+
individual.dig = digResults;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
_individual: individual,
|
|
119
|
+
_aggregated: result,
|
|
120
|
+
...result // Root level for compatibility
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get CSRF token from DNSDumpster homepage
|
|
126
|
+
*/
|
|
127
|
+
async getCsrfToken(baseUrl, options = {}) {
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(baseUrl, {
|
|
130
|
+
headers: {
|
|
131
|
+
'User-Agent': this.config.curl?.userAgent || 'Mozilla/5.0 (compatible; ReconBot/1.0)'
|
|
132
|
+
},
|
|
133
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
return [null, null];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const html = await response.text();
|
|
141
|
+
const cookies = response.headers.get('set-cookie') || '';
|
|
142
|
+
|
|
143
|
+
// Extract CSRF token from HTML
|
|
144
|
+
// Format: <input type='hidden' name='csrfmiddlewaretoken' value='TOKEN' />
|
|
145
|
+
const csrfMatch = html.match(/name='csrfmiddlewaretoken'\s+value='([^']+)'/);
|
|
146
|
+
|
|
147
|
+
if (!csrfMatch) {
|
|
148
|
+
return [null, null];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const csrfToken = csrfMatch[1];
|
|
152
|
+
|
|
153
|
+
return [csrfToken, cookies];
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return [null, null];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Submit query to DNSDumpster
|
|
162
|
+
*/
|
|
163
|
+
async submitQuery(baseUrl, domain, csrfToken, cookie, options = {}) {
|
|
164
|
+
try {
|
|
165
|
+
const formData = new URLSearchParams();
|
|
166
|
+
formData.append('csrfmiddlewaretoken', csrfToken);
|
|
167
|
+
formData.append('targetip', domain);
|
|
168
|
+
formData.append('user', 'free');
|
|
169
|
+
|
|
170
|
+
const response = await fetch(baseUrl, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: {
|
|
173
|
+
'User-Agent': this.config.curl?.userAgent || 'Mozilla/5.0 (compatible; ReconBot/1.0)',
|
|
174
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
175
|
+
'Referer': baseUrl,
|
|
176
|
+
'Cookie': cookie
|
|
177
|
+
},
|
|
178
|
+
body: formData.toString(),
|
|
179
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(15000) : undefined
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return await response.text();
|
|
187
|
+
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Parse HTML response from DNSDumpster
|
|
195
|
+
*
|
|
196
|
+
* DNSDumpster returns HTML tables with DNS records.
|
|
197
|
+
* We need to extract data from these tables.
|
|
198
|
+
*/
|
|
199
|
+
parseHtmlResponse(html) {
|
|
200
|
+
const result = {
|
|
201
|
+
dnsRecords: {
|
|
202
|
+
A: [],
|
|
203
|
+
AAAA: [],
|
|
204
|
+
MX: [],
|
|
205
|
+
TXT: [],
|
|
206
|
+
NS: []
|
|
207
|
+
},
|
|
208
|
+
subdomains: [],
|
|
209
|
+
relatedDomains: []
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Extract DNS Host Records (A)
|
|
213
|
+
// Format: <td class="col-md-4">subdomain.example.com<br>IP</td>
|
|
214
|
+
const aRecordMatches = html.matchAll(/<tr[^>]*>[\s\S]*?<td[^>]*>([\w\-\.]+)<br>([\d\.]+)<\/td>[\s\S]*?<\/tr>/g);
|
|
215
|
+
for (const match of aRecordMatches) {
|
|
216
|
+
const hostname = match[1];
|
|
217
|
+
const ip = match[2];
|
|
218
|
+
if (hostname && ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) {
|
|
219
|
+
result.dnsRecords.A.push({ hostname, ip });
|
|
220
|
+
|
|
221
|
+
// Also add to subdomains if it's a subdomain
|
|
222
|
+
if (hostname.includes('.')) {
|
|
223
|
+
result.subdomains.push(hostname);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Extract MX Records
|
|
229
|
+
// Format: <td class="col-md-4">priority mail.example.com<br>IP</td>
|
|
230
|
+
const mxRecordMatches = html.matchAll(/<tr[^>]*>[\s\S]*?<td[^>]*>(\d+)\s+([\w\-\.]+)<br>([\d\.]+)<\/td>[\s\S]*?<\/tr>/g);
|
|
231
|
+
for (const match of mxRecordMatches) {
|
|
232
|
+
const priority = match[1];
|
|
233
|
+
const hostname = match[2];
|
|
234
|
+
const ip = match[3];
|
|
235
|
+
if (hostname && ip) {
|
|
236
|
+
result.dnsRecords.MX.push({ priority, hostname, ip });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Extract TXT Records
|
|
241
|
+
// Format: <td class="col-md-10">TXT content</td>
|
|
242
|
+
const txtSectionMatch = html.match(/TXT Records[\s\S]*?<table[^>]*>([\s\S]*?)<\/table>/i);
|
|
243
|
+
if (txtSectionMatch) {
|
|
244
|
+
const txtMatches = txtSectionMatch[1].matchAll(/<td[^>]*>([^<]+)<\/td>/g);
|
|
245
|
+
for (const match of txtMatches) {
|
|
246
|
+
const content = match[1].trim();
|
|
247
|
+
if (content && content.length > 0) {
|
|
248
|
+
result.dnsRecords.TXT.push({ content });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Extract NS Records
|
|
254
|
+
// Format similar to A records
|
|
255
|
+
const nsSectionMatch = html.match(/DNS Servers[\s\S]*?<table[^>]*>([\s\S]*?)<\/table>/i);
|
|
256
|
+
if (nsSectionMatch) {
|
|
257
|
+
const nsMatches = nsSectionMatch[1].matchAll(/<td[^>]*>([\w\-\.]+)<br>([\d\.]+)<\/td>/g);
|
|
258
|
+
for (const match of nsMatches) {
|
|
259
|
+
const hostname = match[1];
|
|
260
|
+
const ip = match[2];
|
|
261
|
+
if (hostname && ip) {
|
|
262
|
+
result.dnsRecords.NS.push({ hostname, ip });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Deduplicate subdomains
|
|
268
|
+
result.subdomains = [...new Set(result.subdomains)];
|
|
269
|
+
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Fallback: Use dig commands for basic DNS records
|
|
275
|
+
* This is used if DNSDumpster scraping fails
|
|
276
|
+
*/
|
|
277
|
+
async fallbackDigLookup(host) {
|
|
278
|
+
const result = {
|
|
279
|
+
dnsRecords: {
|
|
280
|
+
A: [],
|
|
281
|
+
AAAA: [],
|
|
282
|
+
MX: [],
|
|
283
|
+
TXT: [],
|
|
284
|
+
NS: []
|
|
285
|
+
},
|
|
286
|
+
subdomains: [],
|
|
287
|
+
relatedDomains: []
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
// A records
|
|
292
|
+
const aRun = await this.commandRunner.run('dig', ['+short', 'A', host], { timeout: 5000 });
|
|
293
|
+
if (aRun.ok && aRun.stdout) {
|
|
294
|
+
const ips = aRun.stdout
|
|
295
|
+
.split('\n')
|
|
296
|
+
.map(line => line.trim())
|
|
297
|
+
.filter(line => /^\d+\.\d+\.\d+\.\d+$/.test(line));
|
|
298
|
+
|
|
299
|
+
result.dnsRecords.A = ips.map(ip => ({ hostname: host, ip }));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// AAAA records
|
|
303
|
+
const aaaaRun = await this.commandRunner.run('dig', ['+short', 'AAAA', host], { timeout: 5000 });
|
|
304
|
+
if (aaaaRun.ok && aaaaRun.stdout) {
|
|
305
|
+
const ips = aaaaRun.stdout
|
|
306
|
+
.split('\n')
|
|
307
|
+
.map(line => line.trim())
|
|
308
|
+
.filter(line => /^[0-9a-f:]+$/i.test(line) && line.includes(':'));
|
|
309
|
+
|
|
310
|
+
result.dnsRecords.AAAA = ips.map(ip => ({ hostname: host, ip }));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// MX records
|
|
314
|
+
const mxRun = await this.commandRunner.run('dig', ['+short', 'MX', host], { timeout: 5000 });
|
|
315
|
+
if (mxRun.ok && mxRun.stdout) {
|
|
316
|
+
const mxRecords = mxRun.stdout
|
|
317
|
+
.split('\n')
|
|
318
|
+
.map(line => line.trim())
|
|
319
|
+
.filter(line => line.length > 0)
|
|
320
|
+
.map(line => {
|
|
321
|
+
const parts = line.split(' ');
|
|
322
|
+
if (parts.length === 2) {
|
|
323
|
+
return { priority: parts[0], hostname: parts[1].replace(/\.$/, ''), ip: null };
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
})
|
|
327
|
+
.filter(Boolean);
|
|
328
|
+
|
|
329
|
+
result.dnsRecords.MX = mxRecords;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// TXT records
|
|
333
|
+
const txtRun = await this.commandRunner.run('dig', ['+short', 'TXT', host], { timeout: 5000 });
|
|
334
|
+
if (txtRun.ok && txtRun.stdout) {
|
|
335
|
+
const txtRecords = txtRun.stdout
|
|
336
|
+
.split('\n')
|
|
337
|
+
.map(line => line.trim().replace(/"/g, ''))
|
|
338
|
+
.filter(line => line.length > 0)
|
|
339
|
+
.map(content => ({ content }));
|
|
340
|
+
|
|
341
|
+
result.dnsRecords.TXT = txtRecords;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// NS records
|
|
345
|
+
const nsRun = await this.commandRunner.run('dig', ['+short', 'NS', host], { timeout: 5000 });
|
|
346
|
+
if (nsRun.ok && nsRun.stdout) {
|
|
347
|
+
const nsRecords = nsRun.stdout
|
|
348
|
+
.split('\n')
|
|
349
|
+
.map(line => line.trim().replace(/\.$/, ''))
|
|
350
|
+
.filter(line => line.length > 0)
|
|
351
|
+
.map(hostname => ({ hostname, ip: null }));
|
|
352
|
+
|
|
353
|
+
result.dnsRecords.NS = nsRecords;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
} catch (error) {
|
|
357
|
+
// Silently fail, return empty results
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FingerprintStage
|
|
3
|
+
*
|
|
4
|
+
* Technology fingerprinting:
|
|
5
|
+
* - whatweb (web technology identification)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class FingerprintStage {
|
|
9
|
+
constructor(plugin) {
|
|
10
|
+
this.plugin = plugin;
|
|
11
|
+
this.commandRunner = plugin.commandRunner;
|
|
12
|
+
this.config = plugin.config;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async execute(target, featureConfig = {}) {
|
|
16
|
+
const technologies = new Set();
|
|
17
|
+
const tools = {};
|
|
18
|
+
|
|
19
|
+
if (featureConfig.whatweb) {
|
|
20
|
+
const run = await this.commandRunner.run('whatweb', ['-q', this._buildUrl(target)], {
|
|
21
|
+
timeout: featureConfig.timeout ?? 20000,
|
|
22
|
+
maxBuffer: 2 * 1024 * 1024
|
|
23
|
+
});
|
|
24
|
+
if (run.ok) {
|
|
25
|
+
const parsed = run.stdout
|
|
26
|
+
.split(/[\r\n]+/)
|
|
27
|
+
.flatMap((line) => line.split(' '))
|
|
28
|
+
.map((token) => token.trim())
|
|
29
|
+
.filter((token) => token.includes('[') && token.includes(']'))
|
|
30
|
+
.map((token) => token.substring(0, token.indexOf('[')));
|
|
31
|
+
parsed.forEach((tech) => technologies.add(tech));
|
|
32
|
+
tools.whatweb = { status: 'ok', technologies: parsed.slice(0, 20) };
|
|
33
|
+
if (this.config.storage.persistRawOutput) {
|
|
34
|
+
tools.whatweb.raw = this._truncateOutput(run.stdout);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
tools.whatweb = {
|
|
38
|
+
status: run.error?.code === 'ENOENT' ? 'unavailable' : 'error',
|
|
39
|
+
message: run.error?.message || 'whatweb failed'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (technologies.size === 0 && Object.keys(tools).length === 0) {
|
|
45
|
+
return { status: 'skipped' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
_individual: tools,
|
|
50
|
+
_aggregated: {
|
|
51
|
+
status: technologies.size ? 'ok' : 'empty',
|
|
52
|
+
technologies: Array.from(technologies),
|
|
53
|
+
tools
|
|
54
|
+
},
|
|
55
|
+
status: technologies.size ? 'ok' : 'empty',
|
|
56
|
+
technologies: Array.from(technologies),
|
|
57
|
+
tools
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_buildUrl(target) {
|
|
62
|
+
const protocol = target.protocol || 'https';
|
|
63
|
+
const port = target.port && target.port !== (protocol === 'http' ? 80 : 443) ? `:${target.port}` : '';
|
|
64
|
+
return `${protocol}://${target.host}${port}${target.path || ''}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_truncateOutput(text, maxLength = 10000) {
|
|
68
|
+
if (!text || text.length <= maxLength) return text;
|
|
69
|
+
return text.substring(0, maxLength) + '\n... (truncated)';
|
|
70
|
+
}
|
|
71
|
+
}
|