Skip to main content
Glama

MCP Pentest

fuzzing.ts•27.2 kB
import { exec } from 'child_process'; import { promisify } from 'util'; import { ScanResult } from './recon.js'; import { ExtractedParameter } from './parameter-extraction.js'; const execAsync = promisify(exec); export interface FuzzingResult { parameter: string; payload: string; url: string; method: string; response_code: number; response_size: number; response_time: number; vulnerability_detected: boolean; vulnerability_type?: string; evidence?: string; severity: 'info' | 'low' | 'medium' | 'high' | 'critical'; } export interface FuzzingConfiguration { tool: 'ffuf' | 'wfuzz'; threads: number; timeout: number; delay: number; wordlist: string; extensions?: string[]; filter_codes?: number[]; filter_size?: number[]; match_codes?: number[]; custom_headers?: Record<string, string>; } export class FuzzingEngine { async fuzzParameters(parameters: ExtractedParameter[], config: Partial<FuzzingConfiguration> = {}): Promise<ScanResult> { try { const defaultConfig: FuzzingConfiguration = { tool: 'ffuf', threads: 10, timeout: 10, delay: 100, wordlist: this.getDefaultWordlist('parameters'), filter_codes: [404, 403], ...config }; console.error(`šŸ” Fuzzing ${parameters.length} parameters with ${defaultConfig.tool}`); const allResults: FuzzingResult[] = []; // Group parameters by URL and method for efficient fuzzing const paramGroups = this.groupParametersByEndpoint(parameters); for (const [endpoint, params] of paramGroups.entries()) { console.error(` Fuzzing endpoint: ${endpoint}`); if (defaultConfig.tool === 'ffuf') { const ffufResults = await this.runFFUF(endpoint, params, defaultConfig); allResults.push(...ffufResults); } else if (defaultConfig.tool === 'wfuzz') { const wfuzzResults = await this.runWfuzz(endpoint, params, defaultConfig); allResults.push(...wfuzzResults); } // Rate limiting between endpoints await this.sleep(defaultConfig.delay); } // Analyze results for vulnerabilities const analyzedResults = this.analyzeResults(allResults); const vulnerabilities = analyzedResults.filter(r => r.vulnerability_detected); return { target: 'parameter_fuzzing', timestamp: new Date().toISOString(), tool: 'fuzzing_engine', results: { total_tests: allResults.length, vulnerabilities_found: vulnerabilities.length, critical_findings: vulnerabilities.filter(v => v.severity === 'critical').length, high_findings: vulnerabilities.filter(v => v.severity === 'high').length, fuzzing_results: allResults, vulnerability_summary: this.summarizeVulnerabilities(vulnerabilities), recommendations: this.generateRecommendations(vulnerabilities) }, status: 'success' }; } catch (error) { return { target: 'parameter_fuzzing', timestamp: new Date().toISOString(), tool: 'fuzzing_engine', results: {}, status: 'error', error: error instanceof Error ? error.message : String(error) }; } } async fuzzDirectories(baseUrl: string, config: Partial<FuzzingConfiguration> = {}): Promise<ScanResult> { try { const defaultConfig: FuzzingConfiguration = { tool: 'ffuf', threads: 20, timeout: 10, delay: 50, wordlist: this.getDefaultWordlist('directories'), extensions: ['php', 'asp', 'aspx', 'jsp', 'html', 'txt', 'xml', 'json'], filter_codes: [404], ...config }; console.error(`šŸ” Directory fuzzing on ${baseUrl}`); let results: FuzzingResult[] = []; if (defaultConfig.tool === 'ffuf') { results = await this.runFFUFDirectories(baseUrl, defaultConfig); } else if (defaultConfig.tool === 'wfuzz') { results = await this.runWfuzzDirectories(baseUrl, defaultConfig); } const analyzedResults = this.analyzeDirectoryResults(results); const interestingFindings = analyzedResults.filter(r => r.vulnerability_detected || r.response_code === 200); return { target: baseUrl, timestamp: new Date().toISOString(), tool: 'directory_fuzzing', results: { total_requests: results.length, found_directories: interestingFindings.length, accessible_paths: interestingFindings.filter(r => r.response_code === 200).length, sensitive_files: interestingFindings.filter(r => this.isSensitiveFile(r.url)).length, fuzzing_results: results, interesting_findings: interestingFindings }, status: 'success' }; } catch (error) { return { target: baseUrl, timestamp: new Date().toISOString(), tool: 'directory_fuzzing', results: {}, status: 'error', error: error instanceof Error ? error.message : String(error) }; } } async customPayloadFuzzing(target: string, parameter: string, payloads: string[], method: string = 'GET'): Promise<ScanResult> { try { console.error(`šŸ” Custom payload fuzzing: ${parameter} with ${payloads.length} payloads`); const results: FuzzingResult[] = []; for (const payload of payloads) { try { const result = await this.testSinglePayload(target, parameter, payload, method); results.push(result); // Rate limiting await this.sleep(100); } catch (error) { console.error(`Error testing payload ${payload}:`, error); } } const vulnerabilities = results.filter(r => r.vulnerability_detected); return { target, timestamp: new Date().toISOString(), tool: 'custom_payload_fuzzing', results: { parameter_tested: parameter, total_payloads: payloads.length, vulnerabilities_found: vulnerabilities.length, test_results: results, vulnerability_types: [...new Set(vulnerabilities.map(v => v.vulnerability_type).filter(Boolean))], severity_breakdown: this.groupBySeverity(vulnerabilities) }, status: 'success' }; } catch (error) { return { target, timestamp: new Date().toISOString(), tool: 'custom_payload_fuzzing', results: {}, status: 'error', error: error instanceof Error ? error.message : String(error) }; } } private async runFFUF(endpoint: string, parameters: ExtractedParameter[], config: FuzzingConfiguration): Promise<FuzzingResult[]> { try { // Check if ffuf is installed try { await execAsync('ffuf -h', { timeout: 5000 }); } catch { console.warn('ffuf not found, skipping ffuf fuzzing'); return []; } const results: FuzzingResult[] = []; for (const param of parameters.slice(0, 5)) { // Limit to 5 params per endpoint const payloads = this.getPayloadsForParameter(param); for (const payload of payloads.slice(0, 20)) { // Limit payloads try { const testUrl = this.buildTestUrl(endpoint, param, payload); const command = `ffuf -u "${testUrl}" -w ${config.wordlist} -t ${config.threads} -timeout ${config.timeout} -p ${config.delay / 1000} -o /dev/null -s`; const { stdout } = await execAsync(command, { timeout: 30000, maxBuffer: 1024 * 1024 }); // Parse ffuf output (simplified) const lines = stdout.split('\n').filter(line => line.trim()); for (const line of lines) { if (line.includes('Status:') && line.includes('Size:')) { const statusMatch = line.match(/Status:\s*(\d+)/); const sizeMatch = line.match(/Size:\s*(\d+)/); const timeMatch = line.match(/Time:\s*(\d+)/); if (statusMatch && sizeMatch) { const result: FuzzingResult = { parameter: param.name, payload, url: testUrl, method: param.method, response_code: parseInt(statusMatch[1]), response_size: parseInt(sizeMatch[1]), response_time: timeMatch ? parseInt(timeMatch[1]) : 0, vulnerability_detected: false, severity: 'info' }; // Analyze for vulnerabilities this.analyzeResponseForVulnerabilities(result, param); results.push(result); } } } } catch (error) { console.error(`FFuf error for ${param.name}:`, error); } } } return results; } catch (error) { console.error('FFuf execution error:', error); return []; } } private async runWfuzz(endpoint: string, parameters: ExtractedParameter[], config: FuzzingConfiguration): Promise<FuzzingResult[]> { try { // Check if wfuzz is installed try { await execAsync('wfuzz --help', { timeout: 5000 }); } catch { console.warn('wfuzz not found, skipping wfuzz fuzzing'); return []; } const results: FuzzingResult[] = []; for (const param of parameters.slice(0, 5)) { // Limit parameters const payloads = this.getPayloadsForParameter(param); // Create temporary payload file const fs = require('fs').promises; const tempFile = `/tmp/wfuzz_payloads_${Date.now()}.txt`; await fs.writeFile(tempFile, payloads.slice(0, 20).join('\n')); try { const testUrl = this.buildTestUrl(endpoint, param, 'FUZZ'); const command = `wfuzz -c -z file,${tempFile} --hc 404,403 -t ${config.threads} "${testUrl}"`; const { stdout } = await execAsync(command, { timeout: 60000, maxBuffer: 1024 * 1024 }); // Parse wfuzz output const lines = stdout.split('\n').filter(line => line.includes('C=')); for (const line of lines) { const match = line.match(/(\d+)\s+C=(\d+)\s+L=(\d+)\s+W=(\d+)\s+Ch=(\d+)\s+"([^"]+)"/); if (match) { const result: FuzzingResult = { parameter: param.name, payload: match[6], url: testUrl.replace('FUZZ', match[6]), method: param.method, response_code: parseInt(match[2]), response_size: parseInt(match[5]), response_time: 0, vulnerability_detected: false, severity: 'info' }; this.analyzeResponseForVulnerabilities(result, param); results.push(result); } } } finally { // Clean up temp file try { await fs.unlink(tempFile); } catch { // Ignore cleanup errors } } } return results; } catch (error) { console.error('Wfuzz execution error:', error); return []; } } private async runFFUFDirectories(baseUrl: string, config: FuzzingConfiguration): Promise<FuzzingResult[]> { try { const results: FuzzingResult[] = []; // Directory fuzzing const dirCommand = `ffuf -u ${baseUrl}/FUZZ -w ${config.wordlist} -t ${config.threads} -timeout ${config.timeout} -fc ${config.filter_codes?.join(',')} -o /dev/null -s`; const { stdout: dirOutput } = await execAsync(dirCommand, { timeout: 120000, maxBuffer: 1024 * 1024 * 5 }); // Parse directory results this.parseFFUFOutput(dirOutput, baseUrl, results); // File extension fuzzing if (config.extensions) { for (const ext of config.extensions) { const extCommand = `ffuf -u ${baseUrl}/FUZZ.${ext} -w ${config.wordlist} -t ${config.threads} -timeout ${config.timeout} -fc ${config.filter_codes?.join(',')} -o /dev/null -s`; try { const { stdout: extOutput } = await execAsync(extCommand, { timeout: 60000, maxBuffer: 1024 * 1024 }); this.parseFFUFOutput(extOutput, baseUrl, results, ext); } catch { // Continue with other extensions } } } return results; } catch (error) { console.error('FFuf directory fuzzing error:', error); return []; } } private async runWfuzzDirectories(baseUrl: string, config: FuzzingConfiguration): Promise<FuzzingResult[]> { try { const results: FuzzingResult[] = []; const command = `wfuzz -c -z file,${config.wordlist} --hc ${config.filter_codes?.join(',')} -t ${config.threads} "${baseUrl}/FUZZ"`; const { stdout } = await execAsync(command, { timeout: 120000, maxBuffer: 1024 * 1024 * 5 }); // Parse wfuzz output const lines = stdout.split('\n').filter(line => line.includes('C=')); for (const line of lines) { const match = line.match(/(\d+)\s+C=(\d+)\s+L=(\d+)\s+W=(\d+)\s+Ch=(\d+)\s+"([^"]+)"/); if (match) { results.push({ parameter: 'directory', payload: match[6], url: `${baseUrl}/${match[6]}`, method: 'GET', response_code: parseInt(match[2]), response_size: parseInt(match[5]), response_time: 0, vulnerability_detected: this.isInterestingDirectory(match[6]), severity: this.calculateDirectorySeverity(match[6], parseInt(match[2])) }); } } return results; } catch (error) { console.error('Wfuzz directory fuzzing error:', error); return []; } } private async testSinglePayload(target: string, parameter: string, payload: string, method: string): Promise<FuzzingResult> { const axios = require('axios'); try { const startTime = Date.now(); let response; if (method.toUpperCase() === 'GET') { const url = new URL(target); url.searchParams.set(parameter, payload); response = await axios.get(url.toString(), { timeout: 10000 }); } else { const data = { [parameter]: payload }; response = await axios.post(target, data, { timeout: 10000 }); } const responseTime = Date.now() - startTime; const result: FuzzingResult = { parameter, payload, url: target, method, response_code: response.status, response_size: response.data?.length || 0, response_time: responseTime, vulnerability_detected: false, severity: 'info' }; // Analyze response for vulnerabilities this.analyzeResponseContent(result, response.data); return result; } catch (error: any) { return { parameter, payload, url: target, method, response_code: error.response?.status || 0, response_size: 0, response_time: 0, vulnerability_detected: false, severity: 'info' }; } } private groupParametersByEndpoint(parameters: ExtractedParameter[]): Map<string, ExtractedParameter[]> { const groups = new Map<string, ExtractedParameter[]>(); for (const param of parameters) { const endpoint = `${param.method}:${param.url}`; if (!groups.has(endpoint)) { groups.set(endpoint, []); } groups.get(endpoint)!.push(param); } return groups; } private getPayloadsForParameter(param: ExtractedParameter): string[] { const basePayloads = [ // XSS payloads '<script>alert(1)</script>', '<img src=x onerror=alert(1)>', 'javascript:alert(1)', // SQL injection payloads "' OR '1'='1", "'; DROP TABLE users; --", "' UNION SELECT null, version() --", // Command injection payloads '; cat /etc/passwd', '| whoami', '`id`', // Path traversal payloads '../../../etc/passwd', '..\\..\\..\\windows\\system32\\drivers\\etc\\hosts', // LDAP injection payloads '*)(uid=*', '*)(|(password=*))', // XXE payloads '<?xml version="1.0"?><!DOCTYPE root [<!ENTITY test SYSTEM "file:///etc/passwd">]><root>&test;</root>', // SSTI payloads '{{7*7}}', '${7*7}', '#{7*7}', // NoSQL injection payloads '{"$ne": null}', '{"$regex": ".*"}', // SSRF payloads 'http://127.0.0.1:80', 'http://localhost:22', 'file:///etc/passwd' ]; // Filter payloads based on parameter vulnerability types const relevantPayloads = basePayloads.filter(payload => { for (const vulnType of param.potential_vuln_types) { if (vulnType.includes('XSS') && payload.includes('<script>')) return true; if (vulnType.includes('SQL') && payload.includes("'")) return true; if (vulnType.includes('Command') && payload.includes(';')) return true; if (vulnType.includes('Path') && payload.includes('../')) return true; if (vulnType.includes('SSRF') && payload.includes('http://')) return true; } return false; }); return relevantPayloads.length > 0 ? relevantPayloads : basePayloads.slice(0, 10); } private buildTestUrl(endpoint: string, param: ExtractedParameter, payload: string): string { try { const url = new URL(endpoint); if (param.method === 'GET') { url.searchParams.set(param.name, payload); } return url.toString(); } catch { return `${endpoint}?${param.name}=${encodeURIComponent(payload)}`; } } private analyzeResponseForVulnerabilities(result: FuzzingResult, param: ExtractedParameter): void { // Analyze based on response code if (result.response_code === 500) { result.vulnerability_detected = true; result.vulnerability_type = 'Application Error'; result.severity = 'medium'; result.evidence = `HTTP 500 error triggered by payload: ${result.payload}`; } // Analyze based on response size anomalies if (result.response_size > 10000) { result.vulnerability_detected = true; result.vulnerability_type = 'Information Disclosure'; result.severity = 'low'; result.evidence = `Unusually large response size: ${result.response_size} bytes`; } // Analyze based on response time if (result.response_time > 5000) { result.vulnerability_detected = true; result.vulnerability_type = 'Potential Blind Injection'; result.severity = 'medium'; result.evidence = `Response time anomaly: ${result.response_time}ms`; } // Payload-specific analysis if (result.payload.includes('<script>') && result.response_code === 200) { result.vulnerability_detected = true; result.vulnerability_type = 'Potential XSS'; result.severity = 'high'; result.evidence = `XSS payload reflected in response`; } if (result.payload.includes("'") && result.response_code === 500) { result.vulnerability_detected = true; result.vulnerability_type = 'Potential SQL Injection'; result.severity = 'high'; result.evidence = `SQL error triggered by injection payload`; } } private analyzeResponseContent(result: FuzzingResult, responseContent: string): void { if (!responseContent) return; const content = responseContent.toLowerCase(); // Check for reflected payload if (content.includes(result.payload.toLowerCase())) { result.vulnerability_detected = true; result.vulnerability_type = 'Reflected Input'; result.severity = 'medium'; result.evidence = 'Payload reflected in response content'; } // Check for error messages const errorPatterns = [ 'sql syntax error', 'mysql error', 'postgresql error', 'oracle error', 'syntax error near', 'unexpected token', 'stack trace', 'exception', 'error message' ]; for (const pattern of errorPatterns) { if (content.includes(pattern)) { result.vulnerability_detected = true; result.vulnerability_type = 'Application Error'; result.severity = 'medium'; result.evidence = `Error pattern detected: ${pattern}`; break; } } // Check for sensitive file content if (content.includes('root:') || content.includes('[users]') || content.includes('password')) { result.vulnerability_detected = true; result.vulnerability_type = 'Information Disclosure'; result.severity = 'high'; result.evidence = 'Sensitive file content detected'; } } private analyzeResults(results: FuzzingResult[]): FuzzingResult[] { // Additional analysis and correlation return results; } private analyzeDirectoryResults(results: FuzzingResult[]): FuzzingResult[] { return results.map(result => { if (this.isSensitiveFile(result.url)) { result.vulnerability_detected = true; result.vulnerability_type = 'Sensitive File Exposure'; result.severity = this.calculateFileSeverity(result.url); result.evidence = `Sensitive file accessible: ${result.url}`; } return result; }); } private isSensitiveFile(url: string): boolean { const sensitivePatterns = [ '.env', '.git', '.svn', 'config.php', 'database.sql', 'backup.', '.bak', '.old', '.tmp', 'admin.php', 'phpinfo.php', 'test.php', '.htaccess', 'web.config' ]; return sensitivePatterns.some(pattern => url.toLowerCase().includes(pattern)); } private isInterestingDirectory(directory: string): boolean { const interestingDirs = [ 'admin', 'administrator', 'backup', 'config', 'db', 'database', 'test', 'dev', 'staging', 'api', 'v1', 'v2' ]; return interestingDirs.some(dir => directory.toLowerCase().includes(dir)); } private calculateDirectorySeverity(directory: string, responseCode: number): 'info' | 'low' | 'medium' | 'high' | 'critical' { if (responseCode !== 200) return 'info'; const highRisk = ['admin', 'administrator', 'backup', 'database', 'config']; const mediumRisk = ['test', 'dev', 'staging', 'api']; const dirLower = directory.toLowerCase(); if (highRisk.some(risk => dirLower.includes(risk))) return 'high'; if (mediumRisk.some(risk => dirLower.includes(risk))) return 'medium'; return 'low'; } private calculateFileSeverity(url: string): 'info' | 'low' | 'medium' | 'high' | 'critical' { const urlLower = url.toLowerCase(); if (urlLower.includes('.env') || urlLower.includes('database') || urlLower.includes('.git')) { return 'critical'; } if (urlLower.includes('config') || urlLower.includes('backup') || urlLower.includes('admin')) { return 'high'; } if (urlLower.includes('.bak') || urlLower.includes('.old') || urlLower.includes('test')) { return 'medium'; } return 'low'; } private summarizeVulnerabilities(vulnerabilities: FuzzingResult[]): Record<string, number> { const summary: Record<string, number> = {}; for (const vuln of vulnerabilities) { const type = vuln.vulnerability_type || 'Unknown'; summary[type] = (summary[type] || 0) + 1; } return summary; } private generateRecommendations(vulnerabilities: FuzzingResult[]): string[] { const recommendations: string[] = []; const vulnTypes = new Set(vulnerabilities.map(v => v.vulnerability_type).filter(Boolean)); if (vulnTypes.has('Potential XSS')) { recommendations.push('Implement proper input validation and output encoding to prevent XSS'); } if (vulnTypes.has('Potential SQL Injection')) { recommendations.push('Use parameterized queries and input validation to prevent SQL injection'); } if (vulnTypes.has('Sensitive File Exposure')) { recommendations.push('Remove or protect sensitive files from public access'); } if (vulnTypes.has('Application Error')) { recommendations.push('Implement proper error handling to prevent information disclosure'); } if (recommendations.length === 0) { recommendations.push('Continue regular security testing and monitoring'); } return recommendations; } private groupBySeverity(results: FuzzingResult[]): Record<string, number> { const groups: Record<string, number> = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }; for (const result of results) { groups[result.severity]++; } return groups; } private parseFFUFOutput(output: string, baseUrl: string, results: FuzzingResult[], extension?: string): void { const lines = output.split('\n').filter(line => line.trim() && !line.startsWith('[')); for (const line of lines) { // Simple parsing - in real implementation, use proper JSON output from ffuf const parts = line.trim().split(/\s+/); if (parts.length >= 3) { const payload = parts[0]; const status = parseInt(parts[1]) || 200; const size = parseInt(parts[2]) || 0; const url = extension ? `${baseUrl}/${payload}.${extension}` : `${baseUrl}/${payload}`; results.push({ parameter: 'directory', payload, url, method: 'GET', response_code: status, response_size: size, response_time: 0, vulnerability_detected: this.isInterestingDirectory(payload) || this.isSensitiveFile(url), severity: this.calculateDirectorySeverity(payload, status) }); } } } private getDefaultWordlist(type: 'parameters' | 'directories'): string { // Return default wordlist paths const wordlists = { parameters: '/usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt', directories: '/usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt' }; return wordlists[type] || wordlists.directories; } private async sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adriyansyah-mf/mcp-pentest'

If you have feedback or need assistance with the MCP directory API, please join our Discord server