SBOM 依赖成分扫描实战:syft 与 grype 配置

SBOM 依赖成分扫描是当前软件供应链安全最基础的一步。SBOM(Software Bill of Materials,软件物料清单)把镜像、源码目录、主机系统里全部第三方组件及其版本显式列出来,再交给漏洞引擎比对漏洞库,让「到底用了哪些组件、哪些组件带已披露漏洞」从猜测变成可查询的事实。本文给出 SBOM 依赖成分扫描的完整落地配置,使用 syft 生成清单、grype 做漏洞比对,并接入 CI 流水线闸门。

适用场景

  • 业务镜像上线前需要确认基础镜像与依赖包中没有未修复的高危漏洞。
  • 出现新披露的组件漏洞(压缩库、日志库、加密库、JSON 解析库)时,需要快速判断自有资产是否受影响。
  • 客户或监管要求交付软件时附带组件清单与漏洞报告。
  • 多团队共用基础镜像,需要建立「谁引入了哪个组件」的可追溯链路。
  • 构建机、私有仓库、静态站点等制品需要定期做成分盘点。

前置条件

  • Linux 主机或构建机,具备 Docker 或 containerd 环境(扫描镜像时需要拉取镜像)。
  • syft 1.x 与 grype 1.x,两者的清单格式与漏洞库机制互相对应。
  • 能访问 grype 官方漏洞库下载地址,或已搭建内网镜像源。
  • 源码项目建议保留锁文件(package-lock.json、poetry.lock、go.sum、Gemfile.lock),缺少锁文件时版本解析精度会明显下降。
  • CI 流水线需要可写的工作目录,用于存放生成的 SBOM 与扫描报告。

原理说明

整套流程分两步,职责分离,这是 SBOM 相比「直接扫镜像」的核心差异:

  • 成分识别:syft 遍历目标(容器镜像层、文件系统目录、归档包),解析包管理器元数据(dpkg、rpm、apk、npm、pip、gem、go module),输出组件名称、版本、PURL 与许可证信息。
  • 漏洞比对:grype 读取 SBOM 或直接扫描目标,把每个组件的 PURL/CPE 与漏洞库(NVD、GitHub Advisory、发行版安全公告)按版本区间匹配,输出漏洞 ID、严重级别、修复版本与 EPSS 分数。

输出格式优先选 CycloneDX 或 SPDX JSON,两者都是行业通用格式,能被制品仓库、准入策略与合规平台直接消费。SBOM 的价值是「扫一次、查多次」:清单落盘后,新漏洞披露时无需重新拉镜像,只用新数据库重跑比对即可。相比先做一次镜像扫描,这种方式的响应速度更快、覆盖历史版本更完整。

操作步骤

1. 安装 syft 与 grype

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

syft version
grype version
grype db update

内网环境可下载对应架构的 tar.gz 包,解压后把二进制放入 /usr/local/bin。两个工具共用同一套漏洞库更新机制,离线环境需先在有网机器执行 grype db update,再同步本地库目录。

2. 生成 SBOM 清单

# 扫描容器镜像,输出 CycloneDX JSON
syft registry.example.com/app/web:v1.4.2 -o cyclonedx-json=sbom-web-v1.4.2.cdx.json

# 同时输出人类可读的表格清单,便于人工核对
syft registry.example.com/app/web:v1.4.2 -o table

# 扫描源码目录,依赖锁文件会被解析
syft dir:./ -o spdx-json=sbom-src.spdx.json

# 扫描主机文件系统,盘点系统包
syft / -o cyclonedx-json=sbom-host.cdx.json

镜像建议使用 registry 路径 + 精确 tag 或 digest,不要用 latest,否则清单与实际制品无法对应。生产环境推荐固定 digest:syft registry.example.com/app/web@sha256:xxxx。清单文件命名带上镜像名与版本,便于长期归档检索。

3. 用 grype 做漏洞比对

# 直接扫描镜像,遇到高危即返回非零退出码
grype registry.example.com/app/web:v1.4.2 --fail-on high

# 基于已生成的 SBOM 比对,无需再次拉镜像
grype sbom:./sbom-web-v1.4.2.cdx.json -o table

# 输出 JSON 报告供流水线解析
grype sbom:./sbom-web-v1.4.2.cdx.json -o json > grype-report.json

# 只看有官方修复版本的漏洞,过滤暂时无解的条目
grype sbom:./sbom-web-v1.4.2.cdx.json --only-fixed

关键参数 –fail-on 决定「什么级别的问题会让流水线失败」,可选 negligible/low/medium/high/critical。建议生产分支设为 high,特性分支设为 critical,避免开发阶段被低危问题阻塞。历史版本可用 --only-fixed 先做一轮可修复漏洞清理,把无法修复的条目单独归档跟踪。

4. 接入 CI 流水线闸门

#!/bin/bash
set -euo pipefail
IMAGE="registry.example.com/app/web:${CI_COMMIT_SHORT_SHA}"

syft "$IMAGE" -o cyclonedx-json=sbom.json
grype sbom:./sbom.json -o json > grype.json

CRIT=$(jq '[.matches[] | select(.vulnerability.severity=="Critical")] | length' grype.json)
HIGH=$(jq '[.matches[] | select(.vulnerability.severity=="High")] | length' grype.json)
echo "Critical=$CRIT High=$HIGH"

if [ "$CRIT" -gt 0 ]; then
  echo "存在严重漏洞,阻断发布"
  exit 1
fi

把 sbom.json 作为构建产物归档,与镜像 digest 一并入库。后续拿到任意漏洞 ID,都能反查受影响的镜像版本区间,这是纯镜像扫描做不到的。若流水线使用 GitLab CI 或 GitHub Actions,可把上述脚本封装成一个 job,并把 JSON 报告作为 artifacts 上传。

配置验证

  • 清单完整性:对同一镜像连续跑两次 syft,对比组件数量是否一致。数量为 0 或明显偏低,说明包管理器元数据未被识别,需检查扫描目标路径是否包含应用层目录。
  • 漏洞库时效:执行 grype db status,确认数据库构建时间在 24 小时以内。
  • 已知样本验证:故意构建一个含旧版组件的测试镜像,确认 grype 能报出对应漏洞 ID,验证整条链路真正生效,而不是只生成了空报告。
  • 闸门有效性:向流水线提交一个含高危漏洞的镜像,确认构建被 --fail-on high 或统计逻辑正确阻断,而不是只输出日志后放行。
  • 格式合法性:用 jq . sbom.json 或用 CycloneDX 官方校验工具确认结构合法,能被下游平台解析。

常见问题

FAQ 1:扫描出几百个漏洞,如何确定修复顺序?

按四个维度排序,而不是只看 CVSS 分数。第一,是否有在野利用记录或已进入 KEV 目录,这类必须优先;第二,是否存在官方修复版本,无补丁漏洞只能靠隔离与虚拟补丁缓解,优先级低于可一键升级的;第三,组件是否被实际调用,未被业务路径触达的库风险显著降低;第四,EPSS 分数,它预测未来 30 天内被利用的概率,分数高的先修。实际执行时先批量升级基础镜像与直接依赖,通常一轮升级就能消掉大部分噪声,剩下少量条目再逐个评估。

FAQ 2:出现误报或漏报怎么办?

误报主要来自版本区间匹配不精确,典型场景是发行版回移补丁:上游版本号没变但漏洞已修复,工具仍按上游版本判定为受影响。处理方式是维护忽略清单并强制填写原因与到期时间,同时优先采用发行版专用数据源。漏报常见三类原因:工具不支持的语言生态、编译进二进制而不带元数据的静态依赖、以及漏洞库尚未收录。对应措施是保持 grype db update 最新、对 Go 与 Rust 等静态编译组件补充二进制识别、并在流水线之外按季度做一次人工成分核对。忽略清单必须复审,否则会长期掩盖真实风险。

总结

SBOM 依赖成分扫描的落点很清晰:syft 把制品里的第三方组件变成可查询清单,grype 把清单变成漏洞结论,–fail-on 与严重级别统计把结论变成发布闸门。落地顺序建议先覆盖对外发布的镜像与生产主机,再接 CI 闸门,最后补齐归档与追溯链路。清单必须作为构建产物长期保存,否则新漏洞披露时无法在分钟内回答「哪些版本受影响」这个最关键的问题。