Staffardprobable495/fleet-cve-scanner
GitHub: Staffardprobable495/fleet-cve-scanner
一款无需 agent 的开源 PowerShell CVE 扫描器,将 RMM 软件清单与多个权威安全数据源比对,帮助 IT 团队评估终端群漏洞风险及修复优先级。
Stars: 0 | Forks: 0
# fleet-cve-scanner
一款开源的单脚本 CVE 扫描器,专为 RMM 管理的终端群设计。纯
PowerShell 7 编写,无需 agent,无需设备,无许可费用。
它针对你管理的每一台终端上的每一款软件回答一个问题:
**当前安装的版本是否存在已知漏洞,以及紧急程度如何?**
——通过将你 RMM 的软件清单与免费、权威的安全源进行比对:
- **NIST NVD** — 已知 CVE 及受影响版本范围
- **CISA KEV** — 在野被积极利用(绕过分数阈值)
- **EPSS** — 未来 30 天内的漏洞利用概率
- **CISA SSVC** — Act / Attend / Track 优先级决策
- **MSRC** — 证明 Microsoft 补丁已涵盖该漏洞的证据
- **endoflife.date** — 永远不会再收到补丁的软件
输出内容包括:针对每个设备的 CSV 报告、包含 SLA 计时和周环比变更事件的 SQLite 历史记录、自包含的 HTML 仪表盘,以及可选的 Markdown 暴露报告。
## 状态
**v1.0.0。** 该引擎是从一个曾在生产环境中针对 Windows 终端群每周运行的扫描器经过净室移植而来的(参见
[docs/HISTORY.md](docs/HISTORY.md))。在此版本发布之前,两者曾针对同一个实时终端群进行运行并逐行对比:在数以万计的检测结果中,凡是两者识别出相同的 *(设备, 软件, 版本)*,每一个计算出的字段——状态、CVSS、严重性、KEV 标志、SSVC 决策、修复版本——都完全一致。
该对比涵盖了 **verdict**(判定)逻辑。它并未测试历史记录/SLA 层,也不能替代在你自己的环境中对该工具进行的验证。在信任它之前,请阅读 [docs/known-limitations.md](docs/known-limitations.md)
——它的撰写初衷是诚实客观,而非自夸。
## 快速开始
你需要 [PowerShell 7](#requirements) (`pwsh`)。有两种运行方式:
### A. NinjaOne 实时扫描
```
cp config.example.json config.json
# 编辑 config.json:填写 ninjaone.client_id / client_secret / base_url,
# 设置 output.report_dir,以及(推荐)nvd.api_key
pwsh -File fleet-cve-scan.ps1
```
### B. 离线 —— 任何 RMM、SCCM 或 Intune(无需 API 访问权限)
将你的清单导出为包含 `hostname, software, version`(可选 `device_id, os`)列的 CSV,然后:
```
cp config.example.json config.json # only output.report_dir is needed here
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv
```
`-InputCsv` 会发起 **零次** NinjaOne 调用——没有 OAuth,没有 API。它仍然会查询
NVD 和其他公开源,因此你仍然需要在
`config.json` 中配置 `nvd.api_key` 并指定有效的 `output.report_dir`。有关输入契约和
各 RMM 的导出方法,请参阅 [docs/rmm-adapters.md](docs/rmm-adapters.md)。
### 安装前置条件
- **PowerShell 7** — macOS: `brew install powershell`; Windows:
`winget install Microsoft.PowerShell`; Debian/Ubuntu: 从
Microsoft 软件包仓库 (`packages.microsoft.com`) 安装。
- **sqlite3**(可选,支持历史记录/趋势分析)— macOS: 已预装或
`brew install sqlite`; Debian/Ubuntu: `apt-get install sqlite3`; Windows:
从 sqlite.org 下载 SQLite "tools" 工具包,并将 `sqlite3.exe` 放入
`PATH` 或脚本同级目录下。没有它扫描依然可以运行;`first_seen`
会回退为运行日期。
## 环境要求
- **PowerShell 7**(推荐 7.4+)。并行扫描需要 7.0+。
- **sqlite3** — 可选;启用历史数据库、趋势分析和 SLA
`first_seen` 追踪。如果没有此组件,这些功能将被跳过,但扫描仍会完成。
- **网络出口** 访问 NVD、CISA (KEV)、`epss.empiricalsecurity.com`
(EPSS 批量评分主机 —— FIRST 将 EPSS 数据托管转移到了此处)、MSRC、
endoflife.date 和 GitHub raw (CVE Program `cvelistV5`) —— 以及实时模式下的 NinjaOne
API。
- **凭据** — 实时模式需要 NinjaOne OAuth2 client id/secret;离线
模式仅需清单 CSV。从技术上讲,NVD API key 是可选的,但在
终端群规模下**实际上是必需的**:NVD 允许使用 key 时每 30 秒请求 50 次,无
key 则仅为 5 次。扫描器会检测你拥有哪种情况并
进行相应的限流,因此无 key 运行虽然正确,但速度会慢约 10 倍 —— 适合
少量产品,但对于真实的终端群来说则不切实际。
[Key 是免费的。](https://nvd.nist.gov/developers/request-an-api-key)
## 配置参考
配置文件为 JSON 格式(默认为脚本旁的 `config.json`;可通过
`-ConfigPath` 覆盖)。从 [`config.example.json`](config.example.json) 开始。以下是扫描器读取的每一个
key、省略时的默认值及其作用:
### `ninjaone`(实时模式)
| Key | Default | Purpose |
| --- | --- | --- |
| `ninjaone.client_id` | `""` | NinjaOne API OAuth2 client id。实时扫描必填;使用 `-InputCsv` 时留空。 |
| `ninjaone.client_secret` | `""` | NinjaOne API OAuth2 client secret。 |
| `ninjaone.base_url` | — (示例中包含 `https://app.ninjarmm.com`) | NinjaOne API base URL;实时扫描必填,无内置回退机制。请使用你所在区域的主机 (例如 `eu.ninjarmm.com`, `oc.ninjarmm.com`)。 |
### `nvd`
| Key | Default | Purpose |
| --- | --- | --- |
| `nvd.api_key` | `""` | NVD API key,作为 `apiKey` 标头发送。留空则以未验证身份运行,这会将 `nvd_rate_limit` 和 `nvd_min_spacing_ms` 的默认值降至 NVD 匿名上限 (4/30s, 6500ms),而不是验证身份后的上限 (48/30s, 700ms)。免费,且快约 10 倍。 |
### 扫描与评分
| Key | Default | Purpose |
| --- | --- | --- |
| `cvss_threshold` | `7.0` | 被评分的 CVE 被报告为 VULNERABLE(存在漏洞)的最低 CVSS 基础分数。KEV 列出的 CVE 会绕过此下限。 |
| `ssvc_mission_prevalence` | `high` | SSVC Mission Prevalence(任务普遍性)利益相关者输入 (`low` / `medium` / `high`)。 |
| `ssvc_public_wellbeing` | `high` | SSVC Public Well-being(公共福祉)利益相关者输入 (`low` / `medium` / `high`)。 |
### 缓存与限流
| Key | Default | Purpose |
| --- | --- | --- |
| `nvd_cache_ttl_days` | `7` | NVD 结果(按软件名称)在重新查询前保持缓存的时长(天数)。 |
| `nvd_cache_flush_every` | `500` | 每完成 N 个项目就将 NVD 缓存检查点保存到磁盘,这样即使扫描被终止也能保留其获取的数据。设为 `0` 则禁用(扫描结束时的保存仍会运行)。 |
| `nvd_min_spacing_ms` | 有 API key 时为 `700`,无则为 `6500` | NVD 调用之间的最小毫秒间隔。**核心绑定速率约束** —— 请优先调整此项。 |
| `nvd_rate_limit` | 有 API key 时为 `48`,无则为 `4` | 每 30 秒 NVD 调用的滑动窗口上限(托底机制)。下限为 1。 |
| `parallel_throttle` | `20` | `ForEach-Object -Parallel` 的 runspace 数量。无论何种情况,NVD 调用都是串行化的;此项仅限制缓存命中时的并发。下限为 1。 |
| `kev_cache_ttl_hours` | `24` | 缓存的 CISA KEV 目录的 TTL。 |
| `epss_cache_ttl_hours` | `24` | 缓存的 FIRST.org EPSS 评分文件的 TTL。 |
| `eol_cache_ttl_days` | `7` | 缓存的 endoflife.date 查询的 TTL。 |
| `msrc_cache_ttl_days` | `30` | 缓存的 MSRC CVRF 数据的 TTL。 |
| `cvelist_cache_ttl_days` | `7` | 缓存的 CVE Program (`cvelistV5`) / CISA-ADP SSVC 数据的 TTL。 |
| `cvelist_fetch_budget` | `300` | 每次运行中用于 SSVC/vulnrichment 富集的最大 CVE 记录获取次数;其余的推迟到下一次运行。 |
`nvd_min_spacing_ms`、`nvd_rate_limit` 和 `parallel_throttle` 会被
扫描器读取,但并未出现在 `config.example.json` 中 —— 仅在你需要
调整时才添加它们。请参阅 [docs/rate-limiting.md](docs/rate-limiting.md)。
### 富集与 SLA
| Key | Default | Purpose |
| --- | --- | --- |
| `epss_spike_threshold` | `0.10` | 某个漏洞检测的 EPSS 评分在两次扫描之间上升的最小幅度,达到该值即发出 `EPSS_SPIKE` 变更事件。 |
| `sla_days_critical` | `14` | CRITICAL(严重)漏洞检测的 SLA 时限(从 `first_seen` 起的天数)。 |
| `sla_days_high` | `30` | 其他已评分严重等级的 SLA 时限。 |
| `denied_software_csv` | `""` | 用于可选的违规软件 CSV 交叉比对的路径。留空则禁用检查。 |
### `output`
| Key | Default | Purpose |
| --- | --- | --- |
| `output.report_dir` | *(必填)* | 存放所有输出的目录 —— CSV、历史数据库、仪表盘和缓存。 |
| `output.wiki_security_page` | `""` | 指向 Markdown 暴露页面的可选路径。仅在设置后才会写入。 |
| `output.dashboard_share` | `""` | 每次运行时接收仪表盘副本的可选路径/共享。留空 = 仅限本地。 |
## 输出内容
以下所有内容都将存放在 `output.report_dir` 中。
- **`vuln-report-YYYY-MM-DD.csv`** —— 每个漏洞检测的报告。28 列
(schema 见下文)。
- **`vuln-history.db`** —— SQLite 历史记录(需要 `sqlite3`):`runs`(每次运行的汇总),`findings`(针对每个 `(device, software, cve)` 的未解决漏洞历史,包含 `first_seen` / `last_seen` / `resolved_date` / `epss_score`),以及
`changes`(带有类型的周环比事件)。
- **`cve-dashboard.html`** —— 自包含的 HTML 仪表盘(内联了
图表库,无外部请求)。可选择性地复制到 `output.dashboard_share`。
- **`eol-report-YYYY-MM-DD.csv`** —— 当检测到生命终结(end-of-life)的软件家族时写入此文件。
- **`denied-report-YYYY-MM-DD.csv`** —— 当配置了 `denied_software_csv` 并且发生匹配时写入此文件。
- **缓存文件** —— `nvd-cache.json`、`kev-cache.json`、`epss-cache.json`、
`eol-cache.json`、`msrc-cache.json`、`cvelist-cache.json`(均被 gitignore 忽略)。
- **Markdown 暴露页面** —— 位于 `output.wiki_security_page`,仅在设置后生成。
### CSV schema(28 列)
列集合是固定的,并由逻辑门强制执行。顺序如下:
| # | Column | Meaning |
| --- | --- | --- |
| 1 | `device_id` | RMM 设备 ID(或 `-InputCsv` 模式下确定性的合成 ID)。 |
| 2 | `hostname` | 设备主机名。 |
| 3 | `os` | 操作系统字符串(可能为空)。 |
| 4 | `software` | 清单中的产品名称。 |
| 5 | `installed_version` | 清单中的版本字符串。 |
| 6 | `cve_id` | CVE 标识符。 |
| 7 | `cvss_score` | CVSS 基础分数(如果没有则为 `0`)。 |
| 8 | `severity` | CVSS 严重性标签。 |
| 9 | `cvss_version` | 生成该分数的 CVSS 标准 (`4.0`/`3.1`/`3.0``2.0`/空白)。 |
| 10 | `epss_score` | FIRST.org EPSS 概率(如果目录中不存在该 CVE 则为空)。 |
| 11 | `epss_percentile` | EPSS 百分位(如果不存在则为空)。 |
| 12 | `ssvc_exploitation` | 原始 CISA SSVC 漏洞利用选项 (`none`/`poc`/`active`;无 ADP 数据则为空白)。 |
| 13 | `ssvc_decision` | SSVC 决策 `Act`/`Attend`/`Track`(无 ADP 数据则为空白)。 |
| 14 | `status` | `VULNERABLE` / `PATCHED` / `UNKNOWN`。 |
| 15 | `published_date` | CVE 发布日期。 |
| 16 | `unknown_reason` | 某行为 UNKNOWN 的原因 (`SUSPECT_VERSION` / `NO_VERSION_BOUND` / `VERSION_PARSE_ERROR`;否则为空白)。 |
| 17 | `is_kev` | 当 CVE 存在于 CISA KEV 目录中时为 `True`。 |
| 18 | `kev_due_date` | CISA KEV 修复截止日期。 |
| 19 | `kev_ransomware_use` | KEV 勒索软件活动使用标志。 |
| 20 | `fix_version` | 计算出的修复边界版本。 |
| 21 | `fix_boundary_inclusive` | 修复边界是否为包含性质。 |
| 22 | `advisory_url` | 供应商公告或 NVD 详细信息 URL。 |
| 23 | `patch_evidence` | 当 Microsoft UNKNOWN 被翻转为 PATCHED 时的证据。 |
| 24 | `triage_status` | 来自 `triage.json` 的分类处置状态(如果没有则为空白)。 |
| 25 | `triage_note` | 分类备注。 |
| 26 | `first_seen` | 首次观察到该漏洞的日期(来自历史数据库)。 |
| 27 | `sla_due` | SLA 截止日期。 |
| 28 | `sla_breach` | SLA 是否已被违反。 |
## 计划任务
`Run-WeeklyScan.ps1` 是一个用于计划任务运行的包装脚本:它会从
脚本自身目录运行扫描器,将 **所有** 输出流捕获到位于
`