defense-mcp-server 0.6.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/CHANGELOG.md +471 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/build/core/auto-installer.d.ts +102 -0
- package/build/core/auto-installer.d.ts.map +1 -0
- package/build/core/auto-installer.js +833 -0
- package/build/core/backup-manager.d.ts +63 -0
- package/build/core/backup-manager.d.ts.map +1 -0
- package/build/core/backup-manager.js +189 -0
- package/build/core/changelog.d.ts +75 -0
- package/build/core/changelog.d.ts.map +1 -0
- package/build/core/changelog.js +123 -0
- package/build/core/command-allowlist.d.ts +129 -0
- package/build/core/command-allowlist.d.ts.map +1 -0
- package/build/core/command-allowlist.js +849 -0
- package/build/core/config.d.ts +79 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +193 -0
- package/build/core/dependency-validator.d.ts +106 -0
- package/build/core/dependency-validator.d.ts.map +1 -0
- package/build/core/dependency-validator.js +405 -0
- package/build/core/distro-adapter.d.ts +177 -0
- package/build/core/distro-adapter.d.ts.map +1 -0
- package/build/core/distro-adapter.js +481 -0
- package/build/core/distro.d.ts +68 -0
- package/build/core/distro.d.ts.map +1 -0
- package/build/core/distro.js +457 -0
- package/build/core/encrypted-state.d.ts +76 -0
- package/build/core/encrypted-state.d.ts.map +1 -0
- package/build/core/encrypted-state.js +209 -0
- package/build/core/executor.d.ts +56 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +350 -0
- package/build/core/installer.d.ts +92 -0
- package/build/core/installer.d.ts.map +1 -0
- package/build/core/installer.js +1072 -0
- package/build/core/logger.d.ts +102 -0
- package/build/core/logger.d.ts.map +1 -0
- package/build/core/logger.js +132 -0
- package/build/core/parsers.d.ts +151 -0
- package/build/core/parsers.d.ts.map +1 -0
- package/build/core/parsers.js +479 -0
- package/build/core/policy-engine.d.ts +170 -0
- package/build/core/policy-engine.d.ts.map +1 -0
- package/build/core/policy-engine.js +656 -0
- package/build/core/preflight.d.ts +157 -0
- package/build/core/preflight.d.ts.map +1 -0
- package/build/core/preflight.js +638 -0
- package/build/core/privilege-manager.d.ts +108 -0
- package/build/core/privilege-manager.d.ts.map +1 -0
- package/build/core/privilege-manager.js +363 -0
- package/build/core/rate-limiter.d.ts +67 -0
- package/build/core/rate-limiter.d.ts.map +1 -0
- package/build/core/rate-limiter.js +129 -0
- package/build/core/rollback.d.ts +73 -0
- package/build/core/rollback.d.ts.map +1 -0
- package/build/core/rollback.js +278 -0
- package/build/core/safeguards.d.ts +58 -0
- package/build/core/safeguards.d.ts.map +1 -0
- package/build/core/safeguards.js +448 -0
- package/build/core/sanitizer.d.ts +118 -0
- package/build/core/sanitizer.d.ts.map +1 -0
- package/build/core/sanitizer.js +459 -0
- package/build/core/secure-fs.d.ts +67 -0
- package/build/core/secure-fs.d.ts.map +1 -0
- package/build/core/secure-fs.js +143 -0
- package/build/core/spawn-safe.d.ts +55 -0
- package/build/core/spawn-safe.d.ts.map +1 -0
- package/build/core/spawn-safe.js +146 -0
- package/build/core/sudo-guard.d.ts +145 -0
- package/build/core/sudo-guard.d.ts.map +1 -0
- package/build/core/sudo-guard.js +349 -0
- package/build/core/sudo-session.d.ts +100 -0
- package/build/core/sudo-session.d.ts.map +1 -0
- package/build/core/sudo-session.js +319 -0
- package/build/core/tool-dependencies.d.ts +61 -0
- package/build/core/tool-dependencies.d.ts.map +1 -0
- package/build/core/tool-dependencies.js +571 -0
- package/build/core/tool-registry.d.ts +111 -0
- package/build/core/tool-registry.d.ts.map +1 -0
- package/build/core/tool-registry.js +656 -0
- package/build/core/tool-wrapper.d.ts +73 -0
- package/build/core/tool-wrapper.d.ts.map +1 -0
- package/build/core/tool-wrapper.js +296 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +247 -0
- package/build/tools/access-control.d.ts +9 -0
- package/build/tools/access-control.d.ts.map +1 -0
- package/build/tools/access-control.js +1818 -0
- package/build/tools/api-security.d.ts +12 -0
- package/build/tools/api-security.d.ts.map +1 -0
- package/build/tools/api-security.js +901 -0
- package/build/tools/app-hardening.d.ts +11 -0
- package/build/tools/app-hardening.d.ts.map +1 -0
- package/build/tools/app-hardening.js +768 -0
- package/build/tools/backup.d.ts +8 -0
- package/build/tools/backup.d.ts.map +1 -0
- package/build/tools/backup.js +381 -0
- package/build/tools/cloud-security.d.ts +17 -0
- package/build/tools/cloud-security.d.ts.map +1 -0
- package/build/tools/cloud-security.js +739 -0
- package/build/tools/compliance.d.ts +10 -0
- package/build/tools/compliance.d.ts.map +1 -0
- package/build/tools/compliance.js +1225 -0
- package/build/tools/container-security.d.ts +14 -0
- package/build/tools/container-security.d.ts.map +1 -0
- package/build/tools/container-security.js +788 -0
- package/build/tools/deception.d.ts +13 -0
- package/build/tools/deception.d.ts.map +1 -0
- package/build/tools/deception.js +763 -0
- package/build/tools/dns-security.d.ts +93 -0
- package/build/tools/dns-security.d.ts.map +1 -0
- package/build/tools/dns-security.js +745 -0
- package/build/tools/drift-detection.d.ts +8 -0
- package/build/tools/drift-detection.d.ts.map +1 -0
- package/build/tools/drift-detection.js +326 -0
- package/build/tools/ebpf-security.d.ts +15 -0
- package/build/tools/ebpf-security.d.ts.map +1 -0
- package/build/tools/ebpf-security.js +294 -0
- package/build/tools/encryption.d.ts +9 -0
- package/build/tools/encryption.d.ts.map +1 -0
- package/build/tools/encryption.js +1667 -0
- package/build/tools/firewall.d.ts +9 -0
- package/build/tools/firewall.d.ts.map +1 -0
- package/build/tools/firewall.js +1398 -0
- package/build/tools/hardening.d.ts +10 -0
- package/build/tools/hardening.d.ts.map +1 -0
- package/build/tools/hardening.js +2654 -0
- package/build/tools/ids.d.ts +9 -0
- package/build/tools/ids.d.ts.map +1 -0
- package/build/tools/ids.js +624 -0
- package/build/tools/incident-response.d.ts +10 -0
- package/build/tools/incident-response.d.ts.map +1 -0
- package/build/tools/incident-response.js +1180 -0
- package/build/tools/logging.d.ts +12 -0
- package/build/tools/logging.d.ts.map +1 -0
- package/build/tools/logging.js +454 -0
- package/build/tools/malware.d.ts +10 -0
- package/build/tools/malware.d.ts.map +1 -0
- package/build/tools/malware.js +532 -0
- package/build/tools/meta.d.ts +11 -0
- package/build/tools/meta.d.ts.map +1 -0
- package/build/tools/meta.js +2278 -0
- package/build/tools/network-defense.d.ts +12 -0
- package/build/tools/network-defense.d.ts.map +1 -0
- package/build/tools/network-defense.js +760 -0
- package/build/tools/patch-management.d.ts +3 -0
- package/build/tools/patch-management.d.ts.map +1 -0
- package/build/tools/patch-management.js +708 -0
- package/build/tools/process-security.d.ts +12 -0
- package/build/tools/process-security.d.ts.map +1 -0
- package/build/tools/process-security.js +784 -0
- package/build/tools/reporting.d.ts +11 -0
- package/build/tools/reporting.d.ts.map +1 -0
- package/build/tools/reporting.js +559 -0
- package/build/tools/secrets.d.ts +9 -0
- package/build/tools/secrets.d.ts.map +1 -0
- package/build/tools/secrets.js +596 -0
- package/build/tools/siem-integration.d.ts +18 -0
- package/build/tools/siem-integration.d.ts.map +1 -0
- package/build/tools/siem-integration.js +754 -0
- package/build/tools/sudo-management.d.ts +18 -0
- package/build/tools/sudo-management.d.ts.map +1 -0
- package/build/tools/sudo-management.js +737 -0
- package/build/tools/supply-chain-security.d.ts +8 -0
- package/build/tools/supply-chain-security.d.ts.map +1 -0
- package/build/tools/supply-chain-security.js +256 -0
- package/build/tools/threat-intel.d.ts +22 -0
- package/build/tools/threat-intel.d.ts.map +1 -0
- package/build/tools/threat-intel.js +749 -0
- package/build/tools/vulnerability-management.d.ts +11 -0
- package/build/tools/vulnerability-management.d.ts.map +1 -0
- package/build/tools/vulnerability-management.js +667 -0
- package/build/tools/waf.d.ts +12 -0
- package/build/tools/waf.d.ts.map +1 -0
- package/build/tools/waf.js +843 -0
- package/build/tools/wireless-security.d.ts +19 -0
- package/build/tools/wireless-security.d.ts.map +1 -0
- package/build/tools/wireless-security.js +826 -0
- package/build/tools/zero-trust-network.d.ts +8 -0
- package/build/tools/zero-trust-network.d.ts.map +1 -0
- package/build/tools/zero-trust-network.js +367 -0
- package/docs/SAFEGUARDS.md +518 -0
- package/docs/TOOLS-REFERENCE.md +665 -0
- package/package.json +87 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAF (Web Application Firewall) management tools for Kali Defense MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Registers 1 tool: waf_manage (actions: modsec_audit, modsec_rules,
|
|
5
|
+
* rate_limit_config, owasp_crs_deploy, blocked_requests)
|
|
6
|
+
*
|
|
7
|
+
* Provides ModSecurity WAF auditing, rule management, rate limiting
|
|
8
|
+
* configuration, OWASP CRS deployment checks, and blocked request analysis.
|
|
9
|
+
*/
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { spawnSafe } from "../core/spawn-safe.js";
|
|
12
|
+
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
13
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
14
|
+
const MODSEC_CONF_PATHS = {
|
|
15
|
+
nginx: [
|
|
16
|
+
"/etc/modsecurity/modsecurity.conf",
|
|
17
|
+
"/etc/nginx/modsecurity.conf",
|
|
18
|
+
"/etc/nginx/modsec/modsecurity.conf",
|
|
19
|
+
],
|
|
20
|
+
apache: [
|
|
21
|
+
"/etc/modsecurity/modsecurity.conf",
|
|
22
|
+
"/etc/apache2/mods-enabled/security2.conf",
|
|
23
|
+
"/etc/httpd/conf.d/mod_security.conf",
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
const MODSEC_RULES_DIRS = [
|
|
27
|
+
"/etc/modsecurity/rules",
|
|
28
|
+
"/usr/share/modsecurity-crs/rules",
|
|
29
|
+
"/etc/modsecurity-crs/rules",
|
|
30
|
+
];
|
|
31
|
+
const MODSEC_AUDIT_LOG_PATHS = [
|
|
32
|
+
"/var/log/modsecurity/modsec_audit.log",
|
|
33
|
+
"/var/log/modsec_audit.log",
|
|
34
|
+
"/var/log/apache2/modsec_audit.log",
|
|
35
|
+
"/var/log/nginx/modsec_audit.log",
|
|
36
|
+
];
|
|
37
|
+
const OWASP_CRS_PATHS = [
|
|
38
|
+
"/usr/share/modsecurity-crs",
|
|
39
|
+
"/etc/modsecurity-crs",
|
|
40
|
+
"/opt/owasp-crs",
|
|
41
|
+
"/etc/modsecurity/crs",
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Run a command via spawnSafe and collect output as a promise.
|
|
45
|
+
* Handles errors gracefully — returns error info instead of throwing.
|
|
46
|
+
*/
|
|
47
|
+
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
let child;
|
|
50
|
+
try {
|
|
51
|
+
child = spawnSafe(command, args);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
55
|
+
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let stdout = "";
|
|
59
|
+
let stderr = "";
|
|
60
|
+
let resolved = false;
|
|
61
|
+
const timer = setTimeout(() => {
|
|
62
|
+
if (!resolved) {
|
|
63
|
+
resolved = true;
|
|
64
|
+
child.kill("SIGTERM");
|
|
65
|
+
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
66
|
+
}
|
|
67
|
+
}, timeoutMs);
|
|
68
|
+
child.stdout?.on("data", (data) => {
|
|
69
|
+
stdout += data.toString();
|
|
70
|
+
});
|
|
71
|
+
child.stderr?.on("data", (data) => {
|
|
72
|
+
stderr += data.toString();
|
|
73
|
+
});
|
|
74
|
+
child.on("close", (code) => {
|
|
75
|
+
if (!resolved) {
|
|
76
|
+
resolved = true;
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
child.on("error", (err) => {
|
|
82
|
+
if (!resolved) {
|
|
83
|
+
resolved = true;
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run a command via sudo through spawnSafe.
|
|
92
|
+
*/
|
|
93
|
+
async function runSudoCommand(command, args, timeoutMs = 30_000) {
|
|
94
|
+
return runCommand("sudo", [command, ...args], timeoutMs);
|
|
95
|
+
}
|
|
96
|
+
// ── Action Implementations ─────────────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Audit ModSecurity WAF configuration.
|
|
99
|
+
*/
|
|
100
|
+
async function handleModsecAudit(webServer, outputFormat) {
|
|
101
|
+
try {
|
|
102
|
+
const sections = [];
|
|
103
|
+
const jsonData = {};
|
|
104
|
+
sections.push("🛡️ ModSecurity WAF Audit");
|
|
105
|
+
sections.push("=".repeat(55));
|
|
106
|
+
sections.push(`Web Server: ${webServer}`);
|
|
107
|
+
// Check if ModSecurity is installed
|
|
108
|
+
const dpkgCheck = await runCommand("dpkg", ["-l", "libapache2-mod-security2"]);
|
|
109
|
+
const modsecNginxCheck = await runCommand("dpkg", ["-l", "libnginx-mod-security"]);
|
|
110
|
+
const whichCheck = await runCommand("which", ["modsecurity-check"]);
|
|
111
|
+
const isInstalled = dpkgCheck.exitCode === 0 ||
|
|
112
|
+
modsecNginxCheck.exitCode === 0 ||
|
|
113
|
+
whichCheck.exitCode === 0;
|
|
114
|
+
jsonData.installed = isInstalled;
|
|
115
|
+
sections.push(`\n── Installation Status ──`);
|
|
116
|
+
if (!isInstalled) {
|
|
117
|
+
// Check for config file existence as fallback
|
|
118
|
+
const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
|
|
119
|
+
let configFound = false;
|
|
120
|
+
for (const confPath of confPaths) {
|
|
121
|
+
const testResult = await runCommand("test", ["-f", confPath]);
|
|
122
|
+
if (testResult.exitCode === 0) {
|
|
123
|
+
configFound = true;
|
|
124
|
+
sections.push(` ⚠️ ModSecurity package not found via dpkg, but config exists: ${confPath}`);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!configFound) {
|
|
129
|
+
sections.push(" ❌ ModSecurity is NOT installed");
|
|
130
|
+
sections.push(" Install with:");
|
|
131
|
+
if (webServer === "nginx") {
|
|
132
|
+
sections.push(" sudo apt install libnginx-mod-security");
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
sections.push(" sudo apt install libapache2-mod-security2");
|
|
136
|
+
}
|
|
137
|
+
jsonData.engine_mode = "not_installed";
|
|
138
|
+
if (outputFormat === "json") {
|
|
139
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
140
|
+
}
|
|
141
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
sections.push(" ✅ ModSecurity is installed");
|
|
146
|
+
}
|
|
147
|
+
// Read ModSecurity configuration
|
|
148
|
+
const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
|
|
149
|
+
let configContent = "";
|
|
150
|
+
let activeConfPath = "";
|
|
151
|
+
for (const confPath of confPaths) {
|
|
152
|
+
const catResult = await runSudoCommand("cat", [confPath]);
|
|
153
|
+
if (catResult.exitCode === 0 && catResult.stdout.trim().length > 0) {
|
|
154
|
+
configContent = catResult.stdout;
|
|
155
|
+
activeConfPath = confPath;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
sections.push(`\n── Configuration ──`);
|
|
160
|
+
if (activeConfPath) {
|
|
161
|
+
sections.push(` Config file: ${activeConfPath}`);
|
|
162
|
+
jsonData.config_path = activeConfPath;
|
|
163
|
+
// Check SecRuleEngine status
|
|
164
|
+
const engineMatch = configContent.match(/^\s*SecRuleEngine\s+(\S+)/m);
|
|
165
|
+
const engineMode = engineMatch ? engineMatch[1] : "unknown";
|
|
166
|
+
jsonData.engine_mode = engineMode;
|
|
167
|
+
if (engineMode === "On") {
|
|
168
|
+
sections.push(" ✅ SecRuleEngine: On (active protection)");
|
|
169
|
+
}
|
|
170
|
+
else if (engineMode === "DetectionOnly") {
|
|
171
|
+
sections.push(" ⚠️ SecRuleEngine: DetectionOnly (logging only, not blocking)");
|
|
172
|
+
}
|
|
173
|
+
else if (engineMode === "Off") {
|
|
174
|
+
sections.push(" ❌ SecRuleEngine: Off (WAF disabled!)");
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
sections.push(` ❓ SecRuleEngine: ${engineMode}`);
|
|
178
|
+
}
|
|
179
|
+
// Check audit logging
|
|
180
|
+
const auditLogMatch = configContent.match(/^\s*SecAuditLog\s+(\S+)/m);
|
|
181
|
+
const auditLog = auditLogMatch ? auditLogMatch[1] : "not configured";
|
|
182
|
+
jsonData.audit_log = auditLog;
|
|
183
|
+
sections.push(` Audit Log: ${auditLog}`);
|
|
184
|
+
const auditEngineMatch = configContent.match(/^\s*SecAuditEngine\s+(\S+)/m);
|
|
185
|
+
const auditEngine = auditEngineMatch ? auditEngineMatch[1] : "not set";
|
|
186
|
+
jsonData.audit_engine = auditEngine;
|
|
187
|
+
sections.push(` Audit Engine: ${auditEngine}`);
|
|
188
|
+
// Count rules
|
|
189
|
+
const ruleCount = (configContent.match(/SecRule\s/g) || []).length;
|
|
190
|
+
jsonData.inline_rule_count = ruleCount;
|
|
191
|
+
sections.push(` Inline Rules: ${ruleCount}`);
|
|
192
|
+
// Check for common misconfigurations
|
|
193
|
+
sections.push(`\n── Misconfiguration Checks ──`);
|
|
194
|
+
const issues = [];
|
|
195
|
+
if (engineMode === "Off") {
|
|
196
|
+
issues.push("SecRuleEngine is Off — WAF provides no protection");
|
|
197
|
+
}
|
|
198
|
+
if (auditEngine === "Off" || auditEngine === "not set") {
|
|
199
|
+
issues.push("Audit logging is disabled — no visibility into blocked requests");
|
|
200
|
+
}
|
|
201
|
+
if (!configContent.includes("SecRequestBodyAccess")) {
|
|
202
|
+
issues.push("SecRequestBodyAccess not configured — POST body inspection may be disabled");
|
|
203
|
+
}
|
|
204
|
+
if (!configContent.includes("SecResponseBodyAccess")) {
|
|
205
|
+
issues.push("SecResponseBodyAccess not configured — response inspection may be disabled");
|
|
206
|
+
}
|
|
207
|
+
if (configContent.includes("SecRuleRemoveById")) {
|
|
208
|
+
const removedCount = (configContent.match(/SecRuleRemoveById/g) || []).length;
|
|
209
|
+
issues.push(`${removedCount} rule(s) disabled via SecRuleRemoveById — review for necessity`);
|
|
210
|
+
}
|
|
211
|
+
jsonData.issues = issues;
|
|
212
|
+
if (issues.length === 0) {
|
|
213
|
+
sections.push(" ✅ No common misconfigurations detected");
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
for (const issue of issues) {
|
|
217
|
+
sections.push(` ⚠️ ${issue}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
sections.push(" ❌ No ModSecurity configuration file found");
|
|
223
|
+
sections.push(" Searched paths:");
|
|
224
|
+
for (const p of confPaths) {
|
|
225
|
+
sections.push(` - ${p}`);
|
|
226
|
+
}
|
|
227
|
+
jsonData.config_path = null;
|
|
228
|
+
jsonData.engine_mode = "no_config";
|
|
229
|
+
}
|
|
230
|
+
if (outputFormat === "json") {
|
|
231
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
232
|
+
}
|
|
233
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
237
|
+
return { content: [createErrorContent(`ModSecurity audit failed: ${msg}`)], isError: true };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Manage ModSecurity rules.
|
|
242
|
+
*/
|
|
243
|
+
async function handleModsecRules(ruleAction, ruleId, webServer, outputFormat) {
|
|
244
|
+
try {
|
|
245
|
+
const sections = [];
|
|
246
|
+
const jsonData = {};
|
|
247
|
+
sections.push("📋 ModSecurity Rules Management");
|
|
248
|
+
sections.push("=".repeat(55));
|
|
249
|
+
switch (ruleAction) {
|
|
250
|
+
case "list": {
|
|
251
|
+
sections.push("\n── Loaded Rule Files ──");
|
|
252
|
+
const allFiles = [];
|
|
253
|
+
for (const rulesDir of MODSEC_RULES_DIRS) {
|
|
254
|
+
const lsResult = await runSudoCommand("ls", ["-la", rulesDir]);
|
|
255
|
+
if (lsResult.exitCode === 0) {
|
|
256
|
+
sections.push(`\n Directory: ${rulesDir}`);
|
|
257
|
+
const files = lsResult.stdout
|
|
258
|
+
.split("\n")
|
|
259
|
+
.filter((line) => line.includes(".conf"))
|
|
260
|
+
.map((line) => {
|
|
261
|
+
const parts = line.trim().split(/\s+/);
|
|
262
|
+
return parts[parts.length - 1];
|
|
263
|
+
})
|
|
264
|
+
.filter((f) => f && f.endsWith(".conf"));
|
|
265
|
+
for (const file of files) {
|
|
266
|
+
sections.push(` 📄 ${file}`);
|
|
267
|
+
allFiles.push(`${rulesDir}/${file}`);
|
|
268
|
+
}
|
|
269
|
+
if (files.length === 0) {
|
|
270
|
+
sections.push(" (no .conf rule files found)");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
jsonData.rule_files = allFiles;
|
|
275
|
+
jsonData.total_files = allFiles.length;
|
|
276
|
+
if (allFiles.length === 0) {
|
|
277
|
+
sections.push("\n ❌ No rule files found in standard directories");
|
|
278
|
+
sections.push(" Searched:");
|
|
279
|
+
for (const d of MODSEC_RULES_DIRS) {
|
|
280
|
+
sections.push(` - ${d}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
sections.push(`\n Total rule files: ${allFiles.length}`);
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case "enable":
|
|
289
|
+
case "disable": {
|
|
290
|
+
if (!ruleId) {
|
|
291
|
+
return {
|
|
292
|
+
content: [createErrorContent("rule_id is required for enable/disable actions")],
|
|
293
|
+
isError: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
sections.push(`\n Action: ${ruleAction} rule ${ruleId}`);
|
|
297
|
+
jsonData.rule_id = ruleId;
|
|
298
|
+
jsonData.action = ruleAction;
|
|
299
|
+
// Find the active config file
|
|
300
|
+
const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
|
|
301
|
+
let activeConfPath = "";
|
|
302
|
+
let configContent = "";
|
|
303
|
+
for (const confPath of confPaths) {
|
|
304
|
+
const catResult = await runSudoCommand("cat", [confPath]);
|
|
305
|
+
if (catResult.exitCode === 0 && catResult.stdout.trim().length > 0) {
|
|
306
|
+
configContent = catResult.stdout;
|
|
307
|
+
activeConfPath = confPath;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!activeConfPath) {
|
|
312
|
+
return {
|
|
313
|
+
content: [createErrorContent("No ModSecurity configuration file found")],
|
|
314
|
+
isError: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const removeDirective = `SecRuleRemoveById ${ruleId}`;
|
|
318
|
+
const hasRemoveDirective = configContent.includes(removeDirective);
|
|
319
|
+
if (ruleAction === "disable") {
|
|
320
|
+
if (hasRemoveDirective) {
|
|
321
|
+
sections.push(` ℹ️ Rule ${ruleId} is already disabled`);
|
|
322
|
+
jsonData.status = "already_disabled";
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// Append SecRuleRemoveById to config
|
|
326
|
+
const appendResult = await runSudoCommand("sh", [
|
|
327
|
+
"-c",
|
|
328
|
+
`echo '${removeDirective}' >> ${activeConfPath}`,
|
|
329
|
+
]);
|
|
330
|
+
if (appendResult.exitCode === 0) {
|
|
331
|
+
sections.push(` ✅ Rule ${ruleId} disabled (added ${removeDirective})`);
|
|
332
|
+
sections.push(` Config: ${activeConfPath}`);
|
|
333
|
+
sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
|
|
334
|
+
jsonData.status = "disabled";
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
sections.push(` ❌ Failed to disable rule: ${appendResult.stderr}`);
|
|
338
|
+
jsonData.status = "error";
|
|
339
|
+
jsonData.error = appendResult.stderr;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
// enable — remove the SecRuleRemoveById directive
|
|
345
|
+
if (!hasRemoveDirective) {
|
|
346
|
+
sections.push(` ℹ️ Rule ${ruleId} is already enabled (no removal directive found)`);
|
|
347
|
+
jsonData.status = "already_enabled";
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
const sedResult = await runSudoCommand("sed", [
|
|
351
|
+
"-i",
|
|
352
|
+
`/${removeDirective}/d`,
|
|
353
|
+
activeConfPath,
|
|
354
|
+
]);
|
|
355
|
+
if (sedResult.exitCode === 0) {
|
|
356
|
+
sections.push(` ✅ Rule ${ruleId} enabled (removed ${removeDirective})`);
|
|
357
|
+
sections.push(` Config: ${activeConfPath}`);
|
|
358
|
+
sections.push(` ⚠️ Reload ${webServer} to apply: sudo systemctl reload ${webServer}`);
|
|
359
|
+
jsonData.status = "enabled";
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
sections.push(` ❌ Failed to enable rule: ${sedResult.stderr}`);
|
|
363
|
+
jsonData.status = "error";
|
|
364
|
+
jsonData.error = sedResult.stderr;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
default:
|
|
371
|
+
return {
|
|
372
|
+
content: [createErrorContent(`Unknown rule_action: ${ruleAction}. Use list, enable, or disable.`)],
|
|
373
|
+
isError: true,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (outputFormat === "json") {
|
|
377
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
378
|
+
}
|
|
379
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
383
|
+
return { content: [createErrorContent(`ModSecurity rules management failed: ${msg}`)], isError: true };
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Configure rate limiting at the web server level.
|
|
388
|
+
*/
|
|
389
|
+
async function handleRateLimitConfig(webServer, rateLimit, rateLimitZone, outputFormat) {
|
|
390
|
+
try {
|
|
391
|
+
const sections = [];
|
|
392
|
+
const jsonData = {};
|
|
393
|
+
sections.push("⚡ Rate Limiting Configuration");
|
|
394
|
+
sections.push("=".repeat(55));
|
|
395
|
+
sections.push(`Web Server: ${webServer}`);
|
|
396
|
+
jsonData.web_server = webServer;
|
|
397
|
+
if (webServer === "nginx") {
|
|
398
|
+
// Check current nginx rate limiting configuration
|
|
399
|
+
const nginxConf = await runSudoCommand("cat", ["/etc/nginx/nginx.conf"]);
|
|
400
|
+
const sitesResult = await runSudoCommand("ls", ["/etc/nginx/sites-enabled/"]);
|
|
401
|
+
sections.push("\n── Current Rate Limiting (nginx) ──");
|
|
402
|
+
if (nginxConf.exitCode === 0) {
|
|
403
|
+
const content = nginxConf.stdout;
|
|
404
|
+
// Find limit_req_zone directives
|
|
405
|
+
const zoneMatches = content.match(/limit_req_zone\s+[^;]+;/g) || [];
|
|
406
|
+
const reqMatches = content.match(/limit_req\s+[^;]+;/g) || [];
|
|
407
|
+
jsonData.limit_req_zones = zoneMatches.map((z) => z.trim());
|
|
408
|
+
jsonData.limit_req_directives = reqMatches.map((r) => r.trim());
|
|
409
|
+
if (zoneMatches.length > 0) {
|
|
410
|
+
sections.push(" Rate limit zones defined:");
|
|
411
|
+
for (const zone of zoneMatches) {
|
|
412
|
+
sections.push(` 📊 ${zone.trim()}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
sections.push(" ❌ No limit_req_zone directives found");
|
|
417
|
+
}
|
|
418
|
+
if (reqMatches.length > 0) {
|
|
419
|
+
sections.push(" Rate limit enforcement:");
|
|
420
|
+
for (const req of reqMatches) {
|
|
421
|
+
sections.push(` 🔒 ${req.trim()}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
sections.push(" ❌ No limit_req directives found");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
sections.push(" ❌ Could not read /etc/nginx/nginx.conf");
|
|
430
|
+
jsonData.error = "Could not read nginx config";
|
|
431
|
+
}
|
|
432
|
+
// Suggest configuration
|
|
433
|
+
sections.push("\n── Recommended Configuration ──");
|
|
434
|
+
const effectiveRate = rateLimit ?? 10;
|
|
435
|
+
const effectiveZone = rateLimitZone ?? "default";
|
|
436
|
+
sections.push(" Add to http {} block in nginx.conf:");
|
|
437
|
+
sections.push(` limit_req_zone $binary_remote_addr zone=${effectiveZone}:10m rate=${effectiveRate}r/s;`);
|
|
438
|
+
sections.push(" Add to server {} or location {} block:");
|
|
439
|
+
sections.push(` limit_req zone=${effectiveZone} burst=${effectiveRate * 2} nodelay;`);
|
|
440
|
+
sections.push(" limit_req_status 429;");
|
|
441
|
+
jsonData.suggested_rate = effectiveRate;
|
|
442
|
+
jsonData.suggested_zone = effectiveZone;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
// Apache rate limiting
|
|
446
|
+
sections.push("\n── Current Rate Limiting (Apache) ──");
|
|
447
|
+
// Check for mod_ratelimit
|
|
448
|
+
const ratelimitCheck = await runCommand("apache2ctl", ["-M"]);
|
|
449
|
+
let hasRatelimit = false;
|
|
450
|
+
let hasEvasive = false;
|
|
451
|
+
if (ratelimitCheck.exitCode === 0) {
|
|
452
|
+
hasRatelimit = ratelimitCheck.stdout.includes("ratelimit_module");
|
|
453
|
+
hasEvasive = ratelimitCheck.stdout.includes("evasive");
|
|
454
|
+
}
|
|
455
|
+
jsonData.mod_ratelimit = hasRatelimit;
|
|
456
|
+
jsonData.mod_evasive = hasEvasive;
|
|
457
|
+
sections.push(` mod_ratelimit: ${hasRatelimit ? "✅ loaded" : "❌ not loaded"}`);
|
|
458
|
+
sections.push(` mod_evasive: ${hasEvasive ? "✅ loaded" : "❌ not loaded"}`);
|
|
459
|
+
if (hasEvasive) {
|
|
460
|
+
const evasiveConf = await runSudoCommand("cat", ["/etc/apache2/mods-enabled/evasive.conf"]);
|
|
461
|
+
if (evasiveConf.exitCode === 0) {
|
|
462
|
+
sections.push("\n mod_evasive configuration:");
|
|
463
|
+
for (const line of evasiveConf.stdout.split("\n")) {
|
|
464
|
+
const trimmed = line.trim();
|
|
465
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
466
|
+
sections.push(` ${trimmed}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
sections.push("\n── Recommended Configuration ──");
|
|
472
|
+
const effectiveRate = rateLimit ?? 10;
|
|
473
|
+
if (!hasEvasive) {
|
|
474
|
+
sections.push(" Install mod_evasive:");
|
|
475
|
+
sections.push(" sudo apt install libapache2-mod-evasive");
|
|
476
|
+
sections.push(" sudo a2enmod evasive");
|
|
477
|
+
}
|
|
478
|
+
sections.push(" Recommended /etc/apache2/mods-enabled/evasive.conf:");
|
|
479
|
+
sections.push(" <IfModule mod_evasive20.c>");
|
|
480
|
+
sections.push(` DOSPageCount ${effectiveRate}`);
|
|
481
|
+
sections.push(` DOSSiteCount ${effectiveRate * 5}`);
|
|
482
|
+
sections.push(" DOSPageInterval 1");
|
|
483
|
+
sections.push(" DOSSiteInterval 1");
|
|
484
|
+
sections.push(" DOSBlockingPeriod 60");
|
|
485
|
+
sections.push(" </IfModule>");
|
|
486
|
+
jsonData.suggested_rate = effectiveRate;
|
|
487
|
+
}
|
|
488
|
+
if (outputFormat === "json") {
|
|
489
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
490
|
+
}
|
|
491
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
492
|
+
}
|
|
493
|
+
catch (err) {
|
|
494
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
495
|
+
return { content: [createErrorContent(`Rate limit configuration failed: ${msg}`)], isError: true };
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Check OWASP Core Rule Set deployment status.
|
|
500
|
+
*/
|
|
501
|
+
async function handleOwaspCrsDeploy(webServer, outputFormat) {
|
|
502
|
+
try {
|
|
503
|
+
const sections = [];
|
|
504
|
+
const jsonData = {};
|
|
505
|
+
sections.push("🌐 OWASP Core Rule Set (CRS) Status");
|
|
506
|
+
sections.push("=".repeat(55));
|
|
507
|
+
// Check if CRS is installed
|
|
508
|
+
let crsPath = "";
|
|
509
|
+
for (const path of OWASP_CRS_PATHS) {
|
|
510
|
+
const testResult = await runCommand("test", ["-d", path]);
|
|
511
|
+
if (testResult.exitCode === 0) {
|
|
512
|
+
crsPath = path;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
jsonData.installed = !!crsPath;
|
|
517
|
+
jsonData.crs_path = crsPath || null;
|
|
518
|
+
if (!crsPath) {
|
|
519
|
+
sections.push("\n ❌ OWASP CRS is NOT installed");
|
|
520
|
+
sections.push("\n── Installation Instructions ──");
|
|
521
|
+
sections.push(" Option 1 — Package manager:");
|
|
522
|
+
sections.push(" sudo apt install modsecurity-crs");
|
|
523
|
+
sections.push(" Option 2 — Git (latest version):");
|
|
524
|
+
sections.push(" sudo git clone https://github.com/coreruleset/coreruleset.git /usr/share/modsecurity-crs");
|
|
525
|
+
sections.push(" sudo cp /usr/share/modsecurity-crs/crs-setup.conf.example /usr/share/modsecurity-crs/crs-setup.conf");
|
|
526
|
+
sections.push("\n After installation, add to ModSecurity config:");
|
|
527
|
+
sections.push(" Include /usr/share/modsecurity-crs/crs-setup.conf");
|
|
528
|
+
sections.push(" Include /usr/share/modsecurity-crs/rules/*.conf");
|
|
529
|
+
if (outputFormat === "json") {
|
|
530
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
531
|
+
}
|
|
532
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
533
|
+
}
|
|
534
|
+
sections.push(`\n ✅ CRS found at: ${crsPath}`);
|
|
535
|
+
// Check version
|
|
536
|
+
const changelogResult = await runCommand("head", ["-5", `${crsPath}/CHANGES`]);
|
|
537
|
+
const versionFileResult = await runCommand("cat", [`${crsPath}/VERSION`]);
|
|
538
|
+
let version = "unknown";
|
|
539
|
+
if (versionFileResult.exitCode === 0 && versionFileResult.stdout.trim()) {
|
|
540
|
+
version = versionFileResult.stdout.trim();
|
|
541
|
+
}
|
|
542
|
+
else if (changelogResult.exitCode === 0) {
|
|
543
|
+
const versionMatch = changelogResult.stdout.match(/(\d+\.\d+\.\d+)/);
|
|
544
|
+
if (versionMatch)
|
|
545
|
+
version = versionMatch[1];
|
|
546
|
+
}
|
|
547
|
+
jsonData.version = version;
|
|
548
|
+
sections.push(` Version: ${version}`);
|
|
549
|
+
// Check CRS setup file for paranoia level
|
|
550
|
+
const setupConf = await runSudoCommand("cat", [`${crsPath}/crs-setup.conf`]);
|
|
551
|
+
let paranoiaLevel = "1 (default)";
|
|
552
|
+
if (setupConf.exitCode === 0) {
|
|
553
|
+
const plMatch = setupConf.stdout.match(/^\s*SecAction\s.*setvar:'tx\.paranoia_level=(\d+)'/m);
|
|
554
|
+
if (plMatch) {
|
|
555
|
+
paranoiaLevel = plMatch[1];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
jsonData.paranoia_level = paranoiaLevel;
|
|
559
|
+
sections.push(` Paranoia Level: ${paranoiaLevel}`);
|
|
560
|
+
// Check integration with ModSecurity
|
|
561
|
+
sections.push("\n── Integration Check ──");
|
|
562
|
+
const confPaths = MODSEC_CONF_PATHS[webServer] || MODSEC_CONF_PATHS.nginx;
|
|
563
|
+
let integrated = false;
|
|
564
|
+
for (const confPath of confPaths) {
|
|
565
|
+
const confContent = await runSudoCommand("cat", [confPath]);
|
|
566
|
+
if (confContent.exitCode === 0) {
|
|
567
|
+
if (confContent.stdout.includes("modsecurity-crs") || confContent.stdout.includes("crs-setup")) {
|
|
568
|
+
integrated = true;
|
|
569
|
+
sections.push(` ✅ CRS Include directives found in ${confPath}`);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Also check for include in main nginx/apache conf
|
|
575
|
+
if (!integrated) {
|
|
576
|
+
const mainConf = webServer === "nginx"
|
|
577
|
+
? await runSudoCommand("cat", ["/etc/nginx/nginx.conf"])
|
|
578
|
+
: await runSudoCommand("cat", ["/etc/apache2/apache2.conf"]);
|
|
579
|
+
if (mainConf.exitCode === 0 && (mainConf.stdout.includes("modsecurity-crs") || mainConf.stdout.includes("crs-setup"))) {
|
|
580
|
+
integrated = true;
|
|
581
|
+
sections.push(" ✅ CRS Include directives found in main config");
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
jsonData.integrated = integrated;
|
|
585
|
+
if (!integrated) {
|
|
586
|
+
sections.push(" ❌ CRS Include directives NOT found in ModSecurity configuration");
|
|
587
|
+
sections.push(" Add these lines to your ModSecurity configuration:");
|
|
588
|
+
sections.push(` Include ${crsPath}/crs-setup.conf`);
|
|
589
|
+
sections.push(` Include ${crsPath}/rules/*.conf`);
|
|
590
|
+
}
|
|
591
|
+
// List active rule categories
|
|
592
|
+
sections.push("\n── Active Rule Categories ──");
|
|
593
|
+
const rulesDir = `${crsPath}/rules`;
|
|
594
|
+
const rulesList = await runCommand("ls", [rulesDir]);
|
|
595
|
+
const categories = [];
|
|
596
|
+
if (rulesList.exitCode === 0) {
|
|
597
|
+
const ruleFiles = rulesList.stdout
|
|
598
|
+
.split("\n")
|
|
599
|
+
.filter((f) => f.endsWith(".conf"))
|
|
600
|
+
.sort();
|
|
601
|
+
for (const file of ruleFiles) {
|
|
602
|
+
categories.push(file);
|
|
603
|
+
sections.push(` 📄 ${file}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
jsonData.rule_categories = categories;
|
|
607
|
+
jsonData.total_categories = categories.length;
|
|
608
|
+
sections.push(`\n Total rule files: ${categories.length}`);
|
|
609
|
+
if (outputFormat === "json") {
|
|
610
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
611
|
+
}
|
|
612
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
613
|
+
}
|
|
614
|
+
catch (err) {
|
|
615
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
616
|
+
return { content: [createErrorContent(`OWASP CRS check failed: ${msg}`)], isError: true };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Analyze WAF logs for blocked requests.
|
|
621
|
+
*/
|
|
622
|
+
async function handleBlockedRequests(logPath, outputFormat) {
|
|
623
|
+
try {
|
|
624
|
+
const sections = [];
|
|
625
|
+
const jsonData = {};
|
|
626
|
+
sections.push("🚫 WAF Blocked Requests Analysis");
|
|
627
|
+
sections.push("=".repeat(55));
|
|
628
|
+
// Find the log file
|
|
629
|
+
let activeLogPath = logPath;
|
|
630
|
+
if (!activeLogPath) {
|
|
631
|
+
for (const path of MODSEC_AUDIT_LOG_PATHS) {
|
|
632
|
+
const testResult = await runCommand("test", ["-f", path]);
|
|
633
|
+
if (testResult.exitCode === 0) {
|
|
634
|
+
activeLogPath = path;
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (!activeLogPath) {
|
|
640
|
+
sections.push("\n ❌ No ModSecurity audit log found");
|
|
641
|
+
sections.push(" Searched paths:");
|
|
642
|
+
for (const p of MODSEC_AUDIT_LOG_PATHS) {
|
|
643
|
+
sections.push(` - ${p}`);
|
|
644
|
+
}
|
|
645
|
+
sections.push("\n Ensure SecAuditLog is configured in modsecurity.conf");
|
|
646
|
+
jsonData.log_found = false;
|
|
647
|
+
if (outputFormat === "json") {
|
|
648
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
649
|
+
}
|
|
650
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
651
|
+
}
|
|
652
|
+
jsonData.log_path = activeLogPath;
|
|
653
|
+
jsonData.log_found = true;
|
|
654
|
+
sections.push(` Log file: ${activeLogPath}`);
|
|
655
|
+
// Get log file size
|
|
656
|
+
const statResult = await runCommand("stat", ["--format=%s", activeLogPath]);
|
|
657
|
+
if (statResult.exitCode === 0) {
|
|
658
|
+
const sizeBytes = parseInt(statResult.stdout.trim(), 10);
|
|
659
|
+
const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
|
|
660
|
+
sections.push(` Log size: ${sizeMB} MB`);
|
|
661
|
+
jsonData.log_size_bytes = sizeBytes;
|
|
662
|
+
}
|
|
663
|
+
// Analyze blocked IPs (top 10)
|
|
664
|
+
sections.push("\n── Top Blocked IPs ──");
|
|
665
|
+
const ipResult = await runSudoCommand("grep", ["-oP", "\\d+\\.\\d+\\.\\d+\\.\\d+", activeLogPath]);
|
|
666
|
+
if (ipResult.exitCode === 0 && ipResult.stdout.trim()) {
|
|
667
|
+
// Count IP occurrences manually
|
|
668
|
+
const ipCounts = {};
|
|
669
|
+
for (const ip of ipResult.stdout.trim().split("\n")) {
|
|
670
|
+
const trimmedIp = ip.trim();
|
|
671
|
+
if (trimmedIp) {
|
|
672
|
+
ipCounts[trimmedIp] = (ipCounts[trimmedIp] || 0) + 1;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const sortedIps = Object.entries(ipCounts)
|
|
676
|
+
.sort((a, b) => b[1] - a[1])
|
|
677
|
+
.slice(0, 10);
|
|
678
|
+
jsonData.top_blocked_ips = sortedIps.map(([ip, count]) => ({ ip, count }));
|
|
679
|
+
for (const [ip, count] of sortedIps) {
|
|
680
|
+
sections.push(` ${String(count).padStart(6)} │ ${ip}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
sections.push(" No blocked IPs found");
|
|
685
|
+
jsonData.top_blocked_ips = [];
|
|
686
|
+
}
|
|
687
|
+
// Analyze triggered rules (top 10)
|
|
688
|
+
sections.push("\n── Top Triggered Rules ──");
|
|
689
|
+
const ruleResult = await runSudoCommand("grep", ["-oP", 'id "\\d+"', activeLogPath]);
|
|
690
|
+
if (ruleResult.exitCode === 0 && ruleResult.stdout.trim()) {
|
|
691
|
+
const ruleCounts = {};
|
|
692
|
+
for (const match of ruleResult.stdout.trim().split("\n")) {
|
|
693
|
+
const ruleIdMatch = match.match(/\d+/);
|
|
694
|
+
if (ruleIdMatch) {
|
|
695
|
+
const rid = ruleIdMatch[0];
|
|
696
|
+
ruleCounts[rid] = (ruleCounts[rid] || 0) + 1;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const sortedRules = Object.entries(ruleCounts)
|
|
700
|
+
.sort((a, b) => b[1] - a[1])
|
|
701
|
+
.slice(0, 10);
|
|
702
|
+
jsonData.top_triggered_rules = sortedRules.map(([ruleId, count]) => ({ rule_id: ruleId, count }));
|
|
703
|
+
for (const [ruleId, count] of sortedRules) {
|
|
704
|
+
sections.push(` ${String(count).padStart(6)} │ Rule ${ruleId}`);
|
|
705
|
+
}
|
|
706
|
+
// Identify false positive candidates (rules triggered > 100 times)
|
|
707
|
+
sections.push("\n── Possible False Positives ──");
|
|
708
|
+
const fpCandidates = sortedRules.filter(([, count]) => count > 100);
|
|
709
|
+
jsonData.false_positive_candidates = fpCandidates.map(([ruleId, count]) => ({ rule_id: ruleId, count }));
|
|
710
|
+
if (fpCandidates.length > 0) {
|
|
711
|
+
sections.push(" Rules triggered excessively (may be false positives):");
|
|
712
|
+
for (const [ruleId, count] of fpCandidates) {
|
|
713
|
+
sections.push(` ⚠️ Rule ${ruleId}: ${count} hits — review for tuning`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
sections.push(" No obvious false positive candidates detected");
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
sections.push(" No triggered rules found");
|
|
722
|
+
jsonData.top_triggered_rules = [];
|
|
723
|
+
jsonData.false_positive_candidates = [];
|
|
724
|
+
}
|
|
725
|
+
// Attack categories
|
|
726
|
+
sections.push("\n── Attack Categories ──");
|
|
727
|
+
const categories = {};
|
|
728
|
+
const categoryPatterns = {
|
|
729
|
+
"SQL Injection": "sql|sqli|SQL",
|
|
730
|
+
"XSS": "xss|cross-site scripting",
|
|
731
|
+
"Path Traversal": "traversal|path-traversal|directory",
|
|
732
|
+
"Remote File Inclusion": "rfi|remote file",
|
|
733
|
+
"Local File Inclusion": "lfi|local file",
|
|
734
|
+
"Command Injection": "command injection|cmd|rce",
|
|
735
|
+
"Protocol Violation": "protocol|violation|request-",
|
|
736
|
+
"Scanner Detection": "scanner|nikto|nmap|acunetix",
|
|
737
|
+
};
|
|
738
|
+
for (const [category, pattern] of Object.entries(categoryPatterns)) {
|
|
739
|
+
const grepResult = await runSudoCommand("grep", ["-ciP", pattern, activeLogPath]);
|
|
740
|
+
if (grepResult.exitCode === 0) {
|
|
741
|
+
const count = parseInt(grepResult.stdout.trim(), 10);
|
|
742
|
+
if (count > 0) {
|
|
743
|
+
categories[category] = count;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
jsonData.attack_categories = categories;
|
|
748
|
+
const sortedCategories = Object.entries(categories).sort((a, b) => b[1] - a[1]);
|
|
749
|
+
if (sortedCategories.length > 0) {
|
|
750
|
+
for (const [category, count] of sortedCategories) {
|
|
751
|
+
sections.push(` ${String(count).padStart(6)} │ ${category}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
sections.push(" No categorized attacks found");
|
|
756
|
+
}
|
|
757
|
+
// Timeline (recent blocks count by hour — last 24h)
|
|
758
|
+
sections.push("\n── Recent Activity (last 24 lines) ──");
|
|
759
|
+
const tailResult = await runSudoCommand("tail", ["-24", activeLogPath]);
|
|
760
|
+
if (tailResult.exitCode === 0 && tailResult.stdout.trim()) {
|
|
761
|
+
const recentLines = tailResult.stdout.trim().split("\n").slice(0, 10);
|
|
762
|
+
for (const line of recentLines) {
|
|
763
|
+
sections.push(` ${line.substring(0, 120)}`);
|
|
764
|
+
}
|
|
765
|
+
if (tailResult.stdout.trim().split("\n").length > 10) {
|
|
766
|
+
sections.push(" ...");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
sections.push(" No recent entries");
|
|
771
|
+
}
|
|
772
|
+
if (outputFormat === "json") {
|
|
773
|
+
return { content: [formatToolOutput(jsonData)] };
|
|
774
|
+
}
|
|
775
|
+
return { content: [createTextContent(sections.join("\n"))] };
|
|
776
|
+
}
|
|
777
|
+
catch (err) {
|
|
778
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
779
|
+
return { content: [createErrorContent(`Blocked requests analysis failed: ${msg}`)], isError: true };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// ── Registration entry point ───────────────────────────────────────────────
|
|
783
|
+
export function registerWafTools(server) {
|
|
784
|
+
server.tool("waf_manage", "Web Application Firewall management: audit ModSecurity, manage rules, configure rate limiting, deploy OWASP CRS, analyze blocked requests.", {
|
|
785
|
+
action: z
|
|
786
|
+
.enum(["modsec_audit", "modsec_rules", "rate_limit_config", "owasp_crs_deploy", "blocked_requests"])
|
|
787
|
+
.describe("Action: modsec_audit=audit WAF config, modsec_rules=manage rules, rate_limit_config=rate limiting, owasp_crs_deploy=CRS status, blocked_requests=log analysis"),
|
|
788
|
+
web_server: z
|
|
789
|
+
.enum(["nginx", "apache"])
|
|
790
|
+
.optional()
|
|
791
|
+
.default("nginx")
|
|
792
|
+
.describe("Web server type (default: nginx)"),
|
|
793
|
+
rule_id: z
|
|
794
|
+
.string()
|
|
795
|
+
.optional()
|
|
796
|
+
.describe("ModSecurity rule ID (used with modsec_rules action)"),
|
|
797
|
+
rule_action: z
|
|
798
|
+
.enum(["enable", "disable", "list"])
|
|
799
|
+
.optional()
|
|
800
|
+
.default("list")
|
|
801
|
+
.describe("Rule action: enable, disable, or list (used with modsec_rules)"),
|
|
802
|
+
rate_limit: z
|
|
803
|
+
.number()
|
|
804
|
+
.optional()
|
|
805
|
+
.describe("Requests per second for rate limiting (used with rate_limit_config)"),
|
|
806
|
+
rate_limit_zone: z
|
|
807
|
+
.string()
|
|
808
|
+
.optional()
|
|
809
|
+
.describe("Zone name for rate limiting (used with rate_limit_config)"),
|
|
810
|
+
log_path: z
|
|
811
|
+
.string()
|
|
812
|
+
.optional()
|
|
813
|
+
.describe("Path to WAF log file (used with blocked_requests)"),
|
|
814
|
+
output_format: z
|
|
815
|
+
.enum(["text", "json"])
|
|
816
|
+
.optional()
|
|
817
|
+
.default("text")
|
|
818
|
+
.describe("Output format: text or json (default: text)"),
|
|
819
|
+
}, async (params) => {
|
|
820
|
+
const { action } = params;
|
|
821
|
+
const webServer = params.web_server ?? "nginx";
|
|
822
|
+
const outputFormat = params.output_format ?? "text";
|
|
823
|
+
switch (action) {
|
|
824
|
+
case "modsec_audit":
|
|
825
|
+
return handleModsecAudit(webServer, outputFormat);
|
|
826
|
+
case "modsec_rules": {
|
|
827
|
+
const ruleAction = params.rule_action ?? "list";
|
|
828
|
+
return handleModsecRules(ruleAction, params.rule_id, webServer, outputFormat);
|
|
829
|
+
}
|
|
830
|
+
case "rate_limit_config":
|
|
831
|
+
return handleRateLimitConfig(webServer, params.rate_limit, params.rate_limit_zone, outputFormat);
|
|
832
|
+
case "owasp_crs_deploy":
|
|
833
|
+
return handleOwaspCrsDeploy(webServer, outputFormat);
|
|
834
|
+
case "blocked_requests":
|
|
835
|
+
return handleBlockedRequests(params.log_path, outputFormat);
|
|
836
|
+
default:
|
|
837
|
+
return {
|
|
838
|
+
content: [createErrorContent(`Unknown action: ${action}`)],
|
|
839
|
+
isError: true,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
}
|