分析 SBOM 以找出供應鏈漏洞
使用時機
- 新的法規要求(EO 14028、EU CRA)規定軟體交付必須進行 SBOM 分析
- 安全團隊需要透過掃描廠商提供的 SBOM 來評估第三方風險
- CI/CD 管線需要對產生的 SBOM 執行自動化漏洞檢查
- 事件應變需要判斷新揭露的 CVE 是否影響已部署的軟體
- 採購團隊需要對軟體採購進行供應鏈風險評估
請勿用於即時系統的執行期漏洞掃描;這類情況應使用容器掃描工具(Trivy、Grype CLI)或主機型漏洞掃描器(Nessus、Qualys)。
前置需求
- 格式為 CycloneDX JSON(v1.4+)或 SPDX JSON(v2.3+)的 SBOM 檔案
- Python 3.9+,並已安裝 requests、networkx 和 packaging 函式庫
- NVD API 金鑰(免費,取自 https://nvd.nist.gov/developers/request-an-api-key)以獲得較高的速率限制
- 可連線至 NVD API(https://services.nvd.nist.gov/rest/json/cves/2.0)的網路
- 選擇性:用於產生 SBOM 的 syft、用於交叉驗證的 grype
工作流程
步驟 1:產生 SBOM(若未提供)
使用 syft 從容器映像檔或專案目錄建立 SBOM:
# 從容器映像檔產生 CycloneDX JSON
syft alpine:latest -o cyclonedx-json > sbom-cyclonedx.json
# 從專案目錄產生 SPDX JSON
syft dir:/path/to/project -o spdx-json > sbom-spdx.json
# 從執行中的容器產生
syft docker:my-app-container -o cyclonedx-json > sbom.json
Syft 支援超過 30 種套件生態系,包括 npm、PyPI、Maven、Go modules、apt、apk 與 RPM。產生的 SBOM 包含套件名稱、版本、授權條款、CPE 識別碼與 PURL(Package URL)參考。
步驟 2:解析 SBOM 並萃取元件
解析 SBOM 以取出所有軟體元件及其識別碼:
CycloneDX JSON 結構:
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"components": [
{
"type": "library",
"name": "lodash",
"version": "4.17.20",
"purl": "pkg:npm/[email protected]",
"cpe": "cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*",
"licenses": [{"license": {"id": "MIT"}}]
}
],
"dependencies": [
{"ref": "pkg:npm/[email protected]", "dependsOn": ["pkg:npm/[email protected]"]}
]
}
SPDX JSON 結構:
{
"spdxVersion": "SPDX-2.3",
"packages": [
{
"name": "lodash",
"versionInfo": "4.17.20",
"externalRefs": [
{"referenceType": "purl", "referenceLocator": "pkg:npm/[email protected]"},
{"referenceType": "cpe23Type", "referenceLocator": "cpe:2.3:a:lodash:lodash:4.17.20:*:*:*:*:*:*:*"}
],
"licenseConcluded": "MIT"
}
],
"relationships": [
{"spdxElementId": "SPDXRef-express", "relatedSpdxElement": "SPDXRef-lodash",
"relationshipType": "DEPENDS_ON"}
]
}
步驟 3:將元件與 NVD CVE 資料庫建立關聯
查詢 NVD 2.0 API 以尋找每個元件的已知漏洞:
import requests
NVD_API = "https://services.nvd.nist.gov/rest/json/cves/2.0"
def search_cves_by_cpe(cpe_name, api_key=None):
params = {"cpeName": cpe_name, "resultsPerPage": 50}
headers = {"apiKey": api_key} if api_key else {}
resp = requests.get(NVD_API, params=params, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json().get("vulnerabilities", [])
def search_cves_by_keyword(keyword, version=None, api_key=None):
params = {"keywordSearch": keyword, "resultsPerPage": 50}
headers = {"apiKey": api_key} if api_key else {}
resp = requests.get(NVD_API, params=params, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json().get("vulnerabilities", [])
NVD API 支援透過 CPE 名稱(最精確)、關鍵字、CVE ID 與日期範圍進行搜尋。速率限制:無 API 金鑰時每 30 秒 5 次請求,有金鑰時每 30 秒 50 次請求。
步驟 4:建立相依性圖形並識別傳遞性風險
建立相依性的有向圖形以追蹤漏洞傳播:
import networkx as nx
def build_dependency_graph(sbom):
G = nx.DiGraph()
# 為每個元件新增節點
for comp in sbom["components"]:
G.add_node(comp["purl"], name=comp["name"], version=comp["version"])
# 從相依性關係新增邊
for dep in sbom.get("dependencies", []):
for child in dep.get("dependsOn", []):
G.add_edge(dep["ref"], child)
return G
傳遞性相依分析可找出並非直接宣告,而是透過直接相依的相依鏈被帶入的元件。位於深層傳遞性相依(例如 4 層深)中的漏洞仍然代表風險,但可能較難修復。
用於風險評估的關鍵圖形指標:
- 入度:有多少元件依賴於此元件(入度高 = 爆炸半徑大)
- 到根節點的最短路徑:與應用程式進入點的距離(越近 = 越容易被利用)
- 中介中心性:位於許多相依路徑上的元件(瓶頸風險)
步驟 5:計算風險分數
將漏洞資料彙總為元件風險分數與整體風險分數:
風險分數計算:
━━━━━━━━━━━━━━━━━━━━━━
元件風險 = 影響該元件的所有 CVE 的 CVSS 分數最大值
加權風險 = 元件風險 * 相依性因子
其中 相依性因子 = 1.0 + (0.1 * 入度)
(越多相依者 = 組織影響越大)
整體 SBOM 風險 = 所有元件風險的加權平均
權重依據相依性中心性
風險層級:
嚴重: CVSS >= 9.0 或已知已被利用(CISA KEV)
高: CVSS >= 7.0
中: CVSS >= 4.0
低: CVSS < 4.0
步驟 6:以 Grype 進行交叉驗證
使用 grype 獨立掃描 SBOM 並比較結果:
# 使用 grype 掃描 CycloneDX SBOM
grype sbom:sbom-cyclonedx.json -o json > grype-results.json
# 掃描 SPDX SBOM
grype sbom:sbom-spdx.json -o table
# 依嚴重性篩選
grype sbom:sbom-cyclonedx.json --only-fixed --fail-on critical
Grype 從 NVD、GitHub Security Advisories、Alpine SecDB、Red Hat、Debian、Ubuntu、Amazon Linux 與 Oracle 安全性資料庫提取漏洞資料,提供比單獨使用 NVD 更廣泛的覆蓋率。
步驟 7:產生合規報告
產出適合法規遵循的結構化報告:
SBOM 漏洞分析報告
====================================
SBOM 檔案: app-sbom-cyclonedx.json
格式: CycloneDX v1.5
分析日期: 2026-03-19
元件總數: 247
相依性總數: 1,842(直接:34,傳遞性:213)
漏洞摘要
嚴重: 3 個元件 / 5 個 CVE
高: 11 個元件 / 18 個 CVE
中: 27 個元件 / 41 個 CVE
低: 8 個元件 / 12 個 CVE
嚴重發現
1. [email protected]
CVE-2021-23337(CVSS 7.2)- 透過範本的命令注入
CVE-2020-28500(CVSS 5.3)- trimEnd 中的 ReDoS
相依元件:14 個(高爆炸半徑)
修復:升級至 4.17.21+
2. [email protected]
CVE-2021-44228(CVSS 10.0)- Log4Shell RCE [CISA KEV]
CVE-2021-45046(CVSS 9.0)- 不完全修復繞過
相依元件:8 個
修復:升級至 2.17.1+
相依性圖形風險
最多元件相依:[email protected](47 個相依元件)
最深鏈結:app -> framework -> adapter -> codec -> zlib(5 層)
瓶頸元件:3 個元件位於 >50% 的相依路徑上
授權合規
發現 Copyleft 授權:2 個(libxml2 中的 GPL-3.0、mongodb-driver 中的 AGPL-3.0)
商業發布前需要審查
關鍵概念
| 術語 | 定義 |
|---|---|
| SBOM | 軟體物料清單;軟體產品中所有元件、函式庫與相依性的正式清單 |
| CycloneDX | OWASP 維護的 SBOM 標準,支援 JSON、XML 與 protobuf 格式,包含相依性圖形與漏洞資料 |
| SPDX | Linux Foundation 的 SBOM 標準,專注於授權合規,支援套件、檔案與片段層級的詳細資料 |
| PURL | 套件 URL;一種標準化方案,用於跨生態系識別軟體套件(例如 pkg:npm/[email protected]) |
| CPE | 通用平台列舉;NIST 針對 IT 產品的命名方案,用於與 NVD CVE 資料進行關聯 |
| NVD | 國家漏洞資料庫;美國政府以 CVE 識別碼索引的漏洞資料儲存庫 |
| 傳遞性相依 | 並非直接宣告,而是透過直接相依的相依鏈被帶入的相依性 |
| CISA KEV | CISA 已知被利用的漏洞目錄;經確認在真實世界中正被積極利用的 CVE |
工具與系統
- syft(Anchore):開源 SBOM 產生器,支援 30 多種套件生態系與 CycloneDX/SPDX 輸出格式
- grype(Anchore):漏洞掃描器,可接受 SBOM 作為輸入並與多個公告資料庫進行關聯比對
- cyclonedx-python-lib:用於以程式方式建立、解析與驗證 CycloneDX SBOM 的 Python 函式庫
- lib4sbom:用於解析 SPDX 與 CycloneDX 格式 SBOM 的 Python 函式庫
- nvdlib:NVD 2.0 API 的 Python 封裝,支援 CVE 與 CPE 查詢,並包含速率限制管理
- OWASP Dependency-Track:用於持續 SBOM 分析、漏洞追蹤與政策執行的平台
常見情境
情境:Log4Shell 揭露後評估廠商軟體
背景:在 Log4Shell(CVE-2021-44228)揭露後,安全團隊需要判斷哪些廠商提供的應用程式含有易受攻擊的 log4j 版本。數家廠商已依據合約要求提供了 SBOM。
做法:
- 收集所有廠商的 SBOM(CycloneDX 或 SPDX JSON 格式)
- 解析每個 SBOM 並搜尋版本低於 2.17.1 的 log4j-core 元件
- 查詢 NVD API 以取得特定的 CVE(CVE-2021-44228、CVE-2021-45046、CVE-2021-45105)
- 建立相依性圖形以識別哪些應用程式元件依賴 log4j
- 計算爆炸半徑:有多少服務與端點暴露在外
- 產生按暴露程度與業務重要性排序的優先修復報告
- 透過 grype 對相同 SBOM 掃描結果進行交叉驗證
陷阱:
- 廠商 SBOM 可能不完整,遺漏內嵌 log4j 的陰影/捆綁 JAR 檔案
- SPDX 與 CycloneDX 的版本差異可能影響解析器相容性
- 在沒有 API 金鑰的情況下掃描數百個元件時,NVD API 速率限制可能拖慢分析速度
- SBOM 中的 CPE 名稱可能不完全符合 NVD 條目,需要模糊比對
- 即使 log4j 不是直接相依,傳遞性相依仍可能包含它
(前端 YAML 暫時宣告已省略)