web-vuln-scanner
Version:
A JavaScript-based web vulnerability scanner
576 lines (492 loc) • 18.3 kB
JavaScript
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;
}
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;
}
(max-width: 768px) {
.terminal-container { padding: 10px; }
.risk-dashboard { grid-template-columns: repeat(2, 1fr); }
}
`;
}
// ...additional methods for generating different sections...
}
module.exports = AdvancedHtmlReporter;