渗透测试报告工具:基于JavaScript的自动化生成与PDF导出实践 简介这是一款面向渗透测试团队与安全从业者的报告管理工具以JavaScript技术栈实现帮助团队跟踪参与项目、定义测试范围并自动执行nmap、dirb、showmount、nikto及EyeWitness截图等重复性扫描活动。工具内置报告模块可记录调查结果、划分严重性等级并附加截图支持通过自定义模板导出docx文档实验性知识模块还能借助已知SSH凭据自动化部分横向移动操作。资源包共109个文件约90.93MB以js与py脚本为主体辅以css样式、sh脚本、docx模板及png截图等内部通过Docker启动Web服务、动作模块与MongoDB三个容器。目前已有416人学习关注适合需要规范渗透测试流程、沉淀扫描结果与报告输出的安全工程师参考使用。1. 渗透测试报告工具为什么我劝你先别急着写 Word做渗透测试的同行大概都有过这种体验辛辛苦苦测完一轮漏洞列表躺在 Burp 和 sqlmap 的日志里可交付物却要从零开始排版。某次给一家公司做内网评估我花了整整一个下午调 Word 的编号和表格对齐真正写漏洞描述的时间不到两小时。这种本末倒置的事在渗透测试报告工具出现之前几乎是行业常态。pentest-report就是冲着这个痛点来的。它是一个用 JavaScript 写的渗透测试报告生成工具核心思路是把漏洞数据从「手工排版」里抽出来变成结构化输入再统一渲染成报告。你负责填漏洞、定等级、写修复建议它负责目录、编号、样式和导出。适合谁一是经常出报告的一线渗透测试人员二是需要批量产出评估文档的安全服务团队三是想把报告流程塞进 CI 的自动化玩家。它不替代你的测试能力只替代你的排版时间。2. 拆开看结构数据层、模板层和渲染层怎么分工2.1 为什么用 JavaScript 而不是 Python 脚本很多人第一反应是「报告生成用 Python 不香吗」我一开始也这么想。但真正落地时会发现渗透测试报告的最终形态往往是 HTML 或 PDF而前端生态在模板渲染、样式控制和浏览器导出这块成熟得多。pentest-report选 JavaScript本质是让报告生成和 Web 技术栈对齐模板用 HTML/CSS 写导出走无头浏览器样式所见即所得。另一个现实原因是团队协作。安全团队里不一定人人会 Python但几乎人人能看懂 HTML 和 JSON。漏洞数据用 JSON 描述模板用 HTML 写中间用 JavaScript 粘合门槛低到实习生也能改。常见做法是把漏洞库、报告模板、导出配置分成三个独立文件互不干扰。2.2 三层结构数据、模板、渲染这个工具的内部结构可以粗暴拆成三层。第一层是数据层负责描述漏洞。每个漏洞是一个对象包含名称、等级、类型、影响、复现步骤、修复建议等字段。第二层是模板层决定报告长什么样通常是 HTML 模板加 CSS。第三层是渲染层把数据灌进模板再导出成 HTML 或 PDF。三层分离的好处是换模板不用动数据改数据不用碰样式。我一般会把数据层单独抽成一个findings.json模板层放template/目录渲染脚本放根目录。这样一份数据可以出中文版、英文版、简版、详版只要换模板就行。{ project: 某跨平台系统安全评估, tester: A同学, date: 2025-03-01, findings: [ { id: VULN-001, title: 某接口未授权访问, severity: high, type: 越权, description: 接口未校验身份即可读取敏感数据, reproduction: [ 登录普通账号获取 token, 直接请求管理接口, 观察返回数据 ], recommendation: 在接口层增加身份与权限校验 } ] }上面这段 JSON 是数据层的最小示例。severity字段建议统一用critical/high/medium/low/info五级方便后续排序和统计。reproduction用数组而不是长字符串是为了在模板里自动生成有序列表避免手工敲编号。id字段别省报告里交叉引用和修复跟踪都靠它。2.3 模板层怎么写才不返工模板层最容易翻车的地方是「样式写死」。我见过有人把字体、颜色、页边距全写进 HTML 的style属性里结果客户要求换模板时整份重写。正确做法是把样式抽到独立 CSS模板只负责结构。!-- template/report.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 link relstylesheet hrefreport.css /head body h1{{project}} 渗透测试报告/h1 p测试人员{{tester}} 日期{{date}}/p h2漏洞汇总/h2 table thead trth编号/thth名称/thth等级/th/tr /thead tbody {{#each findings}} tr td{{id}}/td td{{title}}/td td classsev-{{severity}}{{severity}}/td /tr {{/each}} /tbody /table {{#each findings}} section classfinding h3{{id}} {{title}}/h3 pstrong等级/strong{{severity}}/p pstrong描述/strong{{description}}/p ol {{#each reproduction}} li{{this}}/li {{/each}} /ol pstrong修复建议/strong{{recommendation}}/p /section {{/each}} /body /html这段模板用的是常见的 Mustache 风格语法{{#each}}负责循环{{变量}}负责取值。注意classsev-{{severity}}这一手把等级拼进 class 名CSS 里就能按等级给不同颜色不用在模板里写 if-else。reproduction的{{#each}}里用{{this}}取当前项因为它是字符串数组而不是对象数组。2.4 渲染脚本把数据灌进模板渲染层是真正干活的脚本。它读 JSON、读模板、编译、输出。下面是一个最小可跑的版本依赖mustache和puppeteer。// render.js const fs require(fs); const Mustache require(mustache); const puppeteer require(puppeteer); async function main() { // 1. 读取数据与模板 const data JSON.parse(fs.readFileSync(findings.json, utf8)); const template fs.readFileSync(template/report.html, utf8); // 2. 编译模板生成 HTML const html Mustache.render(template, data); fs.writeFileSync(output/report.html, html); // 3. 用无头浏览器导出 PDF const browser await puppeteer.launch(); const page await browser.newPage(); await page.goto(file:// process.cwd() /output/report.html, { waitUntil: networkidle0 }); await page.pdf({ path: output/report.pdf, format: A4, printBackground: true, margin: { top: 20mm, bottom: 20mm, left: 15mm, right: 15mm } }); await browser.close(); console.log(报告已生成output/report.pdf); } main();逻辑说明第一步用fs同步读取因为报告生成是一次性任务不需要异步优化。第二步Mustache.render把数据对象和模板合并成完整 HTML。第三步用puppeteer打开本地 HTML 再导出 PDFwaitUntil: networkidle0确保样式和字体加载完再截图否则 PDF 里可能缺样式。参数说明format: A4是纸张规格国内报告一般用 A4printBackground: true必须开否则 CSS 里的背景色和等级色块不会打印margin四个方向按毫米算左右留 15mm 是装订友好值。如果报告要加页眉页脚page.pdf还支持headerTemplate和footerTemplate但要注意它们渲染时拿不到页面 CSS得内联样式。3. 从零跑通环境、命令和第一次导出3.1 环境准备与依赖安装这个工具对 Node 版本有要求建议 16 以上因为puppeteer新版本对低版本 Node 支持不好。先确认环境node -v npm -v如果 Node 低于 16去官网下 LTS 版本重装。然后初始化项目并装依赖mkdir pentest-report-demo cd pentest-report-demo npm init -y npm install mustache puppeteerpuppeteer安装时会自动下载一个 Chromium国内网络下可能卡住。常见做法是设置镜像或跳过下载后用系统 Chrome。如果卡在下载环节可以设环境变量PUPPETEER_SKIP_DOWNLOAD1然后在脚本里指定executablePath指向本机 Chrome。3.2 目录结构与文件放置跑通之前先把目录摆好否则脚本里的相对路径会找不到文件mkdir -p template output # 把 findings.json 放根目录 # 把 report.html 和 report.css 放 template/ # 把 render.js 放根目录目录结构建议固定成template/放模板、output/放产物、根目录放数据和脚本。这样.gitignore里只忽略output/就行模板和数据都能进版本控制。我一般还会加一个assets/放 logo 和字体模板里用相对路径引用。3.3 执行导出与结果检查一切就绪后执行node render.js终端出现「报告已生成」就说明跑通了。打开output/report.pdf检查三件事一是漏洞汇总表的等级颜色有没有出来二是复现步骤的编号是不是自动生成的三是 PDF 有没有缺页或样式错位。如果颜色没出来八成是printBackground没开如果编号重复检查模板里是不是同时用了ol和手写数字。提示第一次导出建议先用两三条假数据跑确认样式没问题再灌真实漏洞。真实数据里如果有中文引号或特殊符号注意 JSON 转义否则JSON.parse会直接报错。3.4 把漏洞数据从工具里导出来实际测试时漏洞数据散落在各处手工敲 JSON 效率低。常见做法是写个小转换脚本把 Burp 的 XML 或 sqlmap 的输出转成findings.json。下面是一个把简化 CSV 转 JSON 的例子// csv2json.js const fs require(fs); const lines fs.readFileSync(findings.csv, utf8).trim().split(\n); const headers lines[0].split(,); const findings lines.slice(1).map((line, idx) { const cols line.split(,); return { id: VULN-${String(idx 1).padStart(3, 0)}, title: cols[0], severity: cols[1], type: cols[2], description: cols[3], reproduction: cols[4].split(|), // 用竖线分隔多步 recommendation: cols[5] }; }); fs.writeFileSync(findings.json, JSON.stringify({ project: 模拟项目X安全评估, tester: 某开发者, date: new Date().toISOString().slice(0, 10), findings }, null, 2));这段脚本把 CSV 每行转成一个漏洞对象padStart(3, 0)保证编号是VULN-001这种三位格式排序时不会乱。reproduction字段用竖线分隔多步转成数组后模板里的{{#each}}就能自动生成有序列表。注意 CSV 里如果字段本身含逗号这个简易解析会出错正式用建议上papaparse之类的库。4. 避坑与排查那些让我返工三次的细节4.1 PDF 里中文变成方块现象导出的 PDF 里中文全是方框英文正常。原因无头浏览器默认字体不含中文字形或者 CSS 里指定的字体在容器里不存在。解决在 CSS 里显式指定系统中文字体并在导出前确认字体已安装。Linux 环境下常见做法是装fonts-noto-cjk然后 CSS 写font-family: Noto Sans CJK SC, sans-serif;。别指望puppeteer自带中文字体它不带。4.2 模板变量不渲染页面出现 {{title}}现象PDF 里直接显示{{title}}而不是实际值。原因Mustache.render的第二个参数结构不对或者模板里用了{{#each}}但数据不是数组。解决先console.log(data)确认字段名和层级再检查模板里的路径。{{#each findings}}要求findings是数组如果传的是对象就会静默失败。另外 Mustache 默认不转义 HTML如果描述里含会被当标签需要{{{description}}}三花括号或提前转义。4.3 导出超时或 Chromium 启动失败现象puppeteer.launch()报错提示找不到浏览器或权限不足。原因容器环境缺依赖库或者以 root 运行沙箱受限。解决启动参数加args: [--no-sandbox, --disable-setuid-sandbox]同时确认系统装了 Chromium 需要的共享库。如果是在 CI 里跑建议用官方puppeteer镜像或提前apt-get install依赖。这个坑在 Docker 里几乎必踩血泪经验是先把依赖装全再跑脚本。4.4 报告页数对不上表格跨页断裂现象漏洞汇总表被硬生生截断在两页之间或者某个漏洞描述跨页后标题和内容分离。原因CSS 没控制分页行为。解决给表格和每个section.finding加page-break-inside: avoid;给标题加page-break-after: avoid;。这样浏览器会尽量把整块内容放在同一页。如果单个漏洞内容超过一页那就只能接受跨页但至少保证标题不孤零零留在页尾。4.5 等级颜色在 PDF 里丢失现象HTML 里等级色块正常PDF 里全白。原因page.pdf的printBackground默认是false。解决显式传printBackground: true。这个参数坑过我不止一次因为 HTML 预览时背景色是显示的只有导出 PDF 才丢排查时容易往 CSS 方向找其实是导出参数的问题。5. 进阶玩法把报告生成塞进自动化流程跑通单次导出只是起点真正省时间的是把它接进自动化。我现在的习惯是测试阶段用脚本把漏洞实时写进findings.json收尾时一条命令出报告。更进一步可以把render.js挂到 CI 的 job 里每次提交漏洞数据就自动生成最新版 PDF 作为构建产物。一个具体技巧是给报告加「版本戳」和「生成时间」。在render.js里注入data.generatedAt new Date().toLocaleString(zh-CN, { timeZone: Asia/Shanghai }); data.version process.env.REPORT_VERSION || draft;模板里加一行p版本{{version}} 生成时间{{generatedAt}}/p。这样每份报告都能追溯到是哪次数据生成的客户拿到后也不会搞混版本。REPORT_VERSION从环境变量读CI 里可以传 git commit 短哈希。另一个进阶方向是模板参数化。同一份数据用不同模板出不同受众的报告给技术团队看的详版保留复现步骤给管理层看的简版只留汇总表和风险等级。实现上就是准备template/tech.html和template/exec.html渲染脚本读一个--template参数决定用哪个。这样一份数据两处交付不用重复劳动。验证报告质量有个笨办法但很管用导出后把 PDF 转成图片逐页扫一遍。重点看三处——汇总表的等级排序是不是从高到低、复现步骤编号有没有断、修复建议有没有被截断。我一般会在render.js里加一段排序逻辑按critical high medium low info把findings重排后再渲染避免手工调整顺序。const order { critical: 0, high: 1, medium: 2, low: 3, info: 4 }; data.findings.sort((a, b) order[a.severity] - order[b.severity]);这段排序放在Mustache.render之前保证汇总表和详情区顺序一致。注意sort会原地修改数组如果后续还要用原始顺序先深拷贝一份。参数上order对象把等级映射成数字数字小的排前面改等级优先级只要调这个映射就行。从那以后我每次出报告都强制走一遍「数据校验 → 排序 → 渲染 → 抽查」四步再也没出现过等级乱序或编号断裂的翻车。希望帮到你。本文还有配套的精品资源点击获取