固件清单(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 的常见误区
最后聊几个我实际踩过的坑,希望能帮你少走弯路。
- 误区一:只扫一次就完事。固件版本迭代很快,每次构建都应该生成新的 SBOM。我见过一个团队用半年前的 SBOM 去响应新漏洞,结果发现组件版本早就变了。
- 误区二:只扫应用层,忽略系统层。很多嵌入式固件用了 BusyBox、glibc、OpenSSL 这些系统级组件,它们往往是漏洞高发区。SBOM 必须覆盖到系统层。
- 误区三:SBOM 格式选一个就行。如果你的客户或监管方要求 SPDX,而你只给了 CycloneDX,对接就会出问题。我建议至少支持两种格式输出,或者跟上下游提前对齐。
- 误区四:生成完就扔一边。SBOM 需要跟漏洞数据库联动,定期重新扫描。新 CVE 每天都在出,昨天的 SBOM 今天可能就过时了。
我的习惯:每次固件发布,我都会把 SBOM 文件跟固件镜像一起归档,打上版本标签。这样三个月后客户问“那个版本用了什么”,我翻出来就能回答,不用重新解压分析。