固件清单(SBOM)入门:从“我到底用了什么”开始

做固件安全这么多年,我见过太多团队在漏洞爆发时手忙脚乱。最典型的一个场景:Log4j 漏洞出来那天,客户打电话问“你们的设备受影响吗?”——结果没人能立刻回答。为什么?因为没人知道自己固件里到底塞了哪些第三方库、什么版本、从哪里来的。

这就是 SBOM 要解决的问题。说白了,SBOM 就是一份“固件成分清单”,跟食品包装上的配料表一个道理。你吃的东西里有什么,得写清楚;你固件里有什么开源组件、什么版本、什么许可证,也得列明白。

SBOM 到底是什么?

SBOM 全称 Software Bill of Materials,软件物料清单。它记录了一个软件或固件中所有组件的详细信息,包括:

  • 组件名称——比如 OpenSSL、zlib、BusyBox
  • 版本号——比如 1.1.1t、1.2.13、1.36.1
  • 许可证类型——Apache 2.0、GPL v3、MIT
  • 依赖关系——谁依赖谁,层级关系
  • 哈希值——用于验证组件完整性
  • 供应商/来源——从哪个仓库或渠道引入的

我个人习惯把 SBOM 分成两类:构建时 SBOM 和 运行时 SBOM。构建时 SBOM 在编译打包阶段生成,记录的是你“打算”放进去的东西;运行时 SBOM 则从实际运行的设备上提取,反映的是“真正”跑起来的东西。两者可能有差异——嗯,这里要注意,我遇到过好几次构建时 SBOM 里写的是 OpenSSL 1.1.1,结果设备上实际跑的是 1.0.2,因为构建脚本里有个隐藏的覆盖逻辑。

核心要点:SBOM 不是一张静态表格,它是固件安全管理的“数据底座”。没有 SBOM,漏洞响应就是盲人摸象。

SBOM 标准格式:SPDX 和 CycloneDX

你想想看,如果每个人写 SBOM 的格式都不一样,那上下游怎么交换数据?所以业界搞了两个主流标准:SPDX 和 CycloneDX。我两个都用过,各有侧重。

对比维度 SPDX CycloneDX
发起方 Linux 基金会 OWASP
核心定位 许可证合规 + 组件清单 安全漏洞 + 组件清单
文件格式 Tag:Value、JSON、YAML、RDF/XML JSON、XML
漏洞关联 支持,但非核心 原生支持,内置漏洞评分
适用场景 法律合规、许可证审计 安全运维、漏洞管理
版本 SPDX 2.3(当前稳定版) CycloneDX 1.5(当前稳定版)

我个人的经验是:如果你的团队更关注许可证合规(比如公司法务天天盯着 GPL 传染性),SPDX 是首选;如果你更关心安全漏洞(比如 IoT 设备经常被 CVE 追着打),CycloneDX 更顺手。当然,两个都支持也没问题——我在一个项目里同时输出两种格式,给不同部门用。

SPDX 示例片段

SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: firmware-router-v2.1.0
Creator: Tool: sbom-tool-0.1.0

##### 组件描述
PackageName: OpenSSL
SPDXID: SPDXRef-Package-OpenSSL
PackageVersion: 1.1.1t
PackageDownloadLocation: https://www.openssl.org/source/openssl-1.1.1t.tar.gz
PackageLicenseConcluded: OpenSSL
PackageChecksum: SHA1: 123abc456def789

CycloneDX 示例片段

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
  "components": [
    {
      "type": "library",
      "name": "OpenSSL",
      "version": "1.1.1t",
      "purl": "pkg:generic/openssl@1.1.1t",
      "licenses": [
        {
          "license": {
            "id": "OpenSSL"
          }
        }
      ],
      "hashes": [
        {
          "alg": "SHA-1",
          "content": "123abc456def789"
        }
      ]
    }
  ]
}

小技巧:CycloneDX 的 purl(Package URL)字段非常有用。它把组件定位标准化了,配合漏洞数据库查询时,直接拿 purl 去匹配 CVE,比靠名字和版本号拼字符串靠谱得多。

SBOM 生成工具:选对工具,事半功倍

工具这块我踩过不少坑。早期我手动维护 Excel 表格,结果版本一多就乱套。后来用脚本自动生成,但格式不标准,上下游没法对接。现在市面上成熟的工具已经很多了,我挑几个常用的说说。

工具名称 支持格式 适用阶段 特点
syft SPDX、CycloneDX 构建时 / 容器镜像 速度快,支持容器和文件系统扫描
cyclonedx-bom CycloneDX 构建时 OWASP 官方工具,支持多种语言
SPDX Tools SPDX 构建时 / 手动 Linux 基金会官方,功能全面但稍重
FOSSology SPDX 源码扫描 许可证扫描能力强,适合合规审计
tern SPDX 容器镜像 专注容器层分析,能追溯每一层

我个人最常用的是 syft,原因很简单:一条命令就能扫完整个固件文件系统,输出 SPDX 或 CycloneDX 格式,干净利落。比如:

# 扫描一个固件解压后的根目录
syft dir:/path/to/firmware-root/ -o spdx-json=output.spdx.json

# 扫描一个 Docker 镜像
syft my-image:latest -o cyclonedx-json=output.cdx.json

避坑指南:我曾经在一个项目里直接用 syft 扫了完整的固件镜像(没解压),结果它把整个 squashfs 当成一个二进制文件处理,只识别出一个“unknown”组件。后来才意识到——必须先解压固件,再扫描文件系统。很多固件用了压缩或加密的文件系统,工具没法直接穿透。

SBOM 的核心逻辑:一张图看懂

下面这张图是我自己总结的 SBOM 核心逻辑。它把“固件成分”到“风险修复”的链路串起来了。你仔细看,SBOM 不是终点,它是起点。

SBOM 核心逻辑:从成分到风险 固件源码 / 二进制 解压后的文件系统 构建环境 / 容器 依赖管理工具 第三方包仓库 npm / pip / maven / conan SBOM 生成工具 syft / cyclonedx-bom / SPDX Tools / tern SPDX 格式 许可证合规 + 组件清单 CycloneDX 格式 安全漏洞 + 组件清单 漏洞匹配 CVE 数据库交叉查询 许可证审计 合规风险排查 版本追踪 过期组件升级计划 SBOM 不是终点,它是安全管理的起点。没有 SBOM,漏洞响应就是盲人摸象。

生成 SBOM 的常见误区

最后聊几个我实际踩过的坑,希望能帮你少走弯路。

  • 误区一:只扫一次就完事。固件版本迭代很快,每次构建都应该生成新的 SBOM。我见过一个团队用半年前的 SBOM 去响应新漏洞,结果发现组件版本早就变了。
  • 误区二:只扫应用层,忽略系统层。很多嵌入式固件用了 BusyBox、glibc、OpenSSL 这些系统级组件,它们往往是漏洞高发区。SBOM 必须覆盖到系统层。
  • 误区三:SBOM 格式选一个就行。如果你的客户或监管方要求 SPDX,而你只给了 CycloneDX,对接就会出问题。我建议至少支持两种格式输出,或者跟上下游提前对齐。
  • 误区四:生成完就扔一边。SBOM 需要跟漏洞数据库联动,定期重新扫描。新 CVE 每天都在出,昨天的 SBOM 今天可能就过时了。

我的习惯:每次固件发布,我都会把 SBOM 文件跟固件镜像一起归档,打上版本标签。这样三个月后客户问“那个版本用了什么”,我翻出来就能回答,不用重新解压分析。