boostedchaos/fleet-cve-scanner

GitHub: boostedchaos/fleet-cve-scanner

一款基于 PowerShell 7 的单脚本开源 CVE 扫描器,通过将 RMM 软件清单与多个权威安全数据源交叉比对,帮助运维团队评估终端设备群的漏洞风险与修复紧迫度。

Stars: 9 | Forks: 1

# 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** 逻辑。它并未测试 history/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 密钥在技术上说是可选的,但在 设备群规模下**实际上是必需的**:NVD 允许有密钥的情况下每 30 秒 50 次请求,无密钥则仅允许每 30 秒 5 次。扫描器会检测你拥有的凭证并 相应地进行限流,因此无密钥运行是正确的,但速度大约慢 10 倍 —— 对于少数产品来说尚可,对于真实的设备群则不切实际。 [密钥是免费的。](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` header 发送。留空则以未认证方式运行,这会将 `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`,无 key 时为 `6500` | NVD 调用之间的最小毫秒间隔。**具有约束力的速率限制** —— 首先调整此项。 | | `nvd_rate_limit` | 有 API key 时为 `48`,无 key 时为 `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_SPIKE` 变更事件所需的 EPSS 分数最小上升幅度。 | | `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`** — 检测到生命周期结束(EOL)的软件系列时写入。 - **`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_percent` | EPSS 百分位(如果不存在则为空)。 | | 12 | `ssvc_exploitation` | 原始 CISA SSVC exploitation 选项(`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` 是用于计划运行的包装器:它会从 自己的目录运行扫描器,将 **所有** 输出流捕获到 `