rksharma-owg/vuln-scan-diff
GitHub: rksharma-owg/vuln-scan-diff
一款命令行漏洞扫描差异比对工具,离线比较两次 Nessus 或 Qualys 扫描结果,精确追踪漏洞的新增、修复与状态变更。
Stars: 0 | Forks: 0
# 漏洞扫描 Diff
一款命令行工具,用于比较两次漏洞扫描的导出文件,并准确识别它们之间的变化。专为需要跟踪连续扫描修复进度安全工程师、漏洞管理人员和 DevSecOps 团队设计。
## 为什么开发此工具
大多数漏洞扫描器提供基于 Web 的仪表盘进行趋势分析,但这些仪表盘需要连接到活跃的扫描器,并且通常缺乏审计级报告所需的精确度。常见的替代方法——将两份导出文件转储为 CSV 并运行 `diff`,或者手动交叉引用电子表格——很容易出错,并且无法区分实际上已修复的漏洞和仅仅因为主机未重新扫描而消失的漏洞。
Vuln Scan Diff 填补了这一空白,提供扫描导出文件的**离线本地比较**,严格区分 **FIXED** 和 **UNVERIFIED** 发现、凭证失败检测、严重性变更跟踪以及主机变更分析——所有这些都不会将数据发送到任何外部服务。
## 支持的格式
| 扫描器 | 格式 | 扩展名 |
|---------|--------|-----------|
| Tenable Nessus | `.nessus` XML export | `.nessus` |
| Qualys VMDR | CSV vulnerability export | `.csv` |
扫描器类型会根据文件扩展名和内容自动检测,也可以使用 `--scanner` 显式指定。
## 安装
**pipx(推荐):**
```
pipx install vuln-scan-diff
```
**venv + pip:**
```
python -m venv .venv
source .venv/bin/activate
pip install vuln-scan-diff
```
要求 Python 3.11 或更高版本。
## CLI 用法
### `vuln-diff compare` -- 比较两次扫描
```
vuln-diff compare OLD_SCAN NEW_SCAN [options]
```
| 选项 | 描述 |
|--------|-------------|
| `OLD_SCAN` | 上一次(较早)扫描导出文件的路径 |
| `NEW_SCAN` | 最新(较新)扫描导出文件的路径 |
| `--scanner`, `-s` | 扫描器类型:`auto`、`nessus` 或 `qualys`(默认:`auto`) |
| `--output`, `-o` | 在此路径生成 Excel 报告 |
| `--json`, `-j` | 在此路径生成 JSON 报告 |
| `--html` | 在此路径生成 HTML 报告 |
| `--host-key` | 主机识别策略:`auto`、`hostname`、`ip`、`asset-id`(默认:`auto`) |
| `--severity` | 用于输出的逗号分隔严重性过滤器(例如 `critical,high`) |
| `--include-informational` | 在结果中包含信息性发现 |
| `--fail-on-new-critical` | 如果检测到新的严重发现,则以代码 3 退出 |
| `--no-color` | 禁用彩色终端输出 |
| `--verbose` | 显示额外详细信息(所有新发现、严重性降低情况) |
### `vuln-diff summary` -- 汇总单次扫描
```
vuln-diff summary SCAN_FILE [options]
```
| 选项 | 描述 |
|--------|-------------|
| `SCAN_FILE` | 漏洞扫描导出文件的路径 |
| `--scanner`, `-s` | 扫描器类型(默认:`auto`) |
| `--no-color` | 禁用彩色终端输出 |
显示主机数量、按严重性和类别统计的发现、漏洞最多的主机、最常见的发现、操作系统分布以及凭证扫描状态。
### `vuln-diff credentials` -- 检查凭证状态
```
vuln-diff credentials SCAN_FILE [options]
```
| 选项 | 描述 |
|--------|-------------|
| `SCAN_FILE` | 漏洞扫描导出文件的路径 |
| `--scanner`, `-s` | 扫描器类型(默认:`auto`) |
| `--no-color` | 禁用彩色终端输出 |
分析每台主机是否存在凭证扫描失败或缺失的迹象,并报告哪些主机存在凭证问题及其支持证据。
## 示例
**基本比较:**
```
$ vuln-diff compare old.nessus new.nessus
Vulnerability Scan Comparison
Previous scan: old.nessus
Latest scan: new.nessus
Scanner: nessus
Hosts
Previous: 4
Latest: 5
New: 1
Missing: 0
Credential issues: 5
Findings
New: 12
Fixed: 7
Still open: 43
Unverified: 3
Severity changed: 2
New findings by severity
Critical: 1
High: 3
Medium: 5
Low: 3
New Critical & High Findings
Host Plugin ID Plugin Name Severity Port CVEs
10.0.1.50 (db01) 12345 SSL Certificate Expired Critical 443 CVE-2024-1234
10.0.1.12 (web03) 98765 Apache Struts2 RCE High 8080 CVE-2023-50164, CVE-2023-50148
10.0.1.30 (app02) 54321 OpenSSH Key Exchange High 22 CVE-2023-48795
```
**生成所有报告格式:**
```
$ vuln-diff compare old.nessus new.nessus -o report.xlsx -j report.json --html report.html
```
**Qualys CSV 比较:**
```
$ vuln-diff compare --scanner qualys old_scan.csv new_scan.csv --output qualys_diff.xlsx
```
**带 JSON 输出的 CI/CD 门禁:**
```
$ vuln-diff compare prev.nessus latest.nessus --json diff.json --fail-on-new-critical
$ echo $?
3
```
**汇总单次扫描:**
```
$ vuln-diff summary scan.nessus
```
**检查凭证状态:**
```
$ vuln-diff credentials scan.nessus
```
## 比较逻辑
上一次扫描中的每一个发现都会使用稳定的身份密钥与最新扫描进行匹配(参见下方的[发现身份逻辑](#finding-identity-logic))。根据是否找到匹配项,发现被分类为以下类别:
| 分类 | 含义 |
|----------------|---------|
| **New** | 发现的身份存在于最新扫描中,但在上一次扫描中不存在。 |
| **Fixed** | 发现的身份存在于上一次扫描中,但在最新扫描中不存在,并且该主机已成功使用凭证重新扫描。 |
| **Still Open** | 两次扫描中都存在相同的发现身份,且严重性没有变化。 |
| **Unverified** | 最新扫描中不存在该发现,但工具无法确认其已修复(参见下方的 [FIXED 与 UNVERIFIED](#fixed-vs-unverified))。 |
| **Severity Changed** | 发现的身份在两次扫描中都存在,但严重性级别不同。跟踪为增加或减少,并附带 CVSS 详细信息。 |
| **Reopened** | **New** 的一个子集——该发现带有历史元数据(`last_fixed` 日期),暗示它之前已被修复但又重新出现。标记为低置信度。 |
同时也会跟踪主机:**New**、**Missing**、**Possibly Renamed**(通过 MAC 或 IP 匹配)以及 **Possible Duplicates**(在同一次扫描中共享 IP 或 MAC)。
## 发现身份逻辑
发现的身份计算方式为:
```
host_identity | protocol | port | plugin_id
```
其中 `host_identity` 源自主机识别策略 (`--host-key`):
- **auto**(默认):asset ID > MAC 地址 > FQDN > hostname > IP(选择最稳定的可用标识符)
- **hostname**:标准化的 hostname 或 FQDN
- **ip**:标准化的 IP 地址
- **asset-id**:扫描器分配的 asset ID
**为什么不仅仅使用 CVE?** 单个 CVE 可能出现在多台主机、端口和服务上。单个插件可能会报告多个 CVE。仅通过 CVE 进行匹配会在同一 CVE 影响不同服务或主机时产生误报。host+port+protocol+pluginID 复合密钥确保了发现是在特定主机的特定服务上的特定漏洞级别进行跟踪的——这正是修复跟踪所关注的。
## FIXED 与 UNVERIFIED
这是使 Vuln Scan Diff 有别于简单 diff 工具的核心区别。
只有在满足以下所有条件时,才会被判定为 **FIXED**:
- 该主机存在于最新扫描中。
- 该主机没有表现出凭证失败的迹象。
- 最新扫描是经过身份验证的(而非无凭证扫描)。
- 扫描看起来没有不完整(例如,主机以前有发现但现在为零,或者扫描持续时间极短)。
当不满足上述任何条件时,会被判定为 **UNVERIFIED**。具体原因包括:
- 该主机完全缺失于最新扫描中。
- 该主机的凭证扫描失败或被禁用。
- 最新扫描未经身份验证,且发现的严重性为中或更高。
- 该主机在上一次扫描中有发现,但在最新扫描中为零发现(表明扫描不完整而非完全修复)。
- 该主机的扫描持续时间极短。
UNVERIFIED 发现在被认定为已修复之前需要人工审查。它们会在终端报告、Excel 报告、HTML 报告和 JSON 输出中单独显示,因此绝不会意外地被计入已修复。
## Excel 报告
通过 `--output report.xlsx` 生成。该工作簿包含 10 个工作表:
| 工作表 | 内容 |
|-------|----------|
| Executive Summary | 主机数量、发现增减量、严重性分布、凭证扫描统计 |
| New Findings | 所有新发现,包含 host、plugin、severity、port、CVEs、CVSS、synopsis、solution |
| Fixed Findings | 所有确认已修复的发现,包含相同的列 |
| Still Open | 两次扫描中都存在且严重性未改变的所有发现 |
| Unverified | 所有无法确认已修复的发现,包含 unverified 的原因 |
| Severity Changes | 严重性增加或减少的发现,包含之前/当前的 CVSS |
| New Hosts | 出现在最新扫描中但在上一次扫描中未出现的主机 |
| Missing Hosts | 存在于上一次扫描中但在最新扫描中缺失的主机 |
| Credential Issues | 检测到凭证扫描失败的主机及其证据 |
| All Current Findings | 最新扫描中所有发现的完整清单 |
所有单元格均采用专业样式格式化(按严重性着色的标题、自动换行、自动调整列宽),并且每个值都经过了净化以防止公式注入。
## JSON Schema
通过 `--json report.json` 生成。结构如下:
```
{
"metadata": {
"tool_version": "1.0.0",
"scanner": "nessus",
"previous_scan": "old.nessus",
"latest_scan": "new.nessus",
"generated_at": "2025-01-15T10:30:00+00:00"
},
"summary": {
"previous_hosts": 4,
"latest_hosts": 5,
"new_hosts": 1,
"missing_hosts": 0,
"new_findings": 12,
"fixed_findings": 7,
"still_open": 43,
"unverified": 3,
"severity_changed": 2,
"credential_issues": 5,
"new_critical_findings": 1
},
"findings": {
"new": [
{
"host_ip": "10.0.1.50",
"host_hostname": "db01",
"plugin_id": "12345",
"plugin_name": "SSL Certificate Expired",
"severity": "critical",
"port": "443",
"protocol": "tcp",
"cves": ["CVE-2024-1234"],
"cvss_v2": null,
"cvss_v3": 9.8
}
],
"fixed": [...],
"still_open": [...],
"unverified": [...],
"severity_changed": [
{
"host_ip": "10.0.1.12",
"host_hostname": "web03",
"plugin_id": "54321",
"plugin_name": "Apache HTTP Server Version",
"severity": "high",
"port": "443",
"protocol": "tcp",
"cves": [],
"cvss_v2": null,
"cvss_v3": 7.5,
"previous_severity": "medium",
"current_severity": "high",
"direction": "increased"
}
]
},
"hosts": {
"new": [...],
"missing": [...],
"possible_renames": [...],
"credential_issues": [
{
"host_ip": "10.0.1.30",
"host_hostname": "app02",
"status": "likely",
"evidence": ["Authentication Failure - Local Checks Not Run"]
}
]
}
}
```
JSON 输出专为 CI/CD 流水线消费而设计。`--fail-on-new-critical` 标志为构建门禁提供了一种简单的退出代码机制。
## HTML 报告
通过 `--html report.html` 生成。特点:
- **自包含**:带有嵌入 CSS 和内联 JavaScript 的单文件。无外部依赖、无 CDN、无分析、无网络请求。
- **离线**:无需互联网连接即可直接在任何浏览器中打开。
- **可搜索**:每个表格都有一个搜索框,可按 host、plugin、CVE 等进行过滤。
- **可过滤**:每个发现表格上都有严重性过滤按钮(全部、严重、高、中、低、信息性)。
- **可展开的详细信息**:点击可显示每个发现的 synopsis、solution 和 description。
- **可打印**:打印样式表会隐藏交互控件并显示所有行。
- **安全性**:所有由扫描器控制的内容都经过 HTML 转义,以防止注入。
## 安全与隐私模型
Vuln Scan Diff 旨在安全地处理敏感漏洞数据:
- **无网络访问**:该工具从不发起网络请求。所有处理均在本地进行。
- **无遥测**:不会发送任何使用数据、扫描内容或元数据。
- **XXE 防护**:Nessus XML 使用 `defusedxml` 解析,可阻止实体扩展、DTD 处理和其他 XML 攻击向量。
- **公式注入防护**:写入 Excel 单元格的所有值都经过净化——以 `=`、`+`、`-` 或 `@` 开头的字符串都会添加单引号前缀。
- **HTML 转义**:HTML 报告中所有由扫描器控制的内容均通过 `html.escape(quote=True)` 进行转义。
- **密钥脱敏**:在详细终端模式下显示的插件输出会扫描常见的密钥模式(密码、API 密钥、bearer token、私钥、AWS 密钥),并替换为 `[REDACTED]`。
## 性能
- **流式 XML 解析**:Nessus `.nessus` 文件使用 `iterparse` 解析——元素会被增量处理和清除,因此内存中一次只保留一台主机的数据量。2 GB 的 `.nessus` 文件不需要 2 GB 的 RAM。
- **处理大型扫描**:已针对 100,000+ 个发现和 10,000+ 台主机进行了测试。
- **Qualys CSV**:使用 Python 的 `csv` 模块逐行处理。
## 已知限制
- 两个文件的扫描器类型必须相同。不支持将 Nessus 导出文件与 Qualys 导出文件进行比较。
- 主机重命名检测依赖于 MAC 和 IP 匹配。如果两者在扫描之间都发生了变化,该主机将同时显示为缺失和新增。
- 凭证分析基于规则,并使用 Nessus 特定的启发式方法(插件 ID、插件家族、输出模式)。它可能会产生误报,或遗漏细微的凭证失败。
- 用于重新打开发现检测的 `last_fixed` 日期仅在扫描器将其包含在导出文件中时可用。
- 与端口无关的发现(未与特定端口关联的发现)在身份密钥中使用空的端口组件。
- 严重性标准化将扫描器特定的严重性标签(例如,Nessus 严重性 0-4,Qualys 1-5)映射到通用标准,但无法考虑在扫描器中配置的自定义严重性阈值。
- 该工具比较的是两个时间点的导出文件。它不访问扫描器数据库或趋势数据。
## 开发设置
```
git clone https://github.com/your-org/vuln-scan-diff.git
cd vuln-scan-diff
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
## 测试
```
pytest
pytest --cov=src/vuln_scan_diff
pytest -m "not slow" # skip slow tests
```
测试套件包含跨 10 个模块的 249 个测试,使用合成的夹具文件(小型 `.nessus` XML 和 Qualys CSV 文件)——运行测试不需要真实的扫描数据。
## 贡献
1. Fork 该仓库。
2. 创建功能分支。
3. 为新功能编写测试。
4. 确保所有测试均通过 `pytest`。
5. 提交 pull request。
请保持更改的聚焦,并确保与现有的 CLI 标志和报告格式保持向后兼容。
## 许可证
MIT License。有关全文,请参见 [LICENSE](LICENSE)。
标签:Blue Team, DevSecOps, GPT, Nessus, PB级数据处理, Python, Qualys, 上游代理, 安全运维, 文档结构分析, 无后门, 漏洞管理, 逆向工具