4. SBOM与漏洞库关联:NVD数据库查询、OSV数据库集成、GHSA数据库对接
SBOM 拿到手了,组件清单列得清清楚楚。然后呢?
你得知道这些组件有没有漏洞。说白了,就是把 SBOM 里的每一个组件名、版本号,拿去跟已知的漏洞数据库做匹配。这一步做不好,后面所有的优先级排序都是空中楼阁。
我个人习惯把漏洞库分成三类:官方的、社区的、生态的。今天咱们就把这三个典型代表——NVD、OSV、GHSA——挨个说透。
核心逻辑: SBOM 中的每个组件(purl/cpe + 版本) → 查询漏洞数据库 → 返回 CVE 列表及评分 → 进入优先级排序流程。
4.1 NVD 数据库查询:绕不开的官方数据源
NVD(National Vulnerability Database)是美国 NIST 维护的官方漏洞库。几乎所有公开的 CVE 都会在这里收录。但说实话,它也有让人头疼的地方。
查询方式:
- REST API: 官方提供
https://services.nvd.nist.gov/rest/json/cves/2.0接口 - 参数: 支持按 CPE、CVE ID、时间范围、关键词过滤
- 速率限制: 无 API Key 时每小时 5 次请求,有 Key 时每小时 100 次
我的经验: 千万别在 CI/CD 流水线里直接实时查 NVD。速率限制会让你崩溃。我一般每天同步一次全量数据到本地,或者用缓存层。
代码示例:Python 查询 NVD API
import requests
import time
def query_nvd_by_cpe(cpe_string, api_key=None):
"""
根据 CPE 查询 NVD 漏洞
"""
base_url = "https://services.nvd.nist.gov/rest/json/cves/2.0"
params = {
"cpeName": cpe_string,
"resultsPerPage": 20
}
headers = {}
if api_key:
headers["apiKey"] = api_key
try:
resp = requests.get(base_url, params=params, headers=headers, timeout=30)
resp.raise_for_status()
data = resp.json()
vulnerabilities = data.get("vulnerabilities", [])
results = []
for vuln in vulnerabilities:
cve_item = vuln.get("cve", {})
cve_id = cve_item.get("id", "")
metrics = cve_item.get("metrics", {})
# 优先取 CVSS v3.1,没有则取 v3.0
cvss_v31 = metrics.get("cvssMetricV31", [])
cvss_v30 = metrics.get("cvssMetricV30", [])
cvss_score = None
if cvss_v31:
cvss_score = cvss_v31[0].get("cvssData", {}).get("baseScore")
elif cvss_v30:
cvss_score = cvss_v30[0].get("cvssData", {}).get("baseScore")
results.append({
"cve_id": cve_id,
"cvss_score": cvss_score,
"source": "NVD"
})
return results
except requests.exceptions.RequestException as e:
print(f"查询 NVD 失败: {e}")
return []
# 使用示例
cpe = "cpe:2.3:a:apache:log4j:2.14.1:*:*:*:*:*:*:*"
vulns = query_nvd_by_cpe(cpe, api_key="your_key_here")
print(vulns)
避坑指南: 我曾经在生产环境直接调 NVD API,结果被限流导致流水线卡了半小时。后来改成每天凌晨同步一次全量 CVE 到本地 Elasticsearch,查询响应从秒级降到毫秒级。
4.2 OSV 数据库集成:开源生态的精准匹配
OSV(Open Source Vulnerabilities)是 Google 维护的开源漏洞数据库。它跟 NVD 最大的区别是什么?
OSV 直接用 Package URL(purl)做匹配,不需要你手动转 CPE。你想想看,SBOM 里本来就是 purl 格式,直接传进去就行,省了一道转换的麻烦。
OSV API 特点:
- 批量查询: 支持一次提交多个 purl,返回所有匹配的漏洞
- 生态覆盖: PyPI、npm、Maven、Go、Rust 等主流生态都支持
- 更新及时: 比 NVD 快,尤其是针对开源项目
代码示例:批量查询 OSV
import requests
import json
def query_osv_batch(purl_list):
"""
批量查询 OSV 漏洞
purl_list: ["pkg:pypi/requests@2.28.0", "pkg:npm/express@4.18.0"]
"""
url = "https://api.osv.dev/v1/querybatch"
payload = {
"queries": []
}
for purl in purl_list:
payload["queries"].append({
"package": {
"purl": purl
}
})
try:
resp = requests.post(url, json=payload, timeout=60)
resp.raise_for_status()
data = resp.json()
results = {}
for idx, query_result in enumerate(data.get("results", [])):
purl = purl_list[idx]
vulns = query_result.get("vulns", [])
cve_list = []
for vuln in vulns:
cve_id = vuln.get("id", "")
# OSV 的 ID 可能是 CVE 也可能是 GHSA
aliases = vuln.get("aliases", [])
cvss = None
# 从数据库特定字段提取 CVSS
cve_list.append({
"id": cve_id,
"aliases": aliases,
"source": "OSV"
})
results[purl] = cve_list
return results
except Exception as e:
print(f"OSV 批量查询失败: {e}")
return {}
# 使用示例
purls = ["pkg:pypi/requests@2.28.0", "pkg:npm/express@4.18.0"]
osv_results = query_osv_batch(purls)
print(json.dumps(osv_results, indent=2))
我的习惯: 先用 OSV 做第一轮快速扫描,因为它快、准、批量能力强。对于 OSV 没覆盖到的组件,再回退到 NVD 做补充查询。这样效率最高。
4.3 GHSA 数据库对接:GitHub 生态的专属情报
GHSA(GitHub Security Advisory)是 GitHub 维护的安全公告数据库。它跟 NVD 和 OSV 有什么不同?
GHSA 更聚焦于 GitHub 上托管项目的漏洞。很多漏洞在 GitHub 上被报告、修复、发布公告,但 NVD 可能要等几周甚至几个月才收录。GHSA 的时效性是最好的。
对接方式:
- GitHub Advisory Database API: 通过 GraphQL 或 REST 接口查询
- 数据格式: 每个公告有唯一的 GHSA ID(如 GHSA-xxxx-xxxx-xxxx)
- 关联 CVE: 大部分 GHSA 会关联对应的 CVE 编号
代码示例:查询 GHSA
import requests
import json
def query_ghsa_by_ecosystem(ecosystem, package_name):
"""
查询 GitHub Security Advisory
ecosystem: "pip", "npm", "maven", "go" 等
"""
url = "https://api.github.com/advisories"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": "Bearer your_github_token"
}
params = {
"type": "reviewed",
"ecosystem": ecosystem,
"package_name": package_name,
"per_page": 20
}
try:
resp = requests.get(url, headers=headers, params=params, timeout=30)
resp.raise_for_status()
advisories = resp.json()
results = []
for adv in advisories:
ghsa_id = adv.get("ghsa_id", "")
summary = adv.get("summary", "")
severity = adv.get("severity", "")
cvss_score = adv.get("cvss", {}).get("score")
cve_id = adv.get("cve_id")
results.append({
"ghsa_id": ghsa_id,
"summary": summary,
"severity": severity,
"cvss_score": cvss_score,
"cve_id": cve_id,
"source": "GHSA"
})
return results
except Exception as e:
print(f"GHSA 查询失败: {e}")
return []
# 使用示例
ghsa_vulns = query_ghsa_by_ecosystem("pip", "requests")
print(json.dumps(ghsa_vulns, indent=2))
注意: GitHub API 有速率限制,未认证用户每小时 60 次,认证后每小时 5000 次。建议申请一个 GitHub Token,哪怕只是个人使用。
4.4 三个数据库的对比与选择策略
你可能会问:到底该用哪个?我的答案是:全都要,但要有主次。
| 特性 | NVD | OSV | GHSA |
|---|---|---|---|
| 维护方 | NIST(美国官方) | GitHub | |
| 覆盖范围 | 最广,含商业软件 | 开源生态为主 | GitHub 项目为主 |
| 更新速度 | 较慢(人工审核) | 较快(自动化) | 最快(社区驱动) |
| 查询方式 | CPE / CVE ID | purl / 包名+版本 | 生态+包名 |
| 批量能力 | 弱(限流严重) | 强(批量查询) | 中(分页查询) |
| 推荐场景 | 商业软件、合规审计 | 开源组件快速扫描 | GitHub 项目实时监控 |
我的推荐策略:
1. 第一轮:OSV 批量查询(覆盖 80% 的开源组件)
2. 第二轮:GHSA 补充查询(针对 GitHub 生态的包)
3. 第三轮:NVD 兜底查询(覆盖 OSV 和 GHSA 遗漏的组件)
4. 合并去重:以 CVE ID 为 key,保留最高 CVSS 评分
1. 第一轮:OSV 批量查询(覆盖 80% 的开源组件)
2. 第二轮:GHSA 补充查询(针对 GitHub 生态的包)
3. 第三轮:NVD 兜底查询(覆盖 OSV 和 GHSA 遗漏的组件)
4. 合并去重:以 CVE ID 为 key,保留最高 CVSS 评分
4.5 实战中的避坑指南
做 SBOM 漏洞关联这几年,我踩过不少坑。挑几个典型的说说:
- 版本号格式不一致: SBOM 里写的是
2.0.1,NVD 里存的是2.0.1.RELEASE。匹配不上?我建议统一用 purl 规范,或者做版本号标准化处理。 - CPE 匹配失败: 同一个组件可能有多个 CPE 别名。比如 Apache Log4j 有
cpe:2.3:a:apache:log4j和cpe:2.3:a:apache:log4j-core。我一般会维护一个 CPE 别名映射表。 - 重复漏洞: 同一个 CVE 可能被 NVD、OSV、GHSA 都收录。合并结果时一定要去重,否则后续优先级排序会重复计算。
- API 限流: 这个前面提过。我的做法是本地缓存 + 定时同步,线上查询只查缓存。
一个小技巧: 如果你用 Python,可以试试
cvss 库来解析和计算 CVSS 向量。它支持 CVSS v2、v3.0、v3.1,还能自动计算环境评分。我每次拿到原始向量字符串,都用它转成结构化数据。