4. SBOM与漏洞库关联:NVD数据库查询、OSV数据库集成、GHSA数据库对接

SBOM 拿到手了,组件清单列得清清楚楚。然后呢?

你得知道这些组件有没有漏洞。说白了,就是把 SBOM 里的每一个组件名、版本号,拿去跟已知的漏洞数据库做匹配。这一步做不好,后面所有的优先级排序都是空中楼阁。

我个人习惯把漏洞库分成三类:官方的、社区的、生态的。今天咱们就把这三个典型代表——NVD、OSV、GHSA——挨个说透。

核心逻辑: SBOM 中的每个组件(purl/cpe + 版本) → 查询漏洞数据库 → 返回 CVE 列表及评分 → 进入优先级排序流程。
SBOM 与漏洞库关联流程 SBOM 组件清单 NVD 数据库 OSV 数据库 GHSA 数据库 合并漏洞结果 → 优先级排序 三个数据库各有侧重:NVD 覆盖最全但更新慢,OSV 开源生态精准,GHSA 对 GitHub 项目最及时

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(美国官方) Google GitHub
覆盖范围 最广,含商业软件 开源生态为主 GitHub 项目为主
更新速度 较慢(人工审核) 较快(自动化) 最快(社区驱动)
查询方式 CPE / CVE ID purl / 包名+版本 生态+包名
批量能力 弱(限流严重) 强(批量查询) 中(分页查询)
推荐场景 商业软件、合规审计 开源组件快速扫描 GitHub 项目实时监控
我的推荐策略:
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,还能自动计算环境评分。我每次拿到原始向量字符串,都用它转成结构化数据。