kramscan 0.1.1 → 0.3.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 +1 -1
- package/README.md +419 -236
- package/dist/agent/confirmation.d.ts +5 -1
- package/dist/agent/confirmation.js +29 -9
- package/dist/agent/context.js +2 -3
- package/dist/agent/orchestrator.d.ts +2 -0
- package/dist/agent/orchestrator.js +50 -8
- package/dist/agent/prompts/system.d.ts +1 -1
- package/dist/agent/prompts/system.js +5 -7
- package/dist/agent/skills/health-check.js +22 -2
- package/dist/agent/skills/index.d.ts +1 -0
- package/dist/agent/skills/index.js +3 -1
- 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.js +46 -0
- package/dist/cli.js +156 -149
- package/dist/commands/agent.js +38 -38
- package/dist/commands/ai.d.ts +2 -0
- package/dist/commands/ai.js +112 -0
- package/dist/commands/analyze.js +103 -54
- package/dist/commands/config.js +55 -29
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +236 -0
- package/dist/commands/doctor.js +20 -15
- package/dist/commands/gate.d.ts +2 -0
- package/dist/commands/gate.js +109 -0
- package/dist/commands/onboard.js +188 -141
- package/dist/commands/report.js +68 -76
- package/dist/commands/scan.js +262 -81
- package/dist/commands/scans.d.ts +2 -0
- package/dist/commands/scans.js +55 -0
- package/dist/core/ai-client.d.ts +6 -1
- package/dist/core/ai-client.js +80 -12
- 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 +8 -31
- package/dist/core/config.js +71 -14
- package/dist/core/diff-engine.d.ts +12 -0
- package/dist/core/diff-engine.js +47 -0
- package/dist/core/errors.d.ts +71 -0
- package/dist/core/errors.js +162 -0
- package/dist/core/scan-index.d.ts +20 -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 +95 -13
- package/dist/core/scanner.js +342 -248
- package/dist/core/server-probe.d.ts +20 -0
- package/dist/core/server-probe.js +109 -0
- package/dist/core/vulnerability-detector.d.ts +9 -0
- package/dist/core/vulnerability-detector.js +46 -15
- 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 +12 -0
- package/dist/plugins/index.js +29 -0
- package/dist/plugins/types.d.ts +55 -0
- package/dist/plugins/types.js +25 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CORSAnalyzerPlugin.js +67 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.d.ts +8 -0
- package/dist/plugins/vulnerabilities/CSRFPlugin.js +34 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/CookieSecurityPlugin.js +91 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.d.ts +15 -0
- package/dist/plugins/vulnerabilities/DebugEndpointPlugin.js +222 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.d.ts +13 -0
- package/dist/plugins/vulnerabilities/DirectoryTraversalPlugin.js +110 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.d.ts +10 -0
- package/dist/plugins/vulnerabilities/OpenRedirectPlugin.js +69 -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 +404 -0
- package/dist/utils/logger.d.ts +33 -1
- package/dist/utils/logger.js +127 -8
- package/dist/utils/theme.d.ts +56 -0
- package/dist/utils/theme.js +201 -0
- package/package.json +6 -3
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DebugEndpointPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class DebugEndpointPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "Debug Endpoint Detector";
|
|
7
|
+
type = "info";
|
|
8
|
+
description = "Probes for common debug, admin, and development endpoints left exposed";
|
|
9
|
+
reportedPaths = new Set();
|
|
10
|
+
/**
|
|
11
|
+
* Common debug/dev endpoints that developers forget to disable before deployment.
|
|
12
|
+
* Each entry has a path, a human-readable name, and expected severity.
|
|
13
|
+
*/
|
|
14
|
+
debugEndpoints = [
|
|
15
|
+
{
|
|
16
|
+
path: "/debug",
|
|
17
|
+
name: "Debug Panel",
|
|
18
|
+
severity: "high",
|
|
19
|
+
indicators: ["debug", "stack", "trace", "error"],
|
|
20
|
+
remediation: "Remove or protect the /debug endpoint behind authentication.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: "/__debug__",
|
|
24
|
+
name: "Django Debug Panel",
|
|
25
|
+
severity: "high",
|
|
26
|
+
indicators: ["django", "debug", "toolbar"],
|
|
27
|
+
remediation: "Set DEBUG=False in Django settings for production.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
path: "/phpinfo.php",
|
|
31
|
+
name: "PHP Info Page",
|
|
32
|
+
severity: "high",
|
|
33
|
+
indicators: ["phpinfo", "PHP Version", "Configuration"],
|
|
34
|
+
remediation: "Remove phpinfo.php from production servers.",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: "/server-status",
|
|
38
|
+
name: "Apache Server Status",
|
|
39
|
+
severity: "medium",
|
|
40
|
+
indicators: ["Apache", "Server Version", "Current Time"],
|
|
41
|
+
remediation: "Restrict /server-status to internal IPs only.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: "/server-info",
|
|
45
|
+
name: "Apache Server Info",
|
|
46
|
+
severity: "medium",
|
|
47
|
+
indicators: ["Apache", "Server Information"],
|
|
48
|
+
remediation: "Disable mod_info or restrict access to internal IPs.",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
path: "/graphql",
|
|
52
|
+
name: "GraphQL Endpoint (Introspection)",
|
|
53
|
+
severity: "medium",
|
|
54
|
+
indicators: ["graphql", "__schema", "query"],
|
|
55
|
+
remediation: "Disable GraphQL introspection in production.",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: "/graphiql",
|
|
59
|
+
name: "GraphiQL IDE",
|
|
60
|
+
severity: "high",
|
|
61
|
+
indicators: ["graphiql", "GraphiQL"],
|
|
62
|
+
remediation: "Remove GraphiQL from production deployments.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
path: "/swagger",
|
|
66
|
+
name: "Swagger UI",
|
|
67
|
+
severity: "medium",
|
|
68
|
+
indicators: ["swagger", "api-docs", "openapi"],
|
|
69
|
+
remediation: "Protect Swagger UI behind authentication or remove from production.",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
path: "/swagger-ui.html",
|
|
73
|
+
name: "Swagger UI HTML",
|
|
74
|
+
severity: "medium",
|
|
75
|
+
indicators: ["swagger", "api-docs"],
|
|
76
|
+
remediation: "Protect Swagger UI behind authentication or remove from production.",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
path: "/api-docs",
|
|
80
|
+
name: "API Documentation",
|
|
81
|
+
severity: "low",
|
|
82
|
+
indicators: ["api", "docs", "openapi", "swagger"],
|
|
83
|
+
remediation: "Restrict API docs to authenticated users in production.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
path: "/actuator",
|
|
87
|
+
name: "Spring Boot Actuator",
|
|
88
|
+
severity: "high",
|
|
89
|
+
indicators: ["actuator", "beans", "health", "info"],
|
|
90
|
+
remediation: "Secure Spring Boot Actuator endpoints with authentication.",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
path: "/actuator/env",
|
|
94
|
+
name: "Spring Boot Environment",
|
|
95
|
+
severity: "critical",
|
|
96
|
+
indicators: ["property", "source", "value"],
|
|
97
|
+
remediation: "Never expose /actuator/env publicly. It reveals environment variables and secrets.",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
path: "/actuator/heapdump",
|
|
101
|
+
name: "Spring Boot Heap Dump",
|
|
102
|
+
severity: "critical",
|
|
103
|
+
indicators: [],
|
|
104
|
+
remediation: "Disable heap dump endpoint. It can expose sensitive memory contents.",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: "/elmah.axd",
|
|
108
|
+
name: "ELMAH Error Log (.NET)",
|
|
109
|
+
severity: "high",
|
|
110
|
+
indicators: ["elmah", "error", "exception"],
|
|
111
|
+
remediation: "Restrict ELMAH access to authenticated administrators.",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
path: "/trace.axd",
|
|
115
|
+
name: ".NET Trace Page",
|
|
116
|
+
severity: "high",
|
|
117
|
+
indicators: ["trace", "request", "response"],
|
|
118
|
+
remediation: "Disable tracing in production web.config.",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
path: "/.env",
|
|
122
|
+
name: "Environment File",
|
|
123
|
+
severity: "critical",
|
|
124
|
+
indicators: ["=", "KEY", "SECRET", "PASSWORD", "DATABASE"],
|
|
125
|
+
remediation: "Never serve .env files. Add them to .gitignore and configure your server to deny access.",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
path: "/wp-config.php",
|
|
129
|
+
name: "WordPress Config",
|
|
130
|
+
severity: "critical",
|
|
131
|
+
indicators: ["DB_NAME", "DB_PASSWORD", "AUTH_KEY"],
|
|
132
|
+
remediation: "Ensure wp-config.php is not accessible via HTTP.",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
path: "/.git/config",
|
|
136
|
+
name: "Git Repository Config",
|
|
137
|
+
severity: "critical",
|
|
138
|
+
indicators: ["[core]", "[remote", "repositoryformatversion"],
|
|
139
|
+
remediation: "Block access to .git directory. Your source code is exposed.",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
path: "/.git/HEAD",
|
|
143
|
+
name: "Git HEAD Reference",
|
|
144
|
+
severity: "high",
|
|
145
|
+
indicators: ["ref:", "refs/heads/"],
|
|
146
|
+
remediation: "Block access to the entire .git directory in your web server configuration.",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: "/config.json",
|
|
150
|
+
name: "JSON Config File",
|
|
151
|
+
severity: "medium",
|
|
152
|
+
indicators: ["apiKey", "secret", "password", "database", "connection"],
|
|
153
|
+
remediation: "Do not serve config files via HTTP. Move them outside the web root.",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
path: "/test",
|
|
157
|
+
name: "Test Endpoint",
|
|
158
|
+
severity: "low",
|
|
159
|
+
indicators: ["test", "debug", "hello"],
|
|
160
|
+
remediation: "Remove test endpoints before deploying to production.",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
path: "/health",
|
|
164
|
+
name: "Health Check (Verbose)",
|
|
165
|
+
severity: "low",
|
|
166
|
+
indicators: ["database", "connection", "redis", "memory", "uptime"],
|
|
167
|
+
remediation: "Limit health check responses to simple status. Do not expose internal service details.",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
path: "/metrics",
|
|
171
|
+
name: "Prometheus Metrics",
|
|
172
|
+
severity: "medium",
|
|
173
|
+
indicators: ["process_", "http_", "nodejs_", "go_"],
|
|
174
|
+
remediation: "Protect /metrics endpoint behind authentication or restrict to internal networks.",
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
async analyzeContent(context, content) {
|
|
178
|
+
const vulnerabilities = [];
|
|
179
|
+
const baseUrl = new URL(context.url).origin;
|
|
180
|
+
for (const endpoint of this.debugEndpoints) {
|
|
181
|
+
const fullPath = `${baseUrl}${endpoint.path}`;
|
|
182
|
+
if (this.reportedPaths.has(fullPath))
|
|
183
|
+
continue;
|
|
184
|
+
try {
|
|
185
|
+
const response = await context.page.evaluate(async (url) => {
|
|
186
|
+
try {
|
|
187
|
+
const res = await fetch(url, {
|
|
188
|
+
method: "GET",
|
|
189
|
+
redirect: "follow",
|
|
190
|
+
credentials: "omit",
|
|
191
|
+
});
|
|
192
|
+
const text = await res.text();
|
|
193
|
+
return { status: res.status, body: text.substring(0, 2000) };
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return { status: 0, body: "" };
|
|
197
|
+
}
|
|
198
|
+
}, fullPath);
|
|
199
|
+
// Check if the endpoint exists and contains debug indicators
|
|
200
|
+
if (response.status >= 200 && response.status < 400) {
|
|
201
|
+
const bodyLower = response.body.toLowerCase();
|
|
202
|
+
// For endpoints with no indicators (like heap dumps), 200 status is enough
|
|
203
|
+
const hasIndicators = endpoint.indicators.length === 0 ||
|
|
204
|
+
endpoint.indicators.some(ind => bodyLower.includes(ind.toLowerCase()));
|
|
205
|
+
if (hasIndicators) {
|
|
206
|
+
this.reportedPaths.add(fullPath);
|
|
207
|
+
vulnerabilities.push(this.createVulnerability(`Exposed ${endpoint.name}`, `The ${endpoint.name} endpoint is accessible at ${endpoint.path}. ` +
|
|
208
|
+
`This can expose sensitive internal information, configuration details, or debug data.`, fullPath, endpoint.severity, `HTTP ${response.status} at ${endpoint.path} (${response.body.substring(0, 100)}...)`, endpoint.remediation, "CWE-215"));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Endpoint not reachable, skip silently
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return vulnerabilities;
|
|
217
|
+
}
|
|
218
|
+
reset() {
|
|
219
|
+
this.reportedPaths.clear();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
exports.DebugEndpointPlugin = DebugEndpointPlugin;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext, VulnerabilityTestResult } from "../types";
|
|
2
|
+
export declare class DirectoryTraversalPlugin extends BaseVulnerabilityPlugin {
|
|
3
|
+
readonly name = "Directory Traversal / LFI Detector";
|
|
4
|
+
readonly type: "lfi";
|
|
5
|
+
readonly description = "Tests for path traversal and local file inclusion vulnerabilities";
|
|
6
|
+
/**
|
|
7
|
+
* Payloads designed to detect directory traversal / LFI.
|
|
8
|
+
* Each payload targets a well-known file with distinctive content markers.
|
|
9
|
+
*/
|
|
10
|
+
private readonly payloads;
|
|
11
|
+
testParameter(context: PluginContext, param: string, value: string): Promise<VulnerabilityTestResult>;
|
|
12
|
+
private buildTestUrl;
|
|
13
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DirectoryTraversalPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class DirectoryTraversalPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "Directory Traversal / LFI Detector";
|
|
7
|
+
type = "lfi";
|
|
8
|
+
description = "Tests for path traversal and local file inclusion vulnerabilities";
|
|
9
|
+
/**
|
|
10
|
+
* Payloads designed to detect directory traversal / LFI.
|
|
11
|
+
* Each payload targets a well-known file with distinctive content markers.
|
|
12
|
+
*/
|
|
13
|
+
payloads = [
|
|
14
|
+
// Linux/Unix path traversal
|
|
15
|
+
{
|
|
16
|
+
payload: "../../../etc/passwd",
|
|
17
|
+
markers: ["root:", "/bin/bash", "/bin/sh", "nobody:"],
|
|
18
|
+
os: "linux",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
payload: "....//....//....//etc/passwd",
|
|
22
|
+
markers: ["root:", "/bin/bash", "/bin/sh"],
|
|
23
|
+
os: "linux",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
payload: "..%2F..%2F..%2Fetc%2Fpasswd",
|
|
27
|
+
markers: ["root:", "/bin/bash", "/bin/sh"],
|
|
28
|
+
os: "linux",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
payload: "%2e%2e/%2e%2e/%2e%2e/etc/passwd",
|
|
32
|
+
markers: ["root:", "/bin/bash"],
|
|
33
|
+
os: "linux",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
payload: "....\\\\....\\\\....\\\\etc\\\\passwd",
|
|
37
|
+
markers: ["root:", "/bin/bash"],
|
|
38
|
+
os: "linux",
|
|
39
|
+
},
|
|
40
|
+
// Windows path traversal
|
|
41
|
+
{
|
|
42
|
+
payload: "..\\..\\..\\windows\\win.ini",
|
|
43
|
+
markers: ["[fonts]", "[extensions]", "[mci extensions]"],
|
|
44
|
+
os: "windows",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
payload: "..%5C..%5C..%5Cwindows%5Cwin.ini",
|
|
48
|
+
markers: ["[fonts]", "[extensions]"],
|
|
49
|
+
os: "windows",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
payload: "../../../windows/win.ini",
|
|
53
|
+
markers: ["[fonts]", "[extensions]"],
|
|
54
|
+
os: "windows",
|
|
55
|
+
},
|
|
56
|
+
// Null byte injection (legacy PHP)
|
|
57
|
+
{
|
|
58
|
+
payload: "../../../etc/passwd%00",
|
|
59
|
+
markers: ["root:", "/bin/bash"],
|
|
60
|
+
os: "linux",
|
|
61
|
+
},
|
|
62
|
+
// Double encoding
|
|
63
|
+
{
|
|
64
|
+
payload: "%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd",
|
|
65
|
+
markers: ["root:", "/bin/bash"],
|
|
66
|
+
os: "linux",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
async testParameter(context, param, value) {
|
|
70
|
+
for (const { payload, markers, os } of this.payloads) {
|
|
71
|
+
try {
|
|
72
|
+
const testUrl = this.buildTestUrl(context.url, param, payload);
|
|
73
|
+
const response = await context.page.evaluate(async (url) => {
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(url, {
|
|
76
|
+
method: "GET",
|
|
77
|
+
redirect: "follow",
|
|
78
|
+
credentials: "omit",
|
|
79
|
+
});
|
|
80
|
+
const text = await res.text();
|
|
81
|
+
return { status: res.status, body: text.substring(0, 5000) };
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { status: 0, body: "" };
|
|
85
|
+
}
|
|
86
|
+
}, testUrl);
|
|
87
|
+
if (response.status >= 200 && response.status < 500) {
|
|
88
|
+
const bodyLower = response.body.toLowerCase();
|
|
89
|
+
const matchedMarkers = markers.filter(m => bodyLower.includes(m.toLowerCase()));
|
|
90
|
+
if (matchedMarkers.length >= 2) {
|
|
91
|
+
return this.success(this.createVulnerability("Directory Traversal / Local File Inclusion", `The parameter '${param}' is vulnerable to directory traversal. ` +
|
|
92
|
+
`An attacker can read arbitrary files from the server filesystem (${os}).`, context.url, "critical", `Payload: ${param}=${payload} — matched markers: ${matchedMarkers.join(", ")}`, "Validate and sanitize all file path inputs. Use a whitelist of allowed files/directories. " +
|
|
93
|
+
"Never pass user input directly to file system operations. " +
|
|
94
|
+
"Use path.resolve() and verify the resolved path starts with the expected base directory.", "CWE-22"));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Skip this payload, try next
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return this.failure();
|
|
103
|
+
}
|
|
104
|
+
buildTestUrl(baseUrl, param, payload) {
|
|
105
|
+
const url = new URL(baseUrl);
|
|
106
|
+
url.searchParams.set(param, payload);
|
|
107
|
+
return url.toString();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.DirectoryTraversalPlugin = DirectoryTraversalPlugin;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
import { Vulnerability } from "../../core/vulnerability-detector";
|
|
3
|
+
export declare class OpenRedirectPlugin extends BaseVulnerabilityPlugin {
|
|
4
|
+
readonly name = "Open Redirect Detector";
|
|
5
|
+
readonly type: "redirect";
|
|
6
|
+
readonly description = "Detects open redirect vulnerabilities in URL parameters";
|
|
7
|
+
private readonly redirectParams;
|
|
8
|
+
private readonly testDomains;
|
|
9
|
+
analyzeContent(context: PluginContext, content: string): Promise<Vulnerability[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenRedirectPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class OpenRedirectPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "Open Redirect Detector";
|
|
7
|
+
type = "redirect";
|
|
8
|
+
description = "Detects open redirect vulnerabilities in URL parameters";
|
|
9
|
+
redirectParams = [
|
|
10
|
+
"url", "redirect", "redirect_url", "redirect_uri", "return", "return_url",
|
|
11
|
+
"returnTo", "next", "next_url", "goto", "target", "dest", "destination",
|
|
12
|
+
"rurl", "redir", "out", "continue", "forward", "callback", "callback_url",
|
|
13
|
+
"path", "ref", "site", "view", "to", "link", "logout_redirect",
|
|
14
|
+
];
|
|
15
|
+
testDomains = [
|
|
16
|
+
"https://evil.com",
|
|
17
|
+
"//evil.com",
|
|
18
|
+
"https://evil.com%2f%2f",
|
|
19
|
+
"/\\evil.com",
|
|
20
|
+
"https:evil.com",
|
|
21
|
+
];
|
|
22
|
+
async analyzeContent(context, content) {
|
|
23
|
+
const vulnerabilities = [];
|
|
24
|
+
const url = new URL(context.url);
|
|
25
|
+
// Check if the current URL uses any redirect-like parameters
|
|
26
|
+
for (const param of this.redirectParams) {
|
|
27
|
+
const value = url.searchParams.get(param);
|
|
28
|
+
if (value) {
|
|
29
|
+
// Current URL has a redirect param — test it
|
|
30
|
+
for (const testDomain of this.testDomains) {
|
|
31
|
+
try {
|
|
32
|
+
const testUrl = new URL(context.url);
|
|
33
|
+
testUrl.searchParams.set(param, testDomain);
|
|
34
|
+
const result = await context.page.evaluate(async (targetUrl) => {
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(targetUrl, {
|
|
37
|
+
method: "GET",
|
|
38
|
+
redirect: "manual",
|
|
39
|
+
credentials: "omit",
|
|
40
|
+
});
|
|
41
|
+
const location = res.headers.get("location") || "";
|
|
42
|
+
return {
|
|
43
|
+
status: res.status,
|
|
44
|
+
location,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { status: 0, location: "" };
|
|
49
|
+
}
|
|
50
|
+
}, testUrl.toString());
|
|
51
|
+
if (result.status >= 300 && result.status < 400 &&
|
|
52
|
+
result.location.includes("evil.com")) {
|
|
53
|
+
vulnerabilities.push(this.createVulnerability("Open Redirect", `The parameter '${param}' allows redirection to arbitrary external URLs. ` +
|
|
54
|
+
`Attackers can use this for phishing by crafting legitimate-looking URLs that redirect to malicious sites.`, context.url, "medium", `${param}=${testDomain} → Location: ${result.location}`, "Validate redirect URLs against a whitelist of allowed domains. " +
|
|
55
|
+
"Use relative paths instead of full URLs. " +
|
|
56
|
+
"Never redirect to user-supplied URLs without validation.", "CWE-601"));
|
|
57
|
+
break; // One proof is enough for this param
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Skip this test
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return vulnerabilities;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.OpenRedirectPlugin = OpenRedirectPlugin;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
export declare class SQLInjectionPlugin extends BaseVulnerabilityPlugin {
|
|
3
|
+
readonly name = "SQL Injection Detector";
|
|
4
|
+
readonly type: "sqli";
|
|
5
|
+
readonly description = "Detects SQL Injection vulnerabilities";
|
|
6
|
+
private readonly errorBasedPayloads;
|
|
7
|
+
private readonly timeBasedPayloads;
|
|
8
|
+
private getErrorPayloads;
|
|
9
|
+
private readonly sqlErrors;
|
|
10
|
+
testParameter(context: PluginContext, param: string, _value: string): Promise<import("../types").VulnerabilityTestResult>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SQLInjectionPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class SQLInjectionPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "SQL Injection Detector";
|
|
7
|
+
type = "sqli";
|
|
8
|
+
description = "Detects SQL Injection vulnerabilities";
|
|
9
|
+
errorBasedPayloads = [
|
|
10
|
+
"'",
|
|
11
|
+
"1' OR '1'='1",
|
|
12
|
+
"1; DROP TABLE users--",
|
|
13
|
+
"' OR 1=1--",
|
|
14
|
+
"' UNION SELECT 1--",
|
|
15
|
+
"1' AND '1'='1",
|
|
16
|
+
];
|
|
17
|
+
timeBasedPayloads = [
|
|
18
|
+
"' AND SLEEP(5)--",
|
|
19
|
+
"1' AND SLEEP(5)--",
|
|
20
|
+
"'; WAITFOR DELAY '00:00:05'--",
|
|
21
|
+
];
|
|
22
|
+
async getErrorPayloads(context, param) {
|
|
23
|
+
if (context.payloadGenerator) {
|
|
24
|
+
const aiPayloads = await context.payloadGenerator.generatePayloads("sqli", {
|
|
25
|
+
parameterName: param,
|
|
26
|
+
url: context.url,
|
|
27
|
+
});
|
|
28
|
+
if (aiPayloads.length > 0) {
|
|
29
|
+
return [...aiPayloads, ...this.errorBasedPayloads];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return this.errorBasedPayloads;
|
|
33
|
+
}
|
|
34
|
+
sqlErrors = [
|
|
35
|
+
"SQL syntax",
|
|
36
|
+
"mysql_fetch",
|
|
37
|
+
"ORA-",
|
|
38
|
+
"PostgreSQL",
|
|
39
|
+
"SQLite",
|
|
40
|
+
"ODBC",
|
|
41
|
+
"JET Database",
|
|
42
|
+
"Microsoft Access Driver",
|
|
43
|
+
"unterminated",
|
|
44
|
+
"mysql_num_rows",
|
|
45
|
+
"mysql_query",
|
|
46
|
+
"Microsoft SQL Native Client error",
|
|
47
|
+
"SQLServer JDBC Driver",
|
|
48
|
+
"ORA-00933",
|
|
49
|
+
"PG::SyntaxError",
|
|
50
|
+
"Warning: pg_",
|
|
51
|
+
"Syntax error",
|
|
52
|
+
];
|
|
53
|
+
async testParameter(context, param, _value) {
|
|
54
|
+
// Test for error-based SQL injection
|
|
55
|
+
const errorPayloads = await this.getErrorPayloads(context, param);
|
|
56
|
+
for (const payload of errorPayloads) {
|
|
57
|
+
try {
|
|
58
|
+
const url = new URL(context.url);
|
|
59
|
+
url.searchParams.set(param, payload);
|
|
60
|
+
await context.page.goto(url.toString(), {
|
|
61
|
+
waitUntil: "networkidle2",
|
|
62
|
+
timeout: context.timeout
|
|
63
|
+
});
|
|
64
|
+
const content = await context.page.content();
|
|
65
|
+
for (const error of this.sqlErrors) {
|
|
66
|
+
if (content.includes(error)) {
|
|
67
|
+
return this.success(this.createVulnerability("SQL Injection", `The parameter '${param}' is vulnerable to SQL injection. Database error messages were detected.`, context.url, "critical", `Error: ${error}`, "Use parameterized queries (prepared statements). Never concatenate user input into SQL.", "CWE-89"));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return this.failure(error.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Test for time-based blind SQL injection
|
|
76
|
+
let baselineTime = 0;
|
|
77
|
+
try {
|
|
78
|
+
const baselineStart = Date.now();
|
|
79
|
+
await context.page.goto(context.url, {
|
|
80
|
+
waitUntil: "networkidle2",
|
|
81
|
+
timeout: context.timeout
|
|
82
|
+
});
|
|
83
|
+
baselineTime = Date.now() - baselineStart;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return this.failure(error.message);
|
|
87
|
+
}
|
|
88
|
+
for (const payload of this.timeBasedPayloads) {
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(context.url);
|
|
91
|
+
url.searchParams.set(param, payload);
|
|
92
|
+
const startTime = Date.now();
|
|
93
|
+
await context.page.goto(url.toString(), {
|
|
94
|
+
waitUntil: "networkidle2",
|
|
95
|
+
timeout: context.timeout
|
|
96
|
+
});
|
|
97
|
+
const testTime = Date.now() - startTime;
|
|
98
|
+
if (testTime > baselineTime + 3000) {
|
|
99
|
+
return this.success(this.createVulnerability("Blind SQL Injection", `Time-based blind SQL injection detected on parameter '${param}'. Response time indicates successful payload execution.`, context.url, "high", `Response time increased by ${testTime - baselineTime}ms with sleep payload`, "Use parameterized queries. Implement input validation and proper error handling.", "CWE-89"));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
return this.failure(error.message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return this.failure();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.SQLInjectionPlugin = SQLInjectionPlugin;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
import { Vulnerability } from "../../core/vulnerability-detector";
|
|
3
|
+
export declare class SecurityHeadersPlugin extends BaseVulnerabilityPlugin {
|
|
4
|
+
readonly name = "Security Headers Analyzer";
|
|
5
|
+
readonly type: "header";
|
|
6
|
+
readonly description = "Analyzes HTTP security headers";
|
|
7
|
+
private readonly reportedHosts;
|
|
8
|
+
private readonly requiredHeaders;
|
|
9
|
+
analyzeHeaders(context: PluginContext, headers: Record<string, string>): Promise<Vulnerability[]>;
|
|
10
|
+
reset(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SecurityHeadersPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class SecurityHeadersPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "Security Headers Analyzer";
|
|
7
|
+
type = "header";
|
|
8
|
+
description = "Analyzes HTTP security headers";
|
|
9
|
+
reportedHosts = new Set();
|
|
10
|
+
requiredHeaders = {
|
|
11
|
+
"content-security-policy": {
|
|
12
|
+
title: "Missing Content-Security-Policy",
|
|
13
|
+
severity: "low",
|
|
14
|
+
remediation: "Implement a strict CSP to prevent XSS and data injection attacks.",
|
|
15
|
+
},
|
|
16
|
+
"x-frame-options": {
|
|
17
|
+
title: "Missing X-Frame-Options",
|
|
18
|
+
severity: "low",
|
|
19
|
+
remediation: "Set X-Frame-Options to DENY or SAMEORIGIN to prevent clickjacking.",
|
|
20
|
+
},
|
|
21
|
+
"strict-transport-security": {
|
|
22
|
+
title: "Missing Strict-Transport-Security (HSTS)",
|
|
23
|
+
severity: "low",
|
|
24
|
+
remediation: "Enable HSTS with max-age of at least 31536000 seconds.",
|
|
25
|
+
},
|
|
26
|
+
"x-content-type-options": {
|
|
27
|
+
title: "Missing X-Content-Type-Options",
|
|
28
|
+
severity: "info",
|
|
29
|
+
remediation: "Set X-Content-Type-Options to 'nosniff'.",
|
|
30
|
+
},
|
|
31
|
+
"referrer-policy": {
|
|
32
|
+
title: "Missing Referrer-Policy",
|
|
33
|
+
severity: "info",
|
|
34
|
+
remediation: "Set Referrer-Policy to 'strict-origin-when-cross-origin'.",
|
|
35
|
+
},
|
|
36
|
+
"permissions-policy": {
|
|
37
|
+
title: "Missing Permissions-Policy",
|
|
38
|
+
severity: "info",
|
|
39
|
+
remediation: "Restrict browser features using Permissions-Policy header.",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
async analyzeHeaders(context, headers) {
|
|
43
|
+
const host = new URL(context.url).host;
|
|
44
|
+
// Only report once per host
|
|
45
|
+
if (this.reportedHosts.has(host)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const vulnerabilities = [];
|
|
49
|
+
for (const [header, config] of Object.entries(this.requiredHeaders)) {
|
|
50
|
+
if (!headers[header.toLowerCase()]) {
|
|
51
|
+
vulnerabilities.push(this.createVulnerability(config.title, `The ${header} security header is not set on ${host}.`, context.url, config.severity, undefined, config.remediation));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (vulnerabilities.length > 0) {
|
|
55
|
+
this.reportedHosts.add(host);
|
|
56
|
+
}
|
|
57
|
+
return vulnerabilities;
|
|
58
|
+
}
|
|
59
|
+
reset() {
|
|
60
|
+
this.reportedHosts.clear();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.SecurityHeadersPlugin = SecurityHeadersPlugin;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseVulnerabilityPlugin, PluginContext } from "../types";
|
|
2
|
+
import { Vulnerability } from "../../core/vulnerability-detector";
|
|
3
|
+
export declare class SensitiveDataPlugin extends BaseVulnerabilityPlugin {
|
|
4
|
+
readonly name = "Sensitive Data Detector";
|
|
5
|
+
readonly type: "sensitive_data";
|
|
6
|
+
readonly description = "Detects exposed sensitive data in responses";
|
|
7
|
+
private readonly patterns;
|
|
8
|
+
analyzeContent(context: PluginContext, content: string): Promise<Vulnerability[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SensitiveDataPlugin = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
class SensitiveDataPlugin extends types_1.BaseVulnerabilityPlugin {
|
|
6
|
+
name = "Sensitive Data Detector";
|
|
7
|
+
type = "sensitive_data";
|
|
8
|
+
description = "Detects exposed sensitive data in responses";
|
|
9
|
+
patterns = [
|
|
10
|
+
{ regex: /sk-[a-zA-Z0-9]{48}/g, name: "OpenAI API Key", severity: "critical" },
|
|
11
|
+
{ regex: /ghp_[a-zA-Z0-9]{36}/g, name: "GitHub Token", severity: "critical" },
|
|
12
|
+
{ regex: /AKIA[0-9A-Z]{16}/g, name: "AWS Access Key", severity: "critical" },
|
|
13
|
+
{ regex: /xox[baprs]-[0-9a-zA-Z]{10,}/g, name: "Slack Token", severity: "high" },
|
|
14
|
+
{ regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*/g, name: "JWT Token", severity: "high" },
|
|
15
|
+
{ regex: /AIza[0-9A-Za-z_-]{35}/g, name: "Google API Key", severity: "high" },
|
|
16
|
+
{ regex: /password["\s:=]+[^\s"]{6,}/gi, name: "Hardcoded Password", severity: "high" },
|
|
17
|
+
{ regex: /api[_-]?key["\s:=]+[\w-]{20,}/gi, name: "Generic API Key", severity: "medium" },
|
|
18
|
+
{ regex: /-----BEGIN (RSA |EC )?PRIVATE KEY-----/g, name: "Private Key", severity: "critical" },
|
|
19
|
+
{ regex: /database[_-]?url["\s:=]+.*(?:mysql|postgres|mongodb):\/\//gi, name: "Database Connection String", severity: "high" },
|
|
20
|
+
];
|
|
21
|
+
async analyzeContent(context, content) {
|
|
22
|
+
const vulnerabilities = [];
|
|
23
|
+
for (const pattern of this.patterns) {
|
|
24
|
+
const matches = content.match(pattern.regex);
|
|
25
|
+
if (matches && matches.length > 0) {
|
|
26
|
+
vulnerabilities.push(this.createVulnerability(`Exposed ${pattern.name}`, `Sensitive ${pattern.name} found in response. This could lead to account compromise or data breach.`, context.url, pattern.severity, `Found: ${matches[0].substring(0, 30)}...`, "Remove sensitive data from client-side code. Use environment variables and secure secret management.", "CWE-200"));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return vulnerabilities;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.SensitiveDataPlugin = SensitiveDataPlugin;
|