OWASP Dependency Check 实战——自动化依赖漏洞扫描在 CI 中的集成
OWASP Dependency Check 实战——自动化依赖漏洞扫描在 CI 中的集成
适读人群:DevOps 工程师、安全工程师、后端工程师 | 阅读时长:约 13 分钟 | 核心价值:用 OWASP Dependency Check 在 CI 中自动发现第三方依赖的已知漏洞,建立依赖安全基线
去年有一次,我帮一家公司做安全评估,发现他们的 Java 项目里引用了 log4j-core:2.14.0。我跟他们技术负责人小刘说了一句:"你们的 Log4Shell 漏洞还没修。"
他一脸懵:Log4Shell 是什么?
我说:就是 CVE-2021-44228,2021 年最严重的 Java 漏洞之一,CVSS 评分 10.0(满分),可以远程代码执行。你们这个版本受影响。
他赶紧翻代码——他们的项目上线了 14 个月,log4j 版本一直没动过。漏洞披露后全网都在修,他们不知道。
这件事让我意识到:开发者很难持续跟踪所有依赖的安全动态,必须有工具来做自动化检测。OWASP Dependency Check 就是这类工具里最成熟的一个。
1. OWASP Dependency Check 是什么?
OWASP Dependency Check(简称 ODC)是 OWASP 基金会维护的开源工具,通过匹配 NVD(国家漏洞数据库)来检测项目依赖中的已知 CVE 漏洞。
支持语言:Java、.NET、Python、Go、Ruby、Node.js 等。
核心能力:
- 扫描 pom.xml、go.mod、requirements.txt、package.json 等依赖文件
- 匹配 CVE 数据库,报告 CVSS 评分和修复建议
- 生成 HTML / JSON / XML 格式报告
- 支持失败阈值(CVSS ≥ N 时 CI 失败)
2. 安装与基础使用
2.1 命令行安装
# 下载
wget https://github.com/jeremylong/DependencyCheck/releases/download/v9.2.0/dependency-check-9.2.0-release.zip
unzip dependency-check-9.2.0-release.zip
# 添加到 PATH
export PATH=$PATH:$(pwd)/dependency-check/bin2.2 基础扫描
# 扫描 Java 项目
dependency-check.sh \
--scan /path/to/project \
--format HTML \
--out ./reports \
--project "My App"
# 扫描 Go 项目(扫描 vendor 目录或 go.sum)
dependency-check.sh \
--scan /path/to/project \
--enableExperimental \ # Go 支持需要实验性特性
--format HTML \
--out ./reports
# 设置失败阈值(CVSS >= 7 时 exit code 非 0)
dependency-check.sh \
--scan . \
--failOnCVSS 7 \
--format JSON \
--out ./reports3. Maven 插件集成(Java 推荐)
<!-- pom.xml -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.2.0</version>
<configuration>
<!-- NVD API Key(需要去 nvd.nist.gov 申请,提高更新速度)-->
<nvdApiKey>${env.NVD_API_KEY}</nvdApiKey>
<!-- 失败阈值:CVSS >= 7 时 build 失败 -->
<failBuildOnCVSS>7</failBuildOnCVSS>
<!-- 报告格式 -->
<formats>
<format>HTML</format>
<format>JSON</format>
</formats>
<!-- 排除测试依赖 -->
<skipTestScope>false</skipTestScope>
<!-- 排除特定 CVE(有 false positive 时使用)-->
<suppressionFiles>
<suppressionFile>owasp-suppressions.xml</suppressionFile>
</suppressionFiles>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin># 运行扫描
mvn dependency-check:check
# 只生成报告,不失败
mvn dependency-check:aggregate4. Python 项目扫描
# 方法 1:ODC 直接扫描(实验性)
dependency-check.sh \
--scan requirements.txt \
--enableExperimental \
--format HTML \
--out ./reports
# 方法 2:pip-audit(更推荐用于 Python)
pip install pip-audit
# 扫描当前环境
pip-audit
# 扫描 requirements 文件
pip-audit -r requirements.txt
# JSON 输出
pip-audit -r requirements.txt -f json -o audit-report.json
# 设置失败阈值
pip-audit -r requirements.txt --vulnerability-service osv5. Go 项目:govulncheck
Go 官方提供了 govulncheck,是 Go 生态最权威的漏洞扫描工具:
# 安装
go install golang.org/x/vuln/cmd/govulncheck@latest
# 扫描当前模块
govulncheck ./...
# 输出 JSON 格式
govulncheck -json ./...
# 示例输出:
# Vulnerability #1: GO-2023-1234
# An attacker can trigger X by doing Y.
# More info: https://pkg.go.dev/vuln/GO-2023-1234
# Module: github.com/some/lib@v1.2.3
# Found in: github.com/some/lib.VulnerableFunc
# Fixed in: github.com/some/lib@v1.2.4govulncheck 的特点是只报告被你的代码实际调用到的漏洞函数,而不是所有含漏洞的包——大幅减少误报。
6. 抑制规则:处理 false positive
不是所有报告的漏洞都是真正的威胁,有些可能是:
- 测试依赖,不在生产路径上
- 漏洞影响的代码路径你根本没用
- 漏洞在你的使用场景下不可触发
对于这类情况,需要添加抑制规则而不是简单忽略:
<!-- owasp-suppressions.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
<!-- 抑制特定 CVE + 文件组合 -->
<suppress>
<notes>
CVE-2021-12345 in library-x:我们只用了不受影响的 API,
经安全团队评估 2024-01-15 确认不影响。
</notes>
<packageUrl regex="true">^pkg:maven/com\.example/library-x@.*$</packageUrl>
<cve>CVE-2021-12345</cve>
</suppress>
<!-- 抑制直到特定日期(过期后自动重新报告)-->
<suppress until="2025-06-01">
<notes>等待上游修复,预计 2025 年 Q2</notes>
<cve>CVE-2024-67890</cve>
</suppress>
</suppressions>原则:每条抑制规则必须有 notes 说明原因,并设置过期时间。
7. GitHub Actions 集成
# .github/workflows/dependency-check.yml
name: Dependency Security Scan
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '0 6 * * 1' # 每周一早上 6 点全量扫描
jobs:
dependency-check-java:
name: OWASP Dependency Check (Java)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
# 缓存 NVD 数据库(避免每次重新下载,很大)
- name: Cache NVD database
uses: actions/cache@v4
with:
path: ~/.m2/repository/org/owasp/dependency-check-data
key: nvd-${{ github.run_id }}
restore-keys: nvd-
- name: Run OWASP Dependency Check
env:
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
run: |
mvn -B dependency-check:check \
-Dnvd.api.key=$NVD_API_KEY \
--no-transfer-progress
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: dependency-check-report
path: target/dependency-check-report.html
dependency-check-go:
name: govulncheck (Go)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck -json ./... > govuln-report.json || true
govulncheck ./... # 这行会以非零 exit code 退出(如有漏洞)
- name: Upload govuln report
uses: actions/upload-artifact@v4
if: always()
with:
name: govuln-report
path: govuln-report.json8. 踩坑实录
踩坑记录 1:NVD 数据库更新慢,CI 卡 30 分钟
ODC 第一次运行时需要从 NVD 下载 CVE 数据库(几百 MB),没有 API Key 时速率受限非常慢。解决方案:申请免费的 NVD API Key(访问 nvd.nist.gov),能把下载速度提升 10 倍以上。同时用 CI 缓存避免每次重建。
踩坑记录 2:CVSS 阈值设置导致 CI 每天误报
设置 --failOnCVSS 4 阈值太低,加上很多传递依赖都有中低危漏洞,CI 几乎每天都在失败。团队渐渐习惯"忽略这个报错",门禁就形同虚设了。
正确做法:建议从 --failOnCVSS 9 开始,只对 Critical 漏洞设置阻塞。其余漏洞产生报告供人工审查,定期清理。
踩坑记录 3:误报导致 CVE 被大量 suppress,失去意义
过度使用 suppress 文件,把几十个 CVE 都加进去,实际上已经不知道哪些是经过评估的抑制,哪些是懒得看的逃避。解决方案:suppress 文件每条必须有负责人名字和评估日期,定期审查过期条目。
8. 依赖安全的完整治理框架
仅仅扫描是不够的,扫描出来的漏洞需要有完整的治理流程。
8.1 依赖更新策略
定期更新依赖是减少漏洞积累的最有效手段。Dependabot(GitHub)和 Renovate Bot 可以自动创建依赖更新的 PR:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "09:00"
timezone: "Asia/Shanghai"
# 按安全更新优先
open-pull-requests-limit: 10
reviewers:
- "your-team"
commit-message:
prefix: "deps"
# 只自动合并补丁版本更新(patch)
# 主版本和次版本更新需要人工审查
allow:
- dependency-type: "all"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]8.2 依赖风险评级矩阵
并非所有漏洞都需要立即修复,可以用风险矩阵来优先级决策:
漏洞 CVSS 评分
低(0-4) 中(4-7) 高(7-9) 严重(9-10)
依赖使用范围
直接依赖 低 中 高 紧急
传递依赖 忽略 低 中 高
测试依赖 忽略 忽略 低 中传递依赖(你的依赖的依赖)的高危漏洞,通常通过升级直接依赖的版本来解决——很少需要直接指定传递依赖的版本(除非用 go.sum replace 指令)。
8.3 漏洞赏金与内部安全响应
对于正式产品,建议建立内部安全漏洞响应 SLA:
严重漏洞(CVSS 9+):24小时内修复并部署
高危漏洞(CVSS 7-9):72小时内修复
中危漏洞(CVSS 4-7):下个迭代内修复
低危漏洞(CVSS < 4):按优先级排期把这个 SLA 写入团队的质量协议,并在 CI 里自动计算"超期未修复漏洞数量",作为质量仪表盘的指标之一。
9. 供应链安全的更大视角
OWASP Dependency Check 和 govulncheck 解决的是第三方库的已知漏洞——这只是软件供应链安全的一部分。完整的供应链安全还包括:
构建可重复性:能否从同一份代码确定性地重新构建出完全相同的二进制?Go modules 的版本锁定(go.sum)提供了基础保障,但构建工具链本身也可能被污染。
SBOM(软件物料清单):能否清晰列出软件里所有的组件和版本?SPDX 和 CycloneDX 是两个主流 SBOM 标准,Trivy 和 Syft 都能生成这些格式。
签名和验证:构建产物是否经过签名?下游用户如何验证他们下载的软件是从你的代码构建的,没有被篡改?Sigstore/cosign 提供了容器镜像签名方案。
供应链安全是一个持续演进的领域。把 govulncheck 和 ODC 写进 CI 是入门级实践,更高阶的团队会逐步覆盖 SBOM、签名、构建可重复性这些维度。
10. 依赖安全的全生命周期管理
依赖漏洞扫描只是依赖安全管理的一个环节。完整的依赖安全管理应该覆盖整个依赖生命周期:引入、使用、更新、废弃。
引入阶段:依赖评估
在引入新依赖之前,养成评估习惯:
这个依赖的最后更新时间是什么时候?如果超过两年没有更新,可能已经被废弃,安全问题不会被修复。维护者是否活跃响应 Issue 和 PR?Stars 和 Download 数量能反映一定的社区信任度,但不是绝对指标。这个依赖有多少传递依赖?传递依赖越多,供应链风险越大。是否有知名的安全审计记录?
像 snyk.io 的 Advisor(snyk.io/advisor)提供了 npm、pip、gem 等包的安全评分和维护状态分析,可以作为引入决策的参考。
使用阶段:版本固定
把所有直接依赖的版本固定,而不是使用宽松的版本范围:
# 不推荐(宽松版本范围)
flask>=2.0,<3.0
# 推荐(固定版本)
flask==3.0.3固定版本确保了可重复构建,也让"依赖升级"成为一个有意识的决策,而不是自动发生的。使用 pip-compile 生成锁文件(requirements.txt),Go 的 go.sum 文件,npm 的 package-lock.json,这些锁文件要提交到代码仓库。
更新阶段:自动化与审查结合
Dependabot(GitHub 原生)或 Renovate Bot 可以自动检测依赖更新并创建 PR:
# .github/dependabot.yml
updates:
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
groups:
# 把安全更新单独分组,优先处理
security-updates:
applies-to: security-updates
patterns: ["*"]安全更新的 PR 要优先处理,最好设置一个 SLA(比如"高危漏洞 24 小时内合并安全更新,中危 7 天内")。非安全的版本升级可以在专门的"依赖维护 Sprint"里批量处理。
11. 组织级的依赖治理
当团队有多个服务时,依赖治理需要上升到组织级别,而不是每个服务团队各自管理。
内部依赖白名单
对于经过安全审查的依赖,维护一个内部白名单,标注每个依赖的版本范围和使用场景。新项目引入白名单之外的依赖,需要经过安全评审流程。这不是限制技术选择,而是确保安全基准线。
依赖漏洞 SLA
制定明确的漏洞修复 SLA(Service Level Agreement):CRITICAL 漏洞:24 小时内提交修复 PR,72 小时内发布;HIGH 漏洞:7 天内修复;MEDIUM 漏洞:30 天内修复;LOW 漏洞:90 天内修复或记录到技术债务。
SLA 要落到具体的负责人(服务的 Owner 团队),而不是让安全团队去追每个服务。服务 Owner 对服务的安全状态负责。
定期的依赖健康检查
每季度做一次依赖健康检查,不只看漏洞,也看:有没有已经废弃的依赖(deprecated)?有没有可以合并的依赖(功能重叠的多个依赖)?有没有没人维护的依赖(可以考虑 fork 或者寻找替代方案)?
依赖管理是一个需要持续维护的工程工作,不是"一次性设置好就完事"。把它纳入日常的工程 Backlog,当成技术债务的一部分来管理,才能保证长期的安全健康状态。
12. 安全工程文化的长期建设
依赖安全扫描是安全工程的一个切入点,但安全工程文化的建立需要更系统的投入。
安全左移(Security Shift Left)
和测试左移一样,安全也需要左移——在需求阶段和设计阶段就考虑安全,而不是在上线前做安全扫描。设计阶段的威胁建模(Threat Modeling)是安全左移的核心实践:针对每个关键功能,识别潜在的攻击向量,在设计阶段就消除安全风险。
安全作为代码质量的一部分
把安全扫描(OWASP Dependency Check、gosec、gitleaks)纳入与代码质量扫描(golangci-lint、SonarQube)同等的 CI 地位,不区别对待。安全问题和代码质量问题一样,是需要在每个 PR 时检查和修复的工程问题,不是周期性的安全审计才关注的议题。
安全意识的日常培养
每次安全扫描发现的漏洞,都是一次学习机会。组织一个简短的"本周安全漏洞解读"分享,解释这个漏洞的原理、潜在影响、修复方法——把漏洞修复变成团队的安全知识积累。时间长了,工程师会在写代码时自然地考虑"这里有没有可能引入安全风险",而不是把安全完全依赖工具来发现。
安全工程和质量工程一样,最终目标是让安全意识成为工程习惯的一部分,而不是一个独立的、偶发的工程活动。
依赖安全是持续工程实践,不是一次性扫描。今天没有漏洞,不代表明天没有——漏洞数据库每天都在更新。建立自动化的持续扫描和及时的修复 SLA,让安全维护成为日常工程节奏的一部分,是成熟工程团队的标志。
软件供应链安全是当前最值得关注的工程安全方向之一。依赖漏洞扫描只是入口,SBOM 生成、镜像签名、准入控制是完整的供应链安全体系的组成部分。今天建立起依赖扫描的习惯,是迈向完整供应链安全的第一步,也是最重要的一步。
依赖安全扫描最终解决的是一个信任问题:我们的软件用的所有第三方代码,是我们知情并评估过的,而不是无意中引入的未知风险。建立这种知情和评估的机制,是对用户的承诺,也是对工程师自己的保护。
安全是工程的基础属性,而不是可以事后添加的功能。依赖漏洞扫描是这个基础属性最容易实践的切入点之一——成本低,工具成熟,效果直接可见。从今天开始,把 OWASP Dependency Check 或 govulncheck 加进你的 CI,让安全意识成为工程日常,而不是季度一次的安全审计。
依赖安全最终是一种工程责任——对使用你软件的用户负责,对团队负责,对自己的工程声誉负责。建立起系统化的依赖安全实践,不只是完成合规要求,更是在兑现这种工程责任。从今天开始,把依赖扫描当成工程日常,而不是安全应急时才想起来的工具。
在软件工程里,安全不是一个可以添加的功能,而是需要从一开始就建立的工程属性。依赖安全扫描的价值,在于它把安全检查从阶段性的审计变成了持续的工程实践。坚持每次 PR 都扫描,坚持每次发现漏洞都及时处理,这种持续的纪律,才是真正的工程安全。
写在最后
依赖安全扫描解决的是"我用了哪些已知有漏洞的库"这个问题,这是供应链安全的基础。Log4Shell 那次事件提醒了整个行业:第三方依赖引入的漏洞,往往比自己写的代码 Bug 更难发现、危害更大。
把 govulncheck 和 ODC 写进 CI,是建立供应链安全基线最低成本的方式。
下一篇我们聊容器镜像安全——Trivy 如何扫描 Docker 镜像里的漏洞。
