OWASP Dependency-Check:用证据与 CPE 匹配治理 Java 依赖漏洞
OWASP Dependency-Check 不只是读取 Maven 坐标再查 CVE。它由多种 Analyzer 收集文件名、Manifest、POM 等证据,推导 CPE 候选,再与 NVD 等数据匹配。名称相似、元数据缺失和 shaded JAR 都可能让证据链偏离真实组件,因此误报治理必须回到证据与 CPE,不能只按 CVE ID 加 suppression。
本文使用 Dependency-Check 13.0.0 作为实验坐标。CLI、Maven/Gradle 插件和数据目录要分别锁定,构建插件版本不能靠默认解析。
CLI 安装与校验
官方 Release 提供命令行压缩包。下载后核对 Release 摘要或组织制品库校验记录,再运行:
unzip dependency-check-13.0.0-release.zip -d .tools
.tools/dependency-check/bin/dependency-check.sh --versionWindows 使用对应 .bat。受控 Runner 固定压缩包摘要或镜像 digest,不从浮动 URL 动态安装。Java 运行时也进入基线;升级 Dependency-Check 前先核对其支持的 Java 版本。
第一次扫描先分离数据更新
联网同步任务准备数据目录:
DATA_DIR="$PWD/.cache/dependency-check"
.tools/dependency-check/bin/dependency-check.sh \
--data "$DATA_DIR" \
--updateonly \
--nvdApiKeyEnvironmentVariable NVD_API_KEYNVD API 有配额与限流。团队使用专用同步身份,避免每个 PR 同时更新。数据目录作为只读制品分发给扫描任务,并保存更新时间、Schema 与工具版本。更新失败不能继续使用未知旧库后返回绿色。
.tools/dependency-check/bin/dependency-check.sh \
--project fixture-app \
--scan app/libs \
--data "$DATA_DIR" \
--noupdate \
--format JSON \
--out artifacts/dependency-check--noupdate 只表示本次不联网,不证明数据新鲜。扫描前由包装脚本检查同步任务元数据与允许年龄。
Analyzer 决定收集哪些证据
Archive、Jar、Central、Nexus、Assembly 等 Analyzer 覆盖不同文件和外部服务。启用外部 Analyzer 会增加网络、凭据和超时边界;关闭某个 Analyzer 可能降低误报,也可能让组件证据不足。
报告中的 evidence、identifiers 与 relatedDependencies 是排查入口。对同一个 JAR,文件名、Manifest Implementation-Title、POM group/artifact/version 可能互相矛盾。先确认被扫字节和依赖来源,再判断 CPE 是否合理。
Maven 与 Gradle 插件要锁版本
Maven 插件示例:
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>13.0.0</version>
<configuration>
<dataDirectory>${project.build.directory}/dependency-check-data</dataDirectory>
<failBuildOnCVSS>8</failBuildOnCVSS>
<suppressionFiles>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</suppressionFiles>
</configuration>
</plugin>CI 中数据目录通常来自受控缓存,不应每个模块各自下载。Monorepo 要确认 reactor 中所有模块和最终打包依赖都被扫描,不能只看根 POM 插件执行成功。
门禁阈值与执行失败分开
CLI 可用 --failOnCVSS 设置门禁:
set +e
.tools/dependency-check/bin/dependency-check.sh \
--project fixture-app \
--scan app/libs \
--data "$DATA_DIR" \
--noupdate \
--format JSON \
--out artifacts/dependency-check \
--failOnCVSS 8
scanner_rc=$?
set -e
test -s artifacts/dependency-check/dependency-check-report.json包装脚本要区分达到阈值和数据库/Analyzer/文件错误,不能统一 || true。CVSS 8 只是实验值;生产策略结合可利用性、暴露面、修复状态和 SLA。完整报告仍保留阈值以下结果。
suppression 必须最小且可过期
Dependency-Check suppression XML 可以按 CVE、CPE、SHA1、package URL 或路径约束。优先使用能唯一识别误配组件的字段,避免全局抑制一个 CVE。每条记录写明理由、owner、审查编号和到期。
<suppress>
<notes><![CDATA[Fixture metadata maps to the wrong product; recheck after upgrade.]]></notes>
<sha1>EXAMPLE_SHA1_FOR_FIXTURE_ONLY</sha1>
<cve>CVE-EXAMPLE-FIXTURE</cve>
</suppress>示例值不用于真实规则。正式 suppression 由报告生成器辅助创建后仍需人工核对。升级 Analyzer 或数据后,旧 suppression 可能不再匹配,也可能继续遮住新证据,必须回放。
NVD、CPE 与生态坐标的边界
CPE 是产品命名体系,不等同 Maven GAV 或 PURL。Java 包的 group/artifact 与厂商产品名可能不一致,fork、重打包和 shaded 依赖会进一步增加歧义。同一 CVE 的受影响范围也可能在 NVD、厂商 advisory 和项目公告间更新。
排查时先固定 JAR 哈希和来源,再看证据权重、选择的 CPE、CVE 版本范围、已安装版本与修复版本。只因为包名相似就接受 finding,或只因为开发者说“不适用”就 suppression,都缺少可复核依据。
数据、权限与成本
扫描任务读取依赖和数据目录,不需要仓库写权限或生产凭据。NVD API key 只进入同步任务,不能出现在构建日志。报告暴露依赖与路径,应限制读取和保留期。
成本主要来自 NVD 同步、Analyzer 网络访问、归档展开、证据匹配和大型 Monorepo 扫描。记录依赖数、归档数、数据年龄、Analyzer 耗时、网络错误和 P50/P95。提高超时不能替代同步分层与缓存治理。
故障、升级与退出
突然大量误报先比较数据版本、Analyzer、CPE 证据和依赖字节。零结果先确认扫描路径、归档支持和模块覆盖。NVD 更新慢时检查 API 配额、代理和共享同步,不要给每个 PR 独立 key。
升级用固定依赖集合跑新旧工具/数据组合,比较 dependency、evidence、CPE、CVE、severity、suppression 与退出码。回滚恢复 CLI/插件、数据快照和 suppression,不能通过关闭 Analyzer 或提高阈值实现。
--purge 会删除数据目录,在共享 Runner 上只能对验证过的明确缓存路径执行。迁移到 PURL 原生扫描器前,用相同 JAR 集验证组件覆盖和历史例外,再移除 Dependency-Check。
