面对Nessus扫描导出的数百条漏洞记录,安全团队经常需要在工单系统或汇报邮件中附上一张直观的漏洞分布图,而不是粘贴一长串原始报告。借助Node.js,我们可以构建一个轻量级工具,将Nessus报告中的主机信息和漏洞统计自动渲染为PNG图片。这个方案被称为Mock2Image,其中Mock表示支持使用模拟数据离线开发,Image代表最终输出为可分享的图片文件。下面从数据获取、解析统计、绘图输出到模块封装,完整展示实现过程。

一、Nessus报告的数据来源与格式分析
Nessus提供REST API,可以通过HTTP请求获取扫描结果。导出格式支持.nessus(本质为XML)和JSON两种。本文重点处理JSON格式,因为Node.js原生支持JSON解析,代码更简洁。如果手里只有XML报告,也可以使用xml2js等库转换成对象后再交给统计模块。通常报告结构包含扫描摘要、主机列表和漏洞列表,每个漏洞带有pluginID、severity、port、protocol等字段。severity取值范围为0到4,分别对应信息、低危、中危、高危和严重。
如果暂时没有Nessus环境,可以手动构造一个模拟JSON文件,这正是Mock2Image名称中Mock的由来。通过模拟数据先完成开发和测试,后续接入真实扫描结果时只需替换数据源。下面给出一个简化的模拟报告结构,后面所有代码都基于这个结构编写。
const mockReport = {
info: {
name: 'Demo Scan',
policy: 'Basic Network Scan',
started: '2025-01-15 10:30:00'
},
hosts: [
{
hostname: 'web-01',
ip: '192.168.1.10',
vulnerabilities: [
{ pluginID: 11213, severity: 4, port: 443, protocol: 'tcp', name: 'TLS Version 1.0 Protocol Detection' },
{ pluginID: 51192, severity: 2, port: 80, protocol: 'tcp', name: 'SSL Certificate Cannot Be Trusted' },
{ pluginID: 10107, severity: 1, port: 0, protocol: 'tcp', name: 'HTTP Server Type and Version' }
]
},
{
hostname: 'db-01',
ip: '192.168.1.20',
vulnerabilities: [
{ pluginID: 11213, severity: 4, port: 3306, protocol: 'tcp', name: 'TLS Version 1.0 Protocol Detection' },
{ pluginID: 26928, severity: 3, port: 3306, protocol: 'tcp', name: 'MySQL Unauthenticated Access' },
{ pluginID: 10107, severity: 1, port: 0, protocol: 'tcp', name: 'HTTP Server Type and Version' }
]
}
]
};
在解析之前需要确认报告版本和字段命名。不同Nessus版本导出的JSON字段略有差异,例如有些版本使用vulnerabilities数组,有些使用findings。建议编写一个归一化函数,将不同结构统一转为内部数据格式,降低后续绘图代码的耦合。这个函数可以接收原始对象,输出只包含hostname、ip和vulnerabilities的标准结构。
二、用Node.js解析并统计漏洞数据
读取JSON文件后,遍历hosts数组和vulnerabilities数组,统计每台主机不同严重等级的漏洞数量,同时汇总所有主机的总体分布。严重等级数字需要映射为中文标签,便于图片展示。实现一个stats函数,返回总主机数、总漏洞数、各等级数量以及Top插件ID列表。下面代码演示了解析与统计过程。
const fs = require('fs');
function parseAndStats(reportPath) {
const raw = fs.readFileSync(reportPath, 'utf-8');
const report = JSON.parse(raw);
const severityLabels = { 0: '信息', 1: '低危', 2: '中危', 3: '高危', 4: '严重' };
const stats = {
totalHosts: report.hosts.length,
totalVulns: 0,
severityCount: { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 },
topPlugins: {}
};
report.hosts.forEach(host => {
host.vulnerabilities.forEach(vuln => {
stats.totalVulns += 1;
const sev = vuln.severity;
if (sev >= 0 && sev <= 4) {
stats.severityCount[sev] += 1;
}
const pluginKey = String(vuln.pluginID);
if (!stats.topPlugins[pluginKey]) {
stats.topPlugins[pluginKey] = { count: 0, name: vuln.name };
}
stats.topPlugins[pluginKey].count += 1;
});
});
stats.topPlugins = Object.entries(stats.topPlugins)
.sort((a, b) => b[1].count - a[1].count)
.slice(0, 5)
.map(([id, item]) => ({ id, name: item.name, count: item.count }));
return stats;
}
统计逻辑中,为了避免重复统计同一漏洞在不同主机上的出现,可以按主机和插件ID去重,或者保留主机维度用于生成主机风险分布子图。本文的统计方式是将每条漏洞记录都计入总数,这样能反映扫描到的漏洞实例数量。如果希望按漏洞类型去重,可以在遍历时记录pluginID集合。
对于大型报告,使用fs.readFileSync整体加载会占用大量内存。若文件超过数百MB,建议改为stream-json等流式解析库逐条处理。不过对于常规内网扫描,几百KB到几十MB的JSON文件完全可以一次性读入。同时要注意Nessus导出的JSON可能包含特殊字符,读取时必须指定utf-8编码,否则可能出现乱码。
三、使用Canvas库生成Mock2Image图片
Node.js中生成图片的库有node-canvas、sharp、puppeteer等。node-canvas提供了与浏览器Canvas相似的API,适合绘制柱状图、文字和图例。安装命令为npm install canvas,注意在Linux服务器上需要提前安装cairo、pango等系统依赖。另一种方案是使用SVG生成矢量图再转换为PNG,但需要额外的转换步骤。本文选择node-canvas,因为它可以直接控制像素级绘制。
绘图函数接收stats统计结果和报告名称,创建一张宽800高400的画布,填充白色背景,绘制标题、柱状图、图例和统计文字。柱状图的高度根据各等级漏洞数量与最大值的比例计算,不同等级使用不同颜色。保存为output.png后即可直接用于汇报。
const { createCanvas } = require('canvas');
async function drawStatsToImage(stats, outputPath, reportName) {
const width = 800;
const height = 400;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = '#1f2937';
ctx.font = 'bold 24px "Microsoft YaHei", sans-serif';
ctx.fillText(reportName + ' 漏洞分布', 30, 50);
const labels = ['信息', '低危', '中危', '高危', '严重'];
const colors = ['#6b7280', '#3b82f6', '#f59e0b', '#ef4444', '#7f1d1d'];
const values = [0, 1, 2, 3, 4].map(sev => stats.severityCount[sev]);
const maxVal = Math.max(...values, 1);
const chartX = 60;
const chartY = 100;
const chartWidth = 600;
const chartHeight = 220;
const barWidth = chartWidth / labels.length - 20;
labels.forEach((label, idx) => {
const barHeight = (values[idx] / maxVal) * chartHeight;
const x = chartX + idx * (barWidth + 20);
const y = chartY + chartHeight - barHeight;
ctx.fillStyle = colors[idx];
ctx.fillRect(x, y, barWidth, barHeight);
ctx.fillStyle = '#111827';
ctx.font = '14px "Microsoft YaHei", sans-serif';
ctx.fillText(label, x, chartY + chartHeight + 20);
ctx.fillText(String(values[idx]), x, y - 8);
});
ctx.fillStyle = '#374151';
ctx.font = '16px "Microsoft YaHei", sans-serif';
ctx.fillText('主机总数: ' + stats.totalHosts, 60, 370);
ctx.fillText('漏洞总数: ' + stats.totalVulns, 220, 370);
ctx.fillText('生成时间: ' + new Date().toLocaleString(), 400, 370);
const buf = canvas.toBuffer('image/png');
require('fs').writeFileSync(outputPath, buf);
console.log('图片已保存到 ' + outputPath);
}
绘制细节中,柱状图的柱体宽度需要根据标签数量动态计算,避免重叠。文本字体使用Microsoft YaHei,在Linux服务器上可能需要替换为WenQuanYi Micro Hei或Noto Sans CJK。生成时间为本地时间戳,如果需要在图片中固定为扫描开始时间,可以将report中的started字段传入函数。
图片中还可以加入主机风险分布或Top插件列表,但空间有限,优先展示风险等级和主机漏洞总数。如果要扩展,可以增加第二张横向条形图,展示Top5插件名称和出现次数。这些都可以使用相同的Canvas绘制方法实现。
四、封装为可复用的Mock2Image模块
将解析和绘图功能封装成一个函数或类,输入报告文件路径和输出图片路径,内部先调用parseAndStats再调用drawStatsToImage。提供命令行入口,通过node mock2image.js input.json output.png调用。在package.json中配置bin字段后,还可以发布为npm包供团队使用。
const fs = require('fs');
const path = require('path');
const { createCanvas } = require('canvas');
function mock2Image(inputPath, outputPath) {
if (!fs.existsSync(inputPath)) {
throw new Error('报告文件不存在: ' + inputPath);
}
const stats = parseAndStats(inputPath);
const reportName = path.basename(inputPath, path.extname(inputPath));
return drawStatsToImage(stats, outputPath, reportName);
}
if (process.argv.length < 4) {
console.log('用法: node mock2image.js <nessus-report.json> <output.png>');
process.exit(1);
}
mock2Image(process.argv[2], process.argv[3])
.then(() => {
console.log('Mock2Image 处理完成');
})
.catch(err => {
console.error('处理失败:', err.message);
process.exit(1);
});
参数校验和错误处理是模块稳定运行的关键。报告文件不存在、JSON格式错误、输出目录不可写、Canvas库初始化失败等情况都需要捕获异常并返回友好的错误信息。上面的命令行示例中捕获了错误并输出到控制台,实际使用中可以根据需要记录日志或发送告警。
可以添加选项支持指定图片尺寸、标题文字、是否显示主机明细等。这些配置可以放在一个options对象中,默认值保持简单,例如宽度800、高度400、字体列表等。这样既能满足快速调用,又能保留扩展空间。
五、常见问题与性能优化
使用node-canvas时最常见的坑是安装失败,原因是系统缺少cairo、pango等底层库。在Debian或Ubuntu上可以执行apt-get install libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev安装。如果服务器不允许安装系统依赖,可以改用纯SVG方案,先用模板字符串生成SVG文本,再用sharp库将SVG转换为PNG,sharp通常不需要额外系统依赖。
中文乱码问题通常是因为服务器缺少中文字体。可以通过fc-list命令查看已安装字体,如果没有中文字体,安装noto-fonts-cjk或复制字体文件到~/.fonts目录并执行fc-cache。在Canvas中设置字体时使用系统存在的字体族名称即可解决。
性能优化上,对于超过一万条漏洞的报告,统计阶段可以只保留汇总数据而不逐条渲染主机明细。绘图部分可以异步生成,配合队列处理多个报告。如果需要在CI/CD流水线中批量生成,建议将mock2Image函数设计为可并行调用,避免阻塞事件循环。
总结来说,Mock2Image实现了从Nessus原始数据到汇报图片的自动化转换。通过Node.js解析JSON报告、统计漏洞严重性分布,再借助Canvas绘制柱状图,整个过程轻量且易于集成。无论是模拟数据开发还是真实扫描结果,都可以通过同一套代码输出直观的态势图,显著提升安全团队的报告效率。
Node.jsNessusMock2Image修改时间:2026-08-19 06:39:43