UNPKG

web-vuln-scanner

Version:
576 lines (492 loc) 18.3 kB
const fs = require('fs'); const path = require('path'); class AdvancedHtmlReporter { constructor(options = {}) { this.options = { includeEvidence: options.includeEvidence !== false, includeRecommendations: options.includeRecommendations !== false, includeTechnicalDetails: options.includeTechnicalDetails !== false, riskInsight: options.riskInsight !== false, executiveSummary: options.executiveSummary !== false, complianceMapping: options.complianceMapping !== false, ...options }; } generateReport(scanResults) { const reportData = this.processResults(scanResults); return this.buildHtmlReport(reportData); } processResults(results) { return { metadata: { target: results.url, scanDate: new Date(results.timestamp).toLocaleString(), scanDuration: Math.round((results.scanDuration || 0) / 1000), version: '1.0.10', totalUrls: results.scannedUrls?.length || 1, userAgent: 'WebVulnScanner/1.0.10' }, summary: this.calculateRiskSummary(results.summary), vulnerabilities: this.enhanceVulnerabilities(results.vulnerabilities), riskAnalysis: this.calculateRiskAnalysis(results.vulnerabilities), complianceReport: this.generateComplianceReport(results.vulnerabilities), executiveSummary: this.generateExecutiveSummary(results), recommendations: this.generatePrioritizedRecommendations(results.vulnerabilities) }; } calculateRiskSummary(summary) { const total = summary.total || 0; const critical = summary.critical || 0; const high = summary.high || 0; const medium = summary.medium || 0; const low = summary.low || 0; const info = summary.info || 0; // Calculate risk score (0-100) const riskScore = Math.min(100, Math.round( (critical * 25) + (high * 15) + (medium * 8) + (low * 3) + (info * 1) )); let riskLevel = 'Low'; if (riskScore >= 80) riskLevel = 'Critical'; else if (riskScore >= 60) riskLevel = 'High'; else if (riskScore >= 30) riskLevel = 'Medium'; return { ...summary, riskScore, riskLevel, percentages: { critical: total > 0 ? Math.round((critical / total) * 100) : 0, high: total > 0 ? Math.round((high / total) * 100) : 0, medium: total > 0 ? Math.round((medium / total) * 100) : 0, low: total > 0 ? Math.round((low / total) * 100) : 0, info: total > 0 ? Math.round((info / total) * 100) : 0 } }; } enhanceVulnerabilities(vulnerabilities) { return vulnerabilities.map((vuln, index) => ({ ...vuln, id: `VULN-${String(index + 1).padStart(4, '0')}`, cvssScore: this.calculateCVSSScore(vuln), exploitability: this.assessExploitability(vuln), businessImpact: this.assessBusinessImpact(vuln), complianceMapping: this.mapToCompliance(vuln), remediationPriority: this.calculateRemediationPriority(vuln), technicalDetails: this.extractTechnicalDetails(vuln) })); } calculateCVSSScore(vuln) { // Simplified CVSS v3.1 scoring const baseScores = { critical: { base: 9.0, vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H' }, high: { base: 7.5, vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N' }, medium: { base: 5.0, vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N' }, low: { base: 2.5, vector: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N' }, info: { base: 0.0, vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N' } }; return baseScores[vuln.severity] || baseScores.info; } assessExploitability(vuln) { // Determine exploitability based on vulnerability type const exploitabilityMap = { 'sql_injection': 'High', 'xss': 'Medium', 'csrf': 'Medium', 'rce': 'Critical', 'path_traversal': 'High', 'idor': 'Medium', 'security_headers': 'Low', 'ssl_issue': 'Low' }; return exploitabilityMap[vuln.type] || 'Unknown'; } assessBusinessImpact(vuln) { // Assess business impact based on vulnerability type and location const impactFactors = { 'sql_injection': 'Critical - Data breach, data loss', 'xss': 'High - Account takeover, data theft', 'csrf': 'Medium - Unauthorized actions', 'rce': 'Critical - Complete system compromise', 'path_traversal': 'High - Information disclosure', 'idor': 'Medium - Unauthorized data access', 'security_headers': 'Low - Security best practices', 'ssl_issue': 'Medium - Data in transit protection' }; return impactFactors[vuln.type] || 'To be assessed'; } mapToCompliance(vuln) { const complianceMap = { 'sql_injection': { 'OWASP Top 10': 'A03:2021 – Injection', 'CWE': 'CWE-89', 'PCI DSS': '6.5.1', 'ISO 27001': 'A.14.2.5' }, 'xss': { 'OWASP Top 10': 'A03:2021 – Injection', 'CWE': 'CWE-79', 'PCI DSS': '6.5.7', 'ISO 27001': 'A.14.2.5' }, 'csrf': { 'OWASP Top 10': 'A01:2021 – Broken Access Control', 'CWE': 'CWE-352', 'PCI DSS': '6.5.9', 'ISO 27001': 'A.14.2.5' }, 'security_headers': { 'OWASP Top 10': 'A05:2021 – Security Misconfiguration', 'CWE': 'CWE-16', 'PCI DSS': '6.5.10', 'ISO 27001': 'A.14.2.5' } }; return complianceMap[vuln.type] || {}; } calculateRemediationPriority(vuln) { const severity = vuln.severity; const exploitability = this.assessExploitability(vuln); // Calculate priority score let score = 0; if (severity === 'critical') score += 40; else if (severity === 'high') score += 30; else if (severity === 'medium') score += 20; else if (severity === 'low') score += 10; if (exploitability === 'Critical') score += 30; else if (exploitability === 'High') score += 20; else if (exploitability === 'Medium') score += 10; if (score >= 60) return 'Immediate'; else if (score >= 40) return 'High'; else if (score >= 20) return 'Medium'; else return 'Low'; } extractTechnicalDetails(vuln) { return { affectedParameter: vuln.parameter || 'N/A', payloadUsed: vuln.payload || 'N/A', httpMethod: vuln.method || 'GET', responseCode: vuln.responseCode || 'N/A', responseTime: vuln.responseTime || 'N/A' }; } calculateRiskAnalysis(vulnerabilities) { const analysis = { topVulnerabilityTypes: {}, riskTrends: [], affectedComponents: {}, exploitabilityDistribution: { Critical: 0, High: 0, Medium: 0, Low: 0 } }; vulnerabilities.forEach(vuln => { // Count vulnerability types analysis.topVulnerabilityTypes[vuln.type] = (analysis.topVulnerabilityTypes[vuln.type] || 0) + 1; // Count exploitability const exploitability = this.assessExploitability(vuln); if (analysis.exploitabilityDistribution[exploitability] !== undefined) { analysis.exploitabilityDistribution[exploitability]++; } // Affected components (simplified) const component = this.getComponentFromUrl(vuln.url); analysis.affectedComponents[component] = (analysis.affectedComponents[component] || 0) + 1; }); return analysis; } getComponentFromUrl(url) { try { const urlObj = new URL(url); const pathSegments = urlObj.pathname.split('/').filter(Boolean); return pathSegments[0] || 'root'; } catch (e) { return 'unknown'; } } generateComplianceReport(vulnerabilities) { const frameworks = { 'OWASP Top 10': {}, 'CWE': {}, 'PCI DSS': {}, 'ISO 27001': {} }; vulnerabilities.forEach(vuln => { const mapping = this.mapToCompliance(vuln); Object.keys(frameworks).forEach(framework => { if (mapping[framework]) { frameworks[framework][mapping[framework]] = (frameworks[framework][mapping[framework]] || 0) + 1; } }); }); return frameworks; } generateExecutiveSummary(results) { const total = results.summary.total || 0; const critical = results.summary.critical || 0; const high = results.summary.high || 0; let riskStatement = 'The security assessment reveals a low risk profile.'; if (critical > 0) { riskStatement = `Critical security vulnerabilities identified requiring immediate attention. ${critical} critical and ${high} high-risk issues found.`; } else if (high > 0) { riskStatement = `High-risk security vulnerabilities identified. ${high} high-risk issues require prompt remediation.`; } else if (total > 0) { riskStatement = `Security vulnerabilities identified with manageable risk levels. ${total} issues found requiring planned remediation.`; } return { riskStatement, keyFindings: this.generateKeyFindings(results.vulnerabilities), businessImpact: this.generateBusinessImpact(results.vulnerabilities), immediateActions: this.generateImmediateActions(results.vulnerabilities) }; } generateKeyFindings(vulnerabilities) { const findings = []; const typeCounts = {}; vulnerabilities.forEach(vuln => { typeCounts[vuln.type] = (typeCounts[vuln.type] || 0) + 1; }); // Top 3 vulnerability types const topTypes = Object.entries(typeCounts) .sort(([,a], [,b]) => b - a) .slice(0, 3); topTypes.forEach(([type, count]) => { findings.push(`${count} instance${count > 1 ? 's' : ''} of ${type.replace('_', ' ')} vulnerability`); }); return findings; } generateBusinessImpact(vulnerabilities) { const impacts = new Set(); vulnerabilities.forEach(vuln => { if (vuln.severity === 'critical' || vuln.severity === 'high') { const impact = this.assessBusinessImpact(vuln); impacts.add(impact); } }); return Array.from(impacts); } generateImmediateActions(vulnerabilities) { const actions = []; const criticalVulns = vulnerabilities.filter(v => v.severity === 'critical'); const highVulns = vulnerabilities.filter(v => v.severity === 'high'); if (criticalVulns.length > 0) { actions.push(`Address ${criticalVulns.length} critical vulnerabilities immediately`); } if (highVulns.length > 0) { actions.push(`Plan remediation for ${highVulns.length} high-risk vulnerabilities`); } actions.push('Implement security monitoring and regular assessment schedule'); actions.push('Review and update security policies and procedures'); return actions; } generatePrioritizedRecommendations(vulnerabilities) { const recommendations = new Map(); vulnerabilities.forEach(vuln => { const priority = this.calculateRemediationPriority(vuln); if (!recommendations.has(priority)) { recommendations.set(priority, []); } if (vuln.recommendation) { recommendations.get(priority).push({ vulnerability: vuln.type, recommendation: vuln.recommendation, urls: [vuln.url] }); } }); // Merge similar recommendations const merged = {}; recommendations.forEach((recs, priority) => { merged[priority] = this.mergeRecommendations(recs); }); return merged; } mergeRecommendations(recommendations) { const merged = new Map(); recommendations.forEach(rec => { if (merged.has(rec.recommendation)) { merged.get(rec.recommendation).urls.push(...rec.urls); } else { merged.set(rec.recommendation, { ...rec, urls: [...rec.urls] }); } }); return Array.from(merged.values()); } buildHtmlReport(data) { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PENETRATION TEST REPORT - CLASSIFIED</title> <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> <style> ${this.getAdvancedStyles()} </style> </head> <body> <div class="terminal-container"> ${this.generateHeader(data)} ${this.generateExecutiveSummary(data)} ${this.generateRiskDashboard(data)} ${this.generateVulnerabilityDetails(data)} ${this.generateComplianceSection(data)} ${this.generateRecommendations(data)} ${this.generateFooter(data)} </div> <script> ${this.getInteractiveScripts()} </script> </body> </html>`; } getAdvancedStyles() { return ` * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Fira Code', 'Courier New', monospace; background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%); color: #00ff41; min-height: 100vh; position: relative; } body::before { content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-image: radial-gradient(circle at 25% 25%, #00ff4108 0%, transparent 50%), radial-gradient(circle at 75% 75%, #ff004108 0%, transparent 50%); pointer-events: none; z-index: -1; } .terminal-container { max-width: 1400px; margin: 0 auto; padding: 20px; } .section { background: rgba(0, 0, 0, 0.9); border: 2px solid #00ff41; border-radius: 10px; margin: 20px 0; padding: 25px; box-shadow: 0 0 30px rgba(0, 255, 65, 0.2); } .section-title { color: #ff6b35; font-size: 1.5em; font-weight: bold; margin-bottom: 20px; text-transform: uppercase; letter-spacing: 2px; border-bottom: 2px solid #ff6b35; padding-bottom: 10px; position: relative; } .section-title::after { content: '>>>'; position: absolute; right: 0; bottom: 10px; color: #00ff41; animation: blink 1s infinite; } @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } .risk-dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .risk-card { background: linear-gradient(145deg, rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.8)); border: 2px solid; border-radius: 12px; padding: 20px; text-align: center; transition: all 0.3s ease; cursor: pointer; } .risk-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); } .risk-card.critical { border-color: #ff0040; } .risk-card.high { border-color: #ff4500; } .risk-card.medium { border-color: #ffa500; } .risk-card.low { border-color: #00ced1; } .risk-card.info { border-color: #708090; } .risk-number { font-size: 2.5em; font-weight: bold; margin-bottom: 10px; text-shadow: 0 0 15px currentColor; } .vuln-item { background: rgba(0, 0, 0, 0.6); border-left: 4px solid; margin-bottom: 20px; padding: 20px; border-radius: 0 8px 8px 0; transition: all 0.3s ease; } .vuln-item:hover { transform: translateX(10px); background: rgba(0, 0, 0, 0.8); } .vuln-item.critical { border-left-color: #ff0040; } .vuln-item.high { border-left-color: #ff4500; } .vuln-item.medium { border-left-color: #ffa500; } .vuln-item.low { border-left-color: #00ced1; } .vuln-item.info { border-left-color: #708090; } .tabs { display: flex; margin-bottom: 20px; border-bottom: 2px solid #00ff41; } .tab { padding: 10px 20px; background: rgba(0, 255, 65, 0.1); border: 1px solid #00ff41; cursor: pointer; transition: all 0.3s ease; } .tab.active { background: #00ff41; color: #000; } .tab-content { display: none; } .tab-content.active { display: block; } .progress-bar { width: 100%; height: 20px; background: rgba(0, 0, 0, 0.5); border-radius: 10px; overflow: hidden; margin: 10px 0; } .progress-fill { height: 100%; transition: width 2s ease; } .chart-container { width: 100%; height: 300px; position: relative; margin: 20px 0; } @media (max-width: 768px) { .terminal-container { padding: 10px; } .risk-dashboard { grid-template-columns: repeat(2, 1fr); } } `; } // ...additional methods for generating different sections... } module.exports = AdvancedHtmlReporter;