/**
* Web security tools
*/
import {
GobusterDirInput,
GobusterDnsInput,
SQLMapInput,
NiktoInput,
WPScanInput,
FfufInput,
NucleiInput,
} from "../schemas/web.schemas.js";
import { ToolResult } from "../types.js";
import { executeCommand } from "../utils/executor.js";
import { formatExecutionResult, formatErrorMessage } from "../utils/formatter.js";
import { validateURL, validateDomain, validateWordlistPath } from "../utils/validator.js";
import { TOOL_PATHS } from "../constants.js";
/**
* Gobuster directory enumeration
*/
export async function gobusterDir(input: GobusterDirInput): Promise<ToolResult> {
if (!validateURL(input.url)) {
return formatErrorMessage("Invalid URL", `URL '${input.url}' is not valid`);
}
if (!validateWordlistPath(input.wordlist)) {
return formatErrorMessage(
"Invalid wordlist path",
`Wordlist '${input.wordlist}' is not in an allowed directory`
);
}
const args: string[] = ["dir", "-u", input.url, "-w", input.wordlist];
if (input.extensions) {
args.push("-x", input.extensions);
}
args.push("-t", String(input.threads));
args.push("-b", input.status_codes);
if (input.user_agent) {
args.push("-a", input.user_agent);
}
if (input.follow_redirect) {
args.push("-r");
}
const result = await executeCommand(TOOL_PATHS.gobuster || "gobuster", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* Gobuster DNS enumeration
*/
export async function gobusterDns(input: GobusterDnsInput): Promise<ToolResult> {
if (!validateDomain(input.domain)) {
return formatErrorMessage("Invalid domain", `Domain '${input.domain}' is not valid`);
}
if (!validateWordlistPath(input.wordlist)) {
return formatErrorMessage(
"Invalid wordlist path",
`Wordlist '${input.wordlist}' is not in an allowed directory`
);
}
const args: string[] = ["dns", "-d", input.domain, "-w", input.wordlist];
args.push("-t", String(input.threads));
if (input.resolver) {
args.push("-r", input.resolver);
}
const result = await executeCommand(TOOL_PATHS.gobuster || "gobuster", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* SQLMap SQL injection testing
*/
export async function sqlmap(input: SQLMapInput): Promise<ToolResult> {
if (!validateURL(input.url)) {
return formatErrorMessage("Invalid URL", `URL '${input.url}' is not valid`);
}
const args: string[] = ["-u", input.url];
if (input.data) {
args.push("--data", input.data);
}
if (input.cookie) {
args.push("--cookie", input.cookie);
}
if (input.method === "POST") {
args.push("--method=POST");
}
if (input.parameter) {
args.push("-p", input.parameter);
}
args.push("--level", String(input.level));
args.push("--risk", String(input.risk));
if (input.dbms) {
args.push("--dbms", input.dbms);
}
if (input.technique) {
args.push("--technique", input.technique);
}
if (input.batch) {
args.push("--batch");
}
// Disable some prompts
args.push("--no-cast");
const result = await executeCommand(TOOL_PATHS.sqlmap || "sqlmap", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* Nikto web scanner
*/
export async function nikto(input: NiktoInput): Promise<ToolResult> {
const args: string[] = ["-h", input.target];
args.push("-p", String(input.port));
if (input.ssl) {
args.push("-ssl");
}
if (input.tuning) {
args.push("-Tuning", input.tuning);
}
const result = await executeCommand(TOOL_PATHS.nikto || "nikto", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* WPScan WordPress scanner
*/
export async function wpscan(input: WPScanInput): Promise<ToolResult> {
if (!validateURL(input.url)) {
return formatErrorMessage("Invalid URL", `URL '${input.url}' is not valid`);
}
const args: string[] = ["--url", input.url];
args.push("--enumerate", input.enumerate);
if (input.api_token) {
args.push("--api-token", input.api_token);
}
args.push("--plugins-detection", input.plugins_detection);
const result = await executeCommand(TOOL_PATHS.wpscan || "wpscan", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* ffuf web fuzzer
*/
export async function ffuf(input: FfufInput): Promise<ToolResult> {
if (!input.url.includes("FUZZ")) {
return formatErrorMessage(
"Invalid URL",
"URL must contain the FUZZ keyword (e.g., 'https://example.com/FUZZ')"
);
}
if (!validateWordlistPath(input.wordlist)) {
return formatErrorMessage(
"Invalid wordlist path",
`Wordlist '${input.wordlist}' is not in an allowed directory`
);
}
const args: string[] = ["-u", input.url, "-w", input.wordlist];
args.push("-X", input.method);
if (input.headers) {
for (const [key, value] of Object.entries(input.headers)) {
args.push("-H", `${key}: ${value}`);
}
}
if (input.data) {
args.push("-d", input.data);
}
args.push("-mc", input.match_codes);
if (input.filter_codes) {
args.push("-fc", input.filter_codes);
}
args.push("-t", String(input.threads));
const result = await executeCommand(TOOL_PATHS.ffuf || "ffuf", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}
/**
* Nuclei vulnerability scanner
*/
export async function nuclei(input: NucleiInput): Promise<ToolResult> {
const args: string[] = ["-u", input.target];
if (input.templates) {
args.push("-t", input.templates);
}
if (input.severity) {
args.push("-s", input.severity);
}
if (input.tags && input.tags.length > 0) {
args.push("-tags", input.tags.join(","));
}
args.push("-c", String(input.threads));
const result = await executeCommand(TOOL_PATHS.nuclei || "nuclei", args, {
timeout: input.timeout * 1000,
});
return formatExecutionResult(result);
}