kramscan 0.1.0 → 0.2.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/README.md +392 -87
- package/dist/agent/confirmation.d.ts +38 -0
- package/dist/agent/confirmation.js +210 -0
- package/dist/agent/context.d.ts +81 -0
- package/dist/agent/context.js +227 -0
- package/dist/agent/index.d.ts +10 -0
- package/dist/agent/index.js +32 -0
- package/dist/agent/orchestrator.d.ts +63 -0
- package/dist/agent/orchestrator.js +370 -0
- package/dist/agent/prompts/system.d.ts +6 -0
- package/dist/agent/prompts/system.js +116 -0
- package/dist/agent/skill-registry.d.ts +78 -0
- package/dist/agent/skill-registry.js +202 -0
- package/dist/agent/skills/analyze-findings.d.ts +22 -0
- package/dist/agent/skills/analyze-findings.js +191 -0
- package/dist/agent/skills/generate-report.d.ts +26 -0
- package/dist/agent/skills/generate-report.js +436 -0
- package/dist/agent/skills/health-check.d.ts +28 -0
- package/dist/agent/skills/health-check.js +344 -0
- package/dist/agent/skills/index.d.ts +9 -0
- package/dist/agent/skills/index.js +17 -0
- package/dist/agent/skills/verify-finding.d.ts +17 -0
- package/dist/agent/skills/verify-finding.js +91 -0
- package/dist/agent/skills/web-scan.d.ts +22 -0
- package/dist/agent/skills/web-scan.js +203 -0
- package/dist/agent/types.d.ts +141 -0
- package/dist/agent/types.js +16 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +176 -139
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.js +250 -0
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +104 -55
- package/dist/commands/config.js +63 -37
- package/dist/commands/doctor.js +22 -17
- package/dist/commands/onboard.js +190 -125
- package/dist/commands/report.js +69 -77
- package/dist/commands/scan.js +261 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +51 -0
- package/dist/core/ai-client.d.ts +7 -2
- package/dist/core/ai-client.js +231 -20
- package/dist/core/ai-payloads.d.ts +17 -0
- package/dist/core/ai-payloads.js +54 -0
- package/dist/core/config-schema.d.ts +197 -0
- package/dist/core/config-schema.js +68 -0
- package/dist/core/config-schema.test.d.ts +1 -0
- package/dist/core/config-schema.test.js +151 -0
- package/dist/core/config.d.ts +17 -36
- package/dist/core/config.js +261 -20
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +19 -0
- package/dist/core/scan-index.js +52 -0
- package/dist/core/scan-storage.d.ts +11 -0
- package/dist/core/scan-storage.js +69 -0
- package/dist/core/scanner.d.ts +101 -4
- package/dist/core/scanner.js +432 -63
- package/dist/core/vulnerability-detector.d.ts +18 -2
- package/dist/core/vulnerability-detector.js +349 -38
- package/dist/core/vulnerability-detector.test.d.ts +1 -0
- package/dist/core/vulnerability-detector.test.js +210 -0
- package/dist/index.js +3 -0
- package/dist/plugins/PluginManager.d.ts +27 -0
- package/dist/plugins/PluginManager.js +166 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.js +19 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SQLInjectionPlugin.js +109 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.d.ts +11 -0
- package/dist/plugins/vulnerabilities/SecurityHeadersPlugin.js +63 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.d.ts +9 -0
- package/dist/plugins/vulnerabilities/SensitiveDataPlugin.js +32 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/XSSPlugin.js +81 -0
- package/dist/reports/PdfGenerator.d.ts +36 -0
- package/dist/reports/PdfGenerator.js +379 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +55 -0
- package/dist/utils/theme.js +195 -0
- package/package.json +27 -6
- package/dist/core/executor.d.ts +0 -2
- package/dist/core/executor.js +0 -74
- package/dist/core/logger.d.ts +0 -12
- package/dist/core/logger.js +0 -51
- package/dist/core/registry.d.ts +0 -3
- package/dist/core/registry.js +0 -35
- package/dist/core/storage.d.ts +0 -4
- package/dist/core/storage.js +0 -39
- package/dist/core/types.d.ts +0 -24
- package/dist/core/types.js +0 -2
- package/dist/skills/base.d.ts +0 -8
- package/dist/skills/base.js +0 -6
- package/dist/skills/builtin.d.ts +0 -4
- package/dist/skills/builtin.js +0 -71
- package/dist/skills/loader.d.ts +0 -2
- package/dist/skills/loader.js +0 -27
- package/dist/skills/types.d.ts +0 -46
- package/dist/skills/types.js +0 -2
|
@@ -3,23 +3,52 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.VulnerabilityDetector = void 0;
|
|
4
4
|
class VulnerabilityDetector {
|
|
5
5
|
vulnerabilities = [];
|
|
6
|
-
|
|
6
|
+
reportedHeaders = new Set();
|
|
7
|
+
reportedPaths = new Set();
|
|
8
|
+
onVulnerabilityFound;
|
|
9
|
+
setOnVulnerabilityFound(callback) {
|
|
10
|
+
this.onVulnerabilityFound = callback;
|
|
11
|
+
}
|
|
12
|
+
addVulnerability(vuln) {
|
|
13
|
+
this.vulnerabilities.push(vuln);
|
|
14
|
+
if (this.onVulnerabilityFound) {
|
|
15
|
+
this.onVulnerabilityFound(vuln);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
7
18
|
detectXSS(url, param, payload, response) {
|
|
8
|
-
// Check if payload is reflected in response
|
|
9
19
|
if (response.includes(payload)) {
|
|
10
|
-
this.vulnerabilities.
|
|
20
|
+
const existing = this.vulnerabilities.find(v => v.type === "xss" && v.url === url && v.evidence?.includes(param));
|
|
21
|
+
if (existing)
|
|
22
|
+
return;
|
|
23
|
+
this.addVulnerability({
|
|
11
24
|
type: "xss",
|
|
12
25
|
severity: "high",
|
|
13
26
|
title: "Reflected Cross-Site Scripting (XSS)",
|
|
14
|
-
description: `The parameter '${param}'
|
|
27
|
+
description: `The parameter '${param}' reflects user input without proper encoding, allowing script injection.`,
|
|
15
28
|
url,
|
|
16
29
|
evidence: `Payload: ${payload}`,
|
|
17
|
-
remediation: "Implement
|
|
30
|
+
remediation: "Implement input validation, output encoding, and Content Security Policy (CSP) headers.",
|
|
31
|
+
cwe: "CWE-79",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
detectStoredXSS(url, payload, response) {
|
|
36
|
+
if (response.includes(payload)) {
|
|
37
|
+
const existing = this.vulnerabilities.find(v => v.type === "xss" && v.url === url && v.title.includes("Stored"));
|
|
38
|
+
if (existing)
|
|
39
|
+
return;
|
|
40
|
+
this.addVulnerability({
|
|
41
|
+
type: "xss",
|
|
42
|
+
severity: "critical",
|
|
43
|
+
title: "Stored Cross-Site Scripting (XSS)",
|
|
44
|
+
description: `Malicious script is persisted on the server and executed when other users view the affected content.`,
|
|
45
|
+
url,
|
|
46
|
+
evidence: `Stored payload reflected in response`,
|
|
47
|
+
remediation: "Implement input validation, output encoding, and CSP. Sanitize HTML content.",
|
|
18
48
|
cwe: "CWE-79",
|
|
19
49
|
});
|
|
20
50
|
}
|
|
21
51
|
}
|
|
22
|
-
// SQL Injection Detection
|
|
23
52
|
detectSQLi(url, param, errorResponse) {
|
|
24
53
|
const sqlErrors = [
|
|
25
54
|
"SQL syntax",
|
|
@@ -30,97 +59,377 @@ class VulnerabilityDetector {
|
|
|
30
59
|
"ODBC",
|
|
31
60
|
"JET Database",
|
|
32
61
|
"Microsoft Access Driver",
|
|
62
|
+
"unterminated",
|
|
63
|
+
"mysql_num_rows",
|
|
64
|
+
"mysql_query",
|
|
65
|
+
"Microsoft SQL Native Client error",
|
|
66
|
+
"SQLServer JDBC Driver",
|
|
67
|
+
"ORA-00933",
|
|
68
|
+
"PG::SyntaxError",
|
|
69
|
+
"Warning: pg_",
|
|
70
|
+
"Syntax error",
|
|
33
71
|
];
|
|
72
|
+
// Check if vulnerability already exists to avoid duplicates
|
|
73
|
+
const existing = this.vulnerabilities.find(v => v.type === "sqli" && v.url === url);
|
|
74
|
+
if (existing)
|
|
75
|
+
return;
|
|
34
76
|
for (const error of sqlErrors) {
|
|
35
77
|
if (errorResponse.includes(error)) {
|
|
36
|
-
this.
|
|
78
|
+
this.addVulnerability({
|
|
37
79
|
type: "sqli",
|
|
38
80
|
severity: "critical",
|
|
39
81
|
title: "SQL Injection",
|
|
40
|
-
description: `The parameter '${param}'
|
|
82
|
+
description: `The parameter '${param}' is vulnerable to SQL injection. Database error messages were detected.`,
|
|
41
83
|
url,
|
|
42
|
-
evidence: `Error
|
|
43
|
-
remediation: "Use parameterized queries
|
|
84
|
+
evidence: `Error: ${error}`,
|
|
85
|
+
remediation: "Use parameterized queries (prepared statements). Never concatenate user input into SQL.",
|
|
44
86
|
cwe: "CWE-89",
|
|
45
87
|
});
|
|
46
88
|
break;
|
|
47
89
|
}
|
|
48
90
|
}
|
|
49
91
|
}
|
|
50
|
-
|
|
92
|
+
detectBlindSQLi(url, param, originalTime, testTime) {
|
|
93
|
+
if (testTime > originalTime + 3000) {
|
|
94
|
+
const existing = this.vulnerabilities.find(v => v.type === "sqli" && v.url === url && v.title.includes("Blind"));
|
|
95
|
+
if (existing)
|
|
96
|
+
return;
|
|
97
|
+
this.addVulnerability({
|
|
98
|
+
type: "sqli",
|
|
99
|
+
severity: "high",
|
|
100
|
+
title: "Blind SQL Injection",
|
|
101
|
+
description: `Time-based blind SQL injection detected on parameter '${param}'. Response time indicates successful payload execution.`,
|
|
102
|
+
url,
|
|
103
|
+
evidence: `Response time increased by ${testTime - originalTime}ms with sleep payload`,
|
|
104
|
+
remediation: "Use parameterized queries. Implement input validation and proper error handling.",
|
|
105
|
+
cwe: "CWE-89",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
51
109
|
detectCSRF(url, formHtml) {
|
|
52
110
|
const hasCSRFToken = formHtml.includes('name="csrf') ||
|
|
53
111
|
formHtml.includes('name="_token') ||
|
|
54
|
-
formHtml.includes('name="authenticity_token')
|
|
112
|
+
formHtml.includes('name="authenticity_token') ||
|
|
113
|
+
formHtml.includes('name="_csrf');
|
|
55
114
|
if (!hasCSRFToken) {
|
|
56
|
-
this.vulnerabilities.
|
|
115
|
+
const existing = this.vulnerabilities.find(v => v.type === "csrf" && v.url === url);
|
|
116
|
+
if (existing)
|
|
117
|
+
return;
|
|
118
|
+
this.addVulnerability({
|
|
57
119
|
type: "csrf",
|
|
58
120
|
severity: "medium",
|
|
59
121
|
title: "Missing CSRF Protection",
|
|
60
|
-
description: "
|
|
122
|
+
description: "Form lacks CSRF tokens. Attackers can forge requests to perform unauthorized actions.",
|
|
61
123
|
url,
|
|
62
|
-
remediation: "Implement CSRF tokens
|
|
124
|
+
remediation: "Implement anti-CSRF tokens. Use SameSite cookies.",
|
|
63
125
|
cwe: "CWE-352",
|
|
64
126
|
});
|
|
65
127
|
}
|
|
66
128
|
}
|
|
67
|
-
// Security Headers Analysis
|
|
68
129
|
analyzeSecurityHeaders(url, headers) {
|
|
130
|
+
const host = new URL(url).host;
|
|
131
|
+
if (this.reportedHeaders.has(host)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
69
134
|
const requiredHeaders = {
|
|
70
135
|
"content-security-policy": {
|
|
71
136
|
title: "Missing Content-Security-Policy",
|
|
72
|
-
severity: "
|
|
73
|
-
remediation: "Implement a strict
|
|
137
|
+
severity: "low",
|
|
138
|
+
remediation: "Implement a strict CSP to prevent XSS and data injection attacks.",
|
|
74
139
|
},
|
|
75
140
|
"x-frame-options": {
|
|
76
141
|
title: "Missing X-Frame-Options",
|
|
77
|
-
severity: "
|
|
78
|
-
remediation: "Set X-Frame-Options to DENY or SAMEORIGIN to prevent clickjacking
|
|
142
|
+
severity: "low",
|
|
143
|
+
remediation: "Set X-Frame-Options to DENY or SAMEORIGIN to prevent clickjacking.",
|
|
79
144
|
},
|
|
80
145
|
"strict-transport-security": {
|
|
81
|
-
title: "Missing Strict-Transport-Security",
|
|
82
|
-
severity: "
|
|
83
|
-
remediation: "Enable HSTS
|
|
146
|
+
title: "Missing Strict-Transport-Security (HSTS)",
|
|
147
|
+
severity: "low",
|
|
148
|
+
remediation: "Enable HSTS with max-age of at least 31536000 seconds.",
|
|
84
149
|
},
|
|
85
150
|
"x-content-type-options": {
|
|
86
151
|
title: "Missing X-Content-Type-Options",
|
|
87
|
-
severity: "
|
|
88
|
-
remediation: "Set X-Content-Type-Options to 'nosniff'
|
|
152
|
+
severity: "info",
|
|
153
|
+
remediation: "Set X-Content-Type-Options to 'nosniff'.",
|
|
154
|
+
},
|
|
155
|
+
"referrer-policy": {
|
|
156
|
+
title: "Missing Referrer-Policy",
|
|
157
|
+
severity: "info",
|
|
158
|
+
remediation: "Set Referrer-Policy to 'strict-origin-when-cross-origin'.",
|
|
159
|
+
},
|
|
160
|
+
"permissions-policy": {
|
|
161
|
+
title: "Missing Permissions-Policy",
|
|
162
|
+
severity: "info",
|
|
163
|
+
remediation: "Restrict browser features using Permissions-Policy header.",
|
|
89
164
|
},
|
|
90
165
|
};
|
|
166
|
+
let missingCount = 0;
|
|
91
167
|
for (const [header, config] of Object.entries(requiredHeaders)) {
|
|
92
168
|
if (!headers[header.toLowerCase()]) {
|
|
93
|
-
|
|
169
|
+
missingCount++;
|
|
170
|
+
this.addVulnerability({
|
|
94
171
|
type: "header",
|
|
95
172
|
severity: config.severity,
|
|
96
173
|
title: config.title,
|
|
97
|
-
description: `The ${header} security header is not set.`,
|
|
174
|
+
description: `The ${header} security header is not set on ${host}.`,
|
|
98
175
|
url,
|
|
99
176
|
remediation: config.remediation,
|
|
100
177
|
});
|
|
101
178
|
}
|
|
102
179
|
}
|
|
180
|
+
if (missingCount > 0) {
|
|
181
|
+
this.reportedHeaders.add(host);
|
|
182
|
+
}
|
|
103
183
|
}
|
|
104
|
-
// Sensitive Data Detection
|
|
105
184
|
detectSensitiveData(url, response) {
|
|
106
185
|
const patterns = [
|
|
107
|
-
{ regex: /
|
|
108
|
-
{ regex: /
|
|
109
|
-
{ regex: /
|
|
110
|
-
{ regex: /
|
|
111
|
-
{ regex: /
|
|
186
|
+
{ regex: /sk-[a-zA-Z0-9]{48}/g, name: "OpenAI API Key", severity: "critical" },
|
|
187
|
+
{ regex: /ghp_[a-zA-Z0-9]{36}/g, name: "GitHub Token", severity: "critical" },
|
|
188
|
+
{ regex: /AKIA[0-9A-Z]{16}/g, name: "AWS Access Key", severity: "critical" },
|
|
189
|
+
{ regex: /xox[baprs]-[0-9a-zA-Z]{10,}/g, name: "Slack Token", severity: "high" },
|
|
190
|
+
{ regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*/g, name: "JWT Token", severity: "high" },
|
|
191
|
+
{ regex: /AIza[0-9A-Za-z_-]{35}/g, name: "Google API Key", severity: "high" },
|
|
192
|
+
{ regex: /password["\s:=]+[^\s"]{6,}/gi, name: "Hardcoded Password", severity: "high" },
|
|
193
|
+
{ regex: /api[_-]?key["\s:=]+[\w-]{20,}/gi, name: "Generic API Key", severity: "medium" },
|
|
194
|
+
{ regex: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g, name: "Private Key", severity: "critical" },
|
|
195
|
+
{ regex: /database[_-]?url["\s:=]+.*(?:mysql|postgres|mongodb):\/\//gi, name: "Database Connection String", severity: "high" },
|
|
112
196
|
];
|
|
113
197
|
for (const pattern of patterns) {
|
|
114
198
|
const matches = response.match(pattern.regex);
|
|
115
199
|
if (matches && matches.length > 0) {
|
|
116
|
-
this.vulnerabilities.
|
|
200
|
+
const existing = this.vulnerabilities.find(v => v.type === "sensitive_data" && v.url === url && v.title.includes(pattern.name));
|
|
201
|
+
if (existing)
|
|
202
|
+
continue;
|
|
203
|
+
this.addVulnerability({
|
|
117
204
|
type: "sensitive_data",
|
|
118
|
-
severity:
|
|
205
|
+
severity: pattern.severity,
|
|
119
206
|
title: `Exposed ${pattern.name}`,
|
|
120
|
-
description: `
|
|
207
|
+
description: `Sensitive ${pattern.name} found in response. This could lead to account compromise or data breach.`,
|
|
208
|
+
url,
|
|
209
|
+
evidence: `Found: ${matches[0].substring(0, 30)}...`,
|
|
210
|
+
remediation: "Remove sensitive data from client-side code. Use environment variables and secure secret management.",
|
|
211
|
+
cwe: "CWE-200",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
detectIDOR(url, paramName, paramValue, testResponse, originalResponse) {
|
|
217
|
+
if (testResponse.length < originalResponse.length * 0.5 ||
|
|
218
|
+
testResponse.length > originalResponse.length * 1.5 ||
|
|
219
|
+
testResponse !== originalResponse) {
|
|
220
|
+
const existing = this.vulnerabilities.find(v => v.type === "idor" && v.url === url && v.evidence?.includes(paramName));
|
|
221
|
+
if (existing)
|
|
222
|
+
return;
|
|
223
|
+
this.addVulnerability({
|
|
224
|
+
type: "idor",
|
|
225
|
+
severity: "high",
|
|
226
|
+
title: "Insecure Direct Object Reference (IDOR)",
|
|
227
|
+
description: `Changing the parameter '${paramName}' from '${paramValue}' reveals different content without authorization. This could allow unauthorized access to other users' data.`,
|
|
228
|
+
url,
|
|
229
|
+
evidence: `Parameter: ${paramName}, Original: ${paramValue}, Response changed significantly`,
|
|
230
|
+
remediation: "Implement proper authorization checks. Validate user permissions before returning data.",
|
|
231
|
+
cwe: "CWE-639",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
detectLFI(url, param, payload, response) {
|
|
236
|
+
const lfiIndicators = [
|
|
237
|
+
"root:x:0:0:",
|
|
238
|
+
"/bin/bash",
|
|
239
|
+
"/bin/sh",
|
|
240
|
+
"[boot loader]",
|
|
241
|
+
"Windows",
|
|
242
|
+
"C:\\Windows",
|
|
243
|
+
"Volume Serial Number",
|
|
244
|
+
"<!DOCTYPE html>",
|
|
245
|
+
"<html",
|
|
246
|
+
];
|
|
247
|
+
for (const indicator of lfiIndicators) {
|
|
248
|
+
if (response.includes(indicator)) {
|
|
249
|
+
const existing = this.vulnerabilities.find(v => v.type === "lfi" && v.url === url);
|
|
250
|
+
if (existing)
|
|
251
|
+
return;
|
|
252
|
+
this.addVulnerability({
|
|
253
|
+
type: "lfi",
|
|
254
|
+
severity: "critical",
|
|
255
|
+
title: "Local File Inclusion (LFI)",
|
|
256
|
+
description: `The parameter '${param}' allows reading arbitrary files from the server.`,
|
|
257
|
+
url,
|
|
258
|
+
evidence: `Payload: ${payload}, Response contains system file content`,
|
|
259
|
+
remediation: "Validate and sanitize file paths. Use whitelisting. Never allow direct user input in file paths.",
|
|
260
|
+
cwe: "CWE-22",
|
|
261
|
+
});
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
detectPathTraversal(url, param, payload, response) {
|
|
267
|
+
const traversalIndicators = [
|
|
268
|
+
"root:x:0:0:",
|
|
269
|
+
"[boot loader]",
|
|
270
|
+
"<!DOCTYPE html>",
|
|
271
|
+
"<html",
|
|
272
|
+
"<!DOCTYPE",
|
|
273
|
+
"<?xml",
|
|
274
|
+
];
|
|
275
|
+
const isLikelyTraversalPayload = payload.includes("../") || payload.includes("..\\");
|
|
276
|
+
if (isLikelyTraversalPayload) {
|
|
277
|
+
for (const indicator of traversalIndicators) {
|
|
278
|
+
if (response.includes(indicator) && !response.includes("<script>")) {
|
|
279
|
+
const pathKey = `${url}:${param}`;
|
|
280
|
+
if (this.reportedPaths.has(pathKey))
|
|
281
|
+
return;
|
|
282
|
+
this.reportedPaths.add(pathKey);
|
|
283
|
+
this.addVulnerability({
|
|
284
|
+
type: "lfi",
|
|
285
|
+
severity: "high",
|
|
286
|
+
title: "Path Traversal",
|
|
287
|
+
description: `The parameter '${param}' allows directory traversal to access files outside the web root.`,
|
|
288
|
+
url,
|
|
289
|
+
evidence: `Payload: ${payload}`,
|
|
290
|
+
remediation: "Validate file paths. Use basename(). Implement whitelist approach.",
|
|
291
|
+
cwe: "CWE-22",
|
|
292
|
+
});
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
detectCMDI(url, param, payload, response) {
|
|
299
|
+
const cmdiIndicators = [
|
|
300
|
+
"uid=",
|
|
301
|
+
"gid=",
|
|
302
|
+
"root",
|
|
303
|
+
"/usr/bin/",
|
|
304
|
+
"Microsoft Windows",
|
|
305
|
+
"Volume Serial Number",
|
|
306
|
+
"C:\\",
|
|
307
|
+
"DIR",
|
|
308
|
+
"Volume in drive",
|
|
309
|
+
"The system cannot find",
|
|
310
|
+
"command not found",
|
|
311
|
+
"Syntax error",
|
|
312
|
+
];
|
|
313
|
+
for (const indicator of cmdiIndicators) {
|
|
314
|
+
if (response.toLowerCase().includes(indicator.toLowerCase())) {
|
|
315
|
+
const existing = this.vulnerabilities.find(v => v.type === "cmdi" && v.url === url);
|
|
316
|
+
if (existing)
|
|
317
|
+
return;
|
|
318
|
+
this.addVulnerability({
|
|
319
|
+
type: "cmdi",
|
|
320
|
+
severity: "critical",
|
|
321
|
+
title: "OS Command Injection",
|
|
322
|
+
description: `The parameter '${param}' executes system commands on the server.`,
|
|
323
|
+
url,
|
|
324
|
+
evidence: `Payload: ${payload}, Response contains system output`,
|
|
325
|
+
remediation: "Avoid system calls. Use built-in APIs. If needed, use strict allowlists for input.",
|
|
326
|
+
cwe: "CWE-78",
|
|
327
|
+
});
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
detectSSRF(url, param, payload, response) {
|
|
333
|
+
const ssrfIndicators = [
|
|
334
|
+
"169.254.169.254",
|
|
335
|
+
"metadata.google.internal",
|
|
336
|
+
"instance-data",
|
|
337
|
+
"internal ip",
|
|
338
|
+
"localhost",
|
|
339
|
+
"127.0.0.1",
|
|
340
|
+
"aws credentials",
|
|
341
|
+
"security credentials",
|
|
342
|
+
"iam",
|
|
343
|
+
];
|
|
344
|
+
for (const indicator of ssrfIndicators) {
|
|
345
|
+
if (response.toLowerCase().includes(indicator.toLowerCase())) {
|
|
346
|
+
const existing = this.vulnerabilities.find(v => v.type === "ssrf" && v.url === url);
|
|
347
|
+
if (existing)
|
|
348
|
+
return;
|
|
349
|
+
this.addVulnerability({
|
|
350
|
+
type: "ssrf",
|
|
351
|
+
severity: "high",
|
|
352
|
+
title: "Server-Side Request Forgery (SSRF)",
|
|
353
|
+
description: `The parameter '${param}' allows fetching arbitrary URLs, potentially accessing internal services.`,
|
|
354
|
+
url,
|
|
355
|
+
evidence: `Payload: ${payload}, Response contains internal service data`,
|
|
356
|
+
remediation: "Validate URLs against allowlists. Disable unused URL schemas. Restrict redirects.",
|
|
357
|
+
cwe: "CWE-918",
|
|
358
|
+
});
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
detectOpenRedirect(url, param, payload, finalUrl) {
|
|
364
|
+
try {
|
|
365
|
+
const payloadUrl = new URL(payload);
|
|
366
|
+
const finalParsed = new URL(finalUrl);
|
|
367
|
+
if (payloadUrl.host &&
|
|
368
|
+
payloadUrl.host !== finalParsed.host &&
|
|
369
|
+
!payload.startsWith("/") &&
|
|
370
|
+
!payload.startsWith(".")) {
|
|
371
|
+
const existing = this.vulnerabilities.find(v => v.type === "redirect" && v.url === url);
|
|
372
|
+
if (existing)
|
|
373
|
+
return;
|
|
374
|
+
this.addVulnerability({
|
|
375
|
+
type: "redirect",
|
|
376
|
+
severity: "medium",
|
|
377
|
+
title: "Open Redirect",
|
|
378
|
+
description: `The parameter '${param}' redirects users to an external site.`,
|
|
379
|
+
url,
|
|
380
|
+
evidence: `Payload: ${payload}, Redirects to: ${finalUrl}`,
|
|
381
|
+
remediation: "Validate redirect URLs. Use allowlists for permitted destinations.",
|
|
382
|
+
cwe: "CWE-601",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
if (payload.startsWith("http://") || payload.startsWith("https://")) {
|
|
388
|
+
const existing = this.vulnerabilities.find(v => v.type === "redirect" && v.url === url);
|
|
389
|
+
if (existing)
|
|
390
|
+
return;
|
|
391
|
+
this.addVulnerability({
|
|
392
|
+
type: "redirect",
|
|
393
|
+
severity: "medium",
|
|
394
|
+
title: "Open Redirect",
|
|
395
|
+
description: `The parameter '${param}' allows redirects to arbitrary external URLs.`,
|
|
396
|
+
url,
|
|
397
|
+
evidence: `Payload: ${payload}`,
|
|
398
|
+
remediation: "Validate redirect URLs against allowlists.",
|
|
399
|
+
cwe: "CWE-601",
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
detectInfoDisclosure(url, response) {
|
|
405
|
+
const infoPatterns = [
|
|
406
|
+
{ regex: /(?:stack trace|StackTrace|exception|Error) in \//gi, name: "Stack Trace", severity: "low" },
|
|
407
|
+
{ regex: /<\?php\s+\w+\(/gi, name: "PHP Code Exposure", severity: "medium" },
|
|
408
|
+
{ regex: /\.git\/[^\s<>]+/gi, name: "Git Directory Exposed", severity: "medium" },
|
|
409
|
+
{ regex: /\.env[^\s<>]*/gi, name: "Environment File Exposed", severity: "high" },
|
|
410
|
+
{ regex: /\.gitignore[^\s<>]*/gi, name: "Gitignore Exposed", severity: "low" },
|
|
411
|
+
{ regex: /\.DS_Store[^\s<>]*/gi, name: "DS_Store Exposed", severity: "low" },
|
|
412
|
+
{ regex: /wp-config\.php/gi, name: "WordPress Config Exposed", severity: "high" },
|
|
413
|
+
{ regex: /phpinfo\(\)/gi, name: "PHPInfo Exposure", severity: "medium" },
|
|
414
|
+
{ regex: /debug[\s:=]*(true|1|on|yes)/gi, name: "Debug Mode Enabled", severity: "medium" },
|
|
415
|
+
{ regex: /"version":\s*"[\d.]+"/gi, name: "Version Information", severity: "info" },
|
|
416
|
+
{ regex: /Server:\s*\w+/gi, name: "Server Header", severity: "info" },
|
|
417
|
+
{ regex: /X-Powered-By:[^\r\n]+/gi, name: "X-Powered-By Header", severity: "info" },
|
|
418
|
+
];
|
|
419
|
+
for (const pattern of infoPatterns) {
|
|
420
|
+
const matches = response.match(pattern.regex);
|
|
421
|
+
if (matches && matches.length > 0) {
|
|
422
|
+
const existing = this.vulnerabilities.find(v => v.type === "info" && v.url === url && v.title === pattern.name);
|
|
423
|
+
if (existing)
|
|
424
|
+
continue;
|
|
425
|
+
this.addVulnerability({
|
|
426
|
+
type: "info",
|
|
427
|
+
severity: pattern.severity,
|
|
428
|
+
title: pattern.name,
|
|
429
|
+
description: `Sensitive information exposed: ${pattern.name}. This could aid attackers in further exploitation.`,
|
|
121
430
|
url,
|
|
122
|
-
evidence: `
|
|
123
|
-
remediation: "
|
|
431
|
+
evidence: `Found: ${matches[0].substring(0, 50)}`,
|
|
432
|
+
remediation: "Disable debug mode. Remove exposed files. Configure server to hide sensitive headers.",
|
|
124
433
|
cwe: "CWE-200",
|
|
125
434
|
});
|
|
126
435
|
}
|
|
@@ -145,6 +454,8 @@ class VulnerabilityDetector {
|
|
|
145
454
|
}
|
|
146
455
|
clear() {
|
|
147
456
|
this.vulnerabilities = [];
|
|
457
|
+
this.reportedHeaders.clear();
|
|
458
|
+
this.reportedPaths.clear();
|
|
148
459
|
}
|
|
149
460
|
}
|
|
150
461
|
exports.VulnerabilityDetector = VulnerabilityDetector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|