欢迎你来读这篇博客,这篇博客主要是关于 OWASP Dependency-Check。
在日常 Java 开发中,我们经常关注业务代码有没有问题、SQL 是否慢、接口是否超时、线程池是否打满,却容易忽略一个非常现实的问题:
项目里引入的第三方依赖,本身可能已经存在公开漏洞。
比如 log4j-core、jackson-databind、fastjson、spring-security、netty、commons-collections 这些依赖,一旦版本落后,就可能把已知漏洞带进系统里。
OWASP Dependency-Check 要解决的就是这个问题。
它不是传统意义上的代码扫描器,而是一个 SCA 工具,也就是 Software Composition Analysis,软件成分分析工具 。
它主要做一件事:
分析项目依赖,识别其中是否包含已经公开披露的安全漏洞。
序言 现在的后端项目,很少有完全从零写的。
一个 Spring Boot 项目可能会引入:
Spring Boot Starter
Spring Security
MyBatis-Plus
Spring Data JPA
Jackson
Netty
Hutool
Apache Commons
Guava
Fastjson
Logback
PostgreSQL Driver
Redis Client
Nacos Client
Dubbo
ShardingSphere
这些依赖极大提高了开发效率,但也带来了供应链安全风险。
业务代码没问题,不代表系统安全。你写的代码很干净,但你引入的依赖可能已经“祖传漏洞套餐”了。
这就是为什么在 DevSecOps 流程里,依赖漏洞扫描越来越重要。
OWASP Dependency-Check 就是一个非常经典的开源选择。
正文 1. 什么是 OWASP Dependency-Check? OWASP Dependency-Check 是 OWASP 旗下的开源依赖漏洞检测工具。
它会扫描项目中的依赖组件,并尝试判断这些组件是否存在已公开披露的漏洞。
简单来说:
它关注的重点不是你的业务代码写得好不好,而是你项目里用了哪些第三方组件,这些组件是否存在已知 CVE 漏洞。
例如你的项目中使用了:
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.47</version > </dependency >
或者:
1 2 3 4 5 <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-core</artifactId > <version > 2.14.1</version > </dependency >
Dependency-Check 会分析这些依赖,然后结合漏洞数据库生成报告,告诉你:
哪个依赖存在漏洞;
对应的 CVE 编号是什么;
漏洞严重程度是多少;
CVSS 分数是多少;
漏洞描述是什么;
有哪些参考链接;
构建是否应该失败。
一句话概括:
OWASP Dependency-Check 是给项目第三方依赖做安全体检的工具。
2. 为什么需要依赖漏洞扫描? 很多线上安全问题不是业务代码直接导致的,而是来自第三方依赖。
典型例子包括:
依赖
可能风险
Log4j
远程代码执行
Jackson Databind
反序列化漏洞
Fastjson
反序列化、远程代码执行
Spring Security
权限绕过、认证绕过
Netty
拒绝服务、HTTP 解析问题
Commons Collections
反序列化利用链
Tomcat
请求走私、路径绕过、DoS
一个企业项目里,直接依赖可能只有几十个,但传递依赖可能有几百个。
比如你只写了:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
但实际引入的可能包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring-web spring-webmvc spring-core spring-context spring-beans spring-aop jackson-databind jackson-core jackson-annotations tomcat-embed-core tomcat-embed-websocket logback-classic logback-core slf4j-api ...
你没有直接声明这些包,但它们确实进入了你的应用。
这也是依赖漏洞扫描的价值所在:
它不只看你写了什么依赖,还会分析传递依赖。
如果没有扫描工具,靠人肉翻 dependency:tree,最后大概率会变成“眼睛看花,脑子宕机”。这活儿应该交给工具,程序员的大脑还是留给更难的 bug 吧。
3. Dependency-Check 的核心原理 Dependency-Check 的基本流程如下:
flowchart LR
A[读取项目依赖] --> B[分析依赖元信息]
B --> C[收集 Evidence 证据]
C --> D[识别 CPE / PURL]
D --> E[匹配 NVD / CVE 数据]
E --> F[生成漏洞报告]
F --> G[根据阈值决定是否中断构建]
它会从依赖包中提取一些证据,例如:
groupId
artifactId
version
pom 信息
jar manifest
包名
文件名
vendor 信息
product 信息
然后尝试把依赖映射到漏洞数据库中的组件标识。
常见概念包括:
概念
说明
CVE
Common Vulnerabilities and Exposures,公开漏洞编号
CVSS
Common Vulnerability Scoring System,漏洞严重程度评分
CPE
Common Platform Enumeration,通用平台枚举,用来标识产品或组件
NVD
National Vulnerability Database,美国 NIST 维护的漏洞数据库
SCA
Software Composition Analysis,软件成分分析
Suppression
抑制规则,用来处理误报
它的扫描结果不是“玄学判断”,而是基于公开漏洞数据进行匹配。
不过,也正因为要做组件识别和 CPE 匹配,所以它有可能出现误报。
比如某个 jar 的名字或元信息刚好和另一个产品相似,Dependency-Check 可能会认为它命中了某个 CPE,从而报告一个实际上并不相关的 CVE。
所以,Dependency-Check 的正确使用姿势不是:
1 扫出漏洞 -> 立刻全部升级 -> 构建炸了 -> 人也炸了
而应该是:
1 扫出漏洞 -> 判断是否真实影响 -> 升级 / 排除 / 替换 / suppression -> 形成治理闭环
4. Dependency-Check 支持哪些使用方式? Dependency-Check 支持多种方式集成:
使用方式
适用场景
Maven Plugin
Java Maven 项目,最常用
Gradle Plugin
Gradle 项目
CLI
任意项目、脚本扫描、容器扫描
Jenkins Plugin
Jenkins 流水线集成
GitHub Actions
GitHub CI 集成
GitLab CI
GitLab 流水线集成
Azure DevOps
企业流水线
Ant Task
老项目构建
对于 Java 后端项目,最推荐从 Maven 插件开始。
因为它和项目生命周期结合得最自然,可以直接挂到 verify 阶段,也可以单独执行。
5. Maven 项目快速接入 5.1 最小 Maven 配置 在 pom.xml 中加入插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <build > <plugins > <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > 12.2.2</version > <executions > <execution > <goals > <goal > check</goal > </goals > </execution > </executions > </plugin > </plugins > </build >
然后执行:
1 mvn dependency-check:check
或者:
执行完成后,默认会在 target 目录下生成报告:
1 target/dependency-check-report.html
直接用浏览器打开即可。
5.2 推荐 Maven 配置 实际项目中,建议不要只生成 HTML,还可以同时生成 JSON、JUNIT、SARIF 等格式,方便 CI 系统、GitHub 安全面板或后续脚本处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <properties > <dependency-check.version > 12.2.2</dependency-check.version > </properties > <build > <plugins > <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > ${dependency-check.version}</version > <configuration > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > <format > JUNIT</format > <format > SARIF</format > </formats > <prettyPrint > true</prettyPrint > <skipTestScope > true</skipTestScope > <skipProvidedScope > true</skipProvidedScope > <suppressionFiles > <suppressionFile > ${project.basedir}/dependency-check-suppressions.xml</suppressionFile > </suppressionFiles > </configuration > <executions > <execution > <goals > <goal > check</goal > </goals > </execution > </executions > </plugin > </plugins > </build >
这里重点看几个参数:
参数
说明
failBuildOnCVSS
当漏洞 CVSS 分数大于等于指定值时,让构建失败
formats
报告输出格式,常用 HTML、JSON、JUNIT、SARIF
prettyPrint
是否格式化 JSON / XML 输出
skipTestScope
是否跳过 test scope 依赖
skipProvidedScope
是否跳过 provided scope 依赖
suppressionFiles
误报抑制文件
对于企业项目,我比较推荐:
1 2 3 本地开发:failBuildOnCVSS = 9 或只生成报告 CI 主分支:failBuildOnCVSS = 7 生产发布:Critical / High 必须处理
不要一上来就设置成 failBuildOnAnyVulnerability=true。
这玩意儿太狠,相当于“看见一个警告就拉闸”。刚开始治理老项目的时候,这样做很容易让流水线变成红灯笼。
6. 多模块 Maven 项目如何扫描? 企业 Java 项目经常是多模块结构:
1 2 3 4 5 6 7 finance-parent ├── finance-api ├── finance-common ├── finance-domain ├── finance-infrastructure ├── finance-application └── finance-start
这种项目建议在父 pom.xml 中使用 aggregate 目标,生成聚合报告:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > ${dependency-check.version}</version > <configuration > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > </formats > </configuration > <executions > <execution > <goals > <goal > aggregate</goal > </goals > </execution > </executions > </plugin >
执行:
1 mvn dependency-check:aggregate
或:
聚合报告会比每个模块单独扫更适合团队查看。
如果模块很多,建议配合 CI 缓存,否则每次扫描都很痛苦。
7. 第一次运行为什么很慢? 第一次执行 Dependency-Check 通常会比较慢。
原因是它需要下载并处理 NVD 漏洞数据库。
常见现象:
1 2 第一次运行:十几分钟到几十分钟 后续运行:如果缓存有效,通常会快很多
它会把数据缓存到本地 Maven 仓库下,常见路径类似:
1 ~/.m2/repository/org/owasp/dependency-check-data/
所以在 CI/CD 环境中,一定要做缓存。
如果你不缓存,每次流水线都重新下载 NVD 数据,那就不是扫描依赖了,是扫描你的耐心。
8. 配置 NVD API Key Dependency-Check 会从 NVD 获取漏洞数据。为了提高稳定性和降低限流问题,建议配置 NVD API Key。
可以把 API Key 放到 Maven settings.xml 中,不建议直接写在 pom.xml。
8.1 Maven settings.xml 1 2 3 4 5 6 7 8 <settings > <servers > <server > <id > nvd-api</id > <password > ${env.NVD_API_KEY}</password > </server > </servers > </settings >
8.2 pom.xml 中引用 server id 1 2 3 4 5 6 7 8 9 10 11 12 13 <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > ${dependency-check.version}</version > <configuration > <nvdApiServerId > nvd-api</nvdApiServerId > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > </formats > </configuration > </plugin >
8.3 本地执行 1 2 export NVD_API_KEY=你的_api_key mvn dependency-check:check
不推荐这样写:
1 <nvdApiKey > 你的_api_key</nvdApiKey >
因为 API Key 属于敏感信息,放在 pom.xml 里很容易被提交到 Git 仓库。
尤其是团队项目,不要把密钥写进代码仓库。密钥进了 Git,基本就像把家门钥匙挂在门口,还配了个夜光牌。
9. 报告怎么看? 生成报告后,打开:
1 target/dependency-check-report.html
通常会看到这些字段:
字段
说明
Dependency
出问题的依赖
File Name
依赖文件名
Package
包信息
CPE
识别出的组件标识
CVE
漏洞编号
CVSS
漏洞评分
Severity
严重级别
Description
漏洞描述
References
漏洞参考链接
Evidence
识别依据
一个典型的排查流程:
flowchart TB
A[打开报告] --> B[按 CVSS 分数排序]
B --> C[优先看 Critical / High]
C --> D[确认依赖来源]
D --> E{是否直接依赖?}
E -->|是| F[升级直接依赖版本]
E -->|否| G[定位传递依赖来源]
G --> H[升级上游依赖或显式覆盖版本]
F --> I[跑测试]
H --> I
I --> J{是否真实误报?}
J -->|否| K[提交修复]
J -->|是| L[添加 suppression 并说明原因]
优先级建议:
1 2 3 4 5 1. Critical:必须立即处理 2. High:优先处理,必要时阻断发布 3. Medium:进入版本计划 4. Low:定期治理 5. 误报:写 suppression,必须说明原因
10. 如何定位漏洞依赖从哪里来的? 如果报告里提示某个传递依赖有漏洞,比如:
1 jackson-databind-2.13.x.jar
你需要知道它是被谁引入的。
可以执行:
1 mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
如果想看完整依赖树:
如果输出太多,可以写到文件:
1 mvn dependency:tree > dependency-tree.txt
示例输出:
1 2 3 4 [INFO] com.example:finance-start:jar:1.0.0 [INFO] \- org.springframework.boot:spring-boot-starter-web:jar:3.5.0 [INFO] \- org.springframework.boot:spring-boot-starter-json:jar:3.5.0 [INFO] \- com.fasterxml.jackson.core:jackson-databind:jar:2.18.x
这时候你就知道:
1 jackson-databind 是通过 spring-boot-starter-web -> spring-boot-starter-json 引进来的
处理方式可能有三种:
10.1 升级 Spring Boot 版本 如果你使用 Spring Boot 的依赖管理,优先考虑升级 Spring Boot patch 版本。
1 2 3 4 5 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 3.5.x</version > </parent >
这是最干净的方式,因为 Spring Boot BOM 会统一管理一组经过测试的依赖版本。
10.2 显式覆盖依赖版本 如果短期不能升级 Spring Boot,可以在 dependencyManagement 中覆盖:
1 2 3 4 5 6 7 8 9 <dependencyManagement > <dependencies > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.18.4</version > </dependency > </dependencies > </dependencyManagement >
这种方式要谨慎。覆盖版本可能解决漏洞,也可能带来兼容性问题。
10.3 排除传递依赖 如果某个依赖完全不需要,可以排除:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > some.group</groupId > <artifactId > some-artifact</artifactId > <version > 1.0.0</version > <exclusions > <exclusion > <groupId > vulnerable.group</groupId > <artifactId > vulnerable-artifact</artifactId > </exclusion > </exclusions > </dependency >
排除依赖之前一定要跑测试。
排错不是“删了就安全”,删错了可能是“安全了,但项目也启动不了了”。
11. 误报处理:Suppression 文件 Dependency-Check 有时会误报。
误报常见原因:
CPE 识别错误;
组件名字相似;
你的使用场景不受漏洞影响;
漏洞只影响特定平台或特定模块;
CVE 数据本身存在滞后或修订。
对误报不能简单忽略,应该写 suppression 文件,并且写清楚原因。
新建文件:
1 dependency-check-suppressions.xml
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="UTF-8" ?> <suppressions xmlns ="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.4.xsd" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.4.xsd https://dependency-check.github.io/DependencyCheck/dependency-suppression.1.4.xsd" > <suppress > <notes > <![CDATA[ 误报说明:该 CVE 匹配到了错误的 CPE。 处理人:security-team 处理时间:2026-06-12 复核建议:下次依赖升级后重新检查。 ]]></notes > <packageUrl regex ="true" > ^pkg:maven/com\.example/example-lib@.*$</packageUrl > <vulnerabilityName > CVE-2024-0000</vulnerabilityName > </suppress > </suppressions >
然后在 Maven 插件中引用:
1 2 3 <suppressionFiles > <suppressionFile > ${project.basedir}/dependency-check-suppressions.xml</suppressionFile > </suppressionFiles >
建议团队制定一个规则:
suppression 不是垃圾桶,而是审计记录。
每条 suppression 至少要包含:
CVE 编号;
影响依赖;
为什么判定为误报;
谁处理的;
什么时候处理的;
什么时候复核。
否则 suppression 文件最后会变成“漏洞坟场”:看起来很安静,其实下面全是坑。
12. GitHub Actions 集成 GitHub Actions 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 name: Dependency Check on: pull_request: branches: - master - main push: branches: - master - main jobs: dependency-check: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: distribution: temurin java-version: '21' cache: maven - name: Cache OWASP Dependency-Check data uses: actions/cache@v4 with: path: ~/.m2/repository/org/owasp/dependency-check-data key: dependency-check-data-${{ runner.os }} restore-keys: | dependency-check-data-${{ runner.os }} - name: Run Dependency-Check env: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} run: | mvn -B verify -DskipTests - name: Upload report if: always() uses: actions/upload-artifact@v4 with: name: dependency-check-report path: | **/target/dependency-check-report.html **/target/dependency-check-report.json **/target/dependency-check-report.sarif
这里重点是两类缓存:
1 2 1. Maven 依赖缓存 2. Dependency-Check 漏洞数据库缓存
没有缓存的话,CI 会非常慢。
13. GitLab CI 集成 .gitlab-ci.yml 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 stages: - test - security - build dependency_check: stage: security image: maven:3.9-eclipse-temurin-21 variables: MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository" cache: key: dependency-check-cache paths: - .m2/repository script: - mvn -B dependency-check:check -DskipTests artifacts: when: always expire_in: 7 days paths: - target/dependency-check-report.html - target/dependency-check-report.json - target/dependency-check-report.xml only: - merge_requests - master - main
如果是多模块项目,可以改成:
1 mvn -B dependency-check:aggregate -DskipTests
也可以单独给安全扫描加 profile。
14. Jenkins Pipeline 集成 Jenkinsfile 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 pipeline { agent any tools { jdk 'jdk-21' maven 'maven-3.9' } environment { NVD_API_KEY = credentials('nvd-api-key' ) } stages { stage('Checkout' ) { steps { checkout scm } } stage('Dependency Check' ) { steps { sh 'mvn -B dependency-check:check -DskipTests' } post { always { archiveArtifacts artifacts: '**/target/dependency-check-report.*' , fingerprint: true } } } } }
如果使用 Jenkins Dependency-Check 插件,也可以把报告发布到 Jenkins 页面中。
15. 推荐使用 Maven Profile 控制扫描 很多项目不希望每次本地 mvn package 都跑安全扫描,因为太慢。
可以使用 Maven Profile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <profiles > <profile > <id > security-scan</id > <build > <plugins > <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > ${dependency-check.version}</version > <configuration > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > <format > JUNIT</format > </formats > <suppressionFiles > <suppressionFile > ${project.basedir}/dependency-check-suppressions.xml</suppressionFile > </suppressionFiles > </configuration > <executions > <execution > <goals > <goal > check</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </profile > </profiles >
执行:
1 mvn verify -Psecurity-scan
这样就可以做到:
1 2 3 本地普通构建:mvn package 本地安全检查:mvn verify -Psecurity-scan CI 安全扫描:mvn verify -Psecurity-scan
16. 老项目如何落地? 老项目第一次接入 Dependency-Check,报告很可能是这样的:
1 2 3 4 High: 一堆 Medium: 一堆 Low: 一堆 误报: 也一堆
这很正常。
不要试图一天内全部处理完。
推荐按阶段治理。
16.1 第一阶段:只生成报告,不阻断构建 1 <failBuildOnCVSS > 11</failBuildOnCVSS >
因为 CVSS 最高 10 分,设置为 11 等于不因漏洞失败。
第一阶段目标不是拦截,而是摸底:
1 2 3 4 5 系统里到底有多少漏洞依赖? 哪些是直接依赖? 哪些是传递依赖? 哪些是误报? 哪些必须升级?
16.2 第二阶段:治理 Critical 把最严重的先处理掉。
1 <failBuildOnCVSS > 9</failBuildOnCVSS >
大多数团队可以先从 9 分开始。
16.3 第三阶段:治理 High 1 <failBuildOnCVSS > 7</failBuildOnCVSS >
进入这个阶段后,说明项目依赖安全已经比较健康。
16.4 第四阶段:形成长期机制 长期机制包括:
每次 PR 扫描;
每天或每周定时扫描;
发布前强制扫描;
高危漏洞阻断发布;
误报必须写 suppression;
suppression 定期复核;
配合 Renovate 自动升级依赖;
配合 OpenRewrite 进行框架升级和 API 迁移。
17. 与 Renovate、OpenRewrite 配合使用 Dependency-Check 只负责发现问题。
发现问题后,你还需要修。
这里可以和 Renovate、OpenRewrite 组成一套工程化闭环。
flowchart LR
A[Dependency-Check 扫描漏洞] --> B[生成漏洞报告]
B --> C{是否需要升级依赖?}
C -->|是| D[Renovate 创建升级 PR]
D --> E[CI 跑单元测试 / 集成测试]
E --> F{是否涉及框架 API 迁移?}
F -->|是| G[OpenRewrite 自动重构]
F -->|否| H[人工 Review]
G --> H
H --> I[合并 PR]
I --> J[再次扫描确认漏洞消失]
三者的职责可以这样理解:
工具
职责
Dependency-Check
发现依赖漏洞
Renovate
自动发现新版本并创建升级 PR
OpenRewrite
自动做 Java / Spring / Maven 迁移重构
一个比较理想的流程是:
1 2 3 4 5 1. Dependency-Check 发现 jackson-databind 有高危漏洞 2. Renovate 提出升级 Spring Boot 或 Jackson 的 PR 3. CI 执行测试、依赖扫描、构建 4. 如果升级涉及 API 变更,用 OpenRewrite 辅助迁移 5. 合并后再次扫描,确认漏洞消失
这就是 DevSecOps 比较舒服的状态:
不是靠人盯漏洞,而是让流水线逼着问题浮出水面。
18. Spring Boot 项目中的依赖治理建议 对于 Spring Boot 项目,不建议到处手写依赖版本。
推荐使用 Spring Boot BOM 管理依赖。
如果使用 parent:
1 2 3 4 5 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 3.5.0</version > </parent >
如果公司有统一 parent,也可以 import BOM:
1 2 3 4 5 6 7 8 9 10 11 <dependencyManagement > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 3.5.0</version > <type > pom</type > <scope > import</scope > </dependency > </dependencies > </dependencyManagement >
原则是:
1 2 3 4 1. 优先升级 Spring Boot patch 版本 2. 再考虑显式覆盖单个依赖 3. 不要随意打破 BOM 管理 4. 覆盖版本后必须跑测试
比如 Spring Boot 管理了一整套 Jackson 版本,你单独覆盖 jackson-databind,但没有覆盖 jackson-core、jackson-annotations,可能会出现版本不一致问题。
安全修复不是只看漏洞报告消失,还要看应用是否真的稳定。
19. 实战案例:发现并修复漏洞依赖 假设 Dependency-Check 报告中出现:
1 2 3 4 Dependency: jackson-databind-2.13.0.jar CVE: CVE-xxxx-yyyy Severity: High CVSS: 8.1
处理流程如下。
19.1 定位依赖来源 1 mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
输出:
1 2 3 4 [INFO] com.example:demo:jar:1.0.0 [INFO] \- org.springframework.boot:spring-boot-starter-web:jar:2.7.0 [INFO] \- org.springframework.boot:spring-boot-starter-json:jar:2.7.0 [INFO] \- com.fasterxml.jackson.core:jackson-databind:jar:2.13.0
说明它来自 Spring Boot Starter。
19.2 优先升级 Spring Boot 1 2 3 4 5 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.18</version > </parent >
或者升级到 Spring Boot 3.x,但这通常涉及 Jakarta 包名迁移,改动更大。
19.3 跑测试
19.4 再次扫描 1 mvn dependency-check:check
如果报告不再出现该 CVE,说明修复完成。
如果仍然存在,要继续检查是否还有其他模块或传递依赖引入了旧版本。
20. 实战案例:CI 中阻断高危漏洞 假设你希望在 GitLab CI 中实现:
1 2 CVSS >= 7,阻断合并 CVSS < 7,只生成报告
Maven 配置:
1 2 3 4 5 6 7 8 <configuration > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > <format > JUNIT</format > </formats > </configuration >
GitLab CI:
1 2 3 4 5 6 7 8 9 10 11 dependency_check: stage: security image: maven:3.9-eclipse-temurin-21 script: - mvn -B verify -Psecurity-scan -DskipTests artifacts: when: always paths: - target/dependency-check-report.html - target/dependency-check-report.json - target/dependency-check-junit.xml
这样一来,如果出现高危漏洞,流水线直接失败。
这才是工具的真正价值:
不是等上线后靠安全同事提醒,而是在合并前就把问题拦住。
21. 企业落地建议 21.1 不要只在发布前扫描 如果只在发布前扫描,很容易出现这种情况:
1 明天要发版 -> 今天扫出一堆高危 -> 大家一起加班 -> 气氛祥和但不多
推荐:
1 PR 阶段扫描 + 主分支定时扫描 + 发布前强制扫描
21.2 不要把所有漏洞都当成同一优先级 漏洞治理要看:
CVSS 分数;
是否可远程利用;
是否有公开 PoC;
是否出现在运行时依赖中;
是否暴露在公网链路;
当前项目是否真的使用了受影响功能;
是否有补丁版本;
升级风险有多大。
21.3 不要盲目覆盖版本 很多 Java 项目为了修漏洞,会直接在 dependencyManagement 里覆盖版本。
这种方式可以用,但不能滥用。
风险包括:
与 Spring Boot BOM 不一致;
与中间件客户端版本不兼容;
运行时出现 NoSuchMethodError;
单元测试没覆盖到的问题上线才爆。
建议:
1 优先升级平台版本,其次升级上游依赖,最后才单点覆盖。
21.4 suppression 必须审计 任何 suppression 都应该被 code review。
建议 suppression 文件放在仓库中:
1 dependency-check-suppressions.xml
并要求每次新增都说明原因。
21.5 定期生成依赖治理报表 可以每周统计:
当前 Critical 数量;
当前 High 数量;
本周新增漏洞;
本周修复漏洞;
长期未处理漏洞;
suppression 数量;
依赖升级 PR 合并率。
这可以让依赖治理从“偶尔想起来”变成“持续机制”。
22. 常见问题 22.1 Dependency-Check 能扫描业务代码漏洞吗? 不能。
它主要扫描第三方依赖漏洞。
如果要扫描业务代码里的 SQL 注入、XSS、命令注入等问题,需要 SAST 工具,比如 SonarQube、Semgrep、CodeQL 等。
22.2 Dependency-Check 和 SonarQube 是什么关系? 两者关注点不同。
工具
重点
Dependency-Check
第三方依赖漏洞
SonarQube
代码质量、代码异味、部分安全规则
CodeQL
代码级安全分析
Trivy
容器镜像、文件系统、IaC、依赖扫描
Grype
容器镜像和 SBOM 漏洞扫描
Renovate
自动升级依赖
Dependency-Check 更偏 SCA。
22.3 扫描出来的漏洞一定要修吗? 不一定,但一定要评估。
有些漏洞可能是误报,有些漏洞可能只影响特定功能,有些漏洞可能出现在测试依赖里。
但对于运行时依赖中的 Critical / High 漏洞,不建议长期拖着。
22.4 只升级直接依赖就够了吗? 不够。
很多漏洞来自传递依赖。
所以要结合:
一起分析。
22.5 本地跑很慢怎么办? 可以:
配置 NVD API Key;
使用本地缓存;
在 CI 中缓存 dependency-check-data;
使用公司内部 NVD mirror;
不要每次普通构建都跑,改用 Maven Profile;
对大项目使用定时任务集中扫描。
22.6 可以只在 CI 中跑吗? 可以,但不建议只在 CI 中跑。
更好的方式是:
1 2 3 4 开发本地可手动跑 PR 自动跑 主分支定时跑 发布前强制跑
这样问题发现得更早。
23. 推荐项目结构 对于一个 Maven 项目,可以这样组织:
1 2 3 4 5 6 7 8 9 10 11 project-root ├── pom.xml ├── dependency-check-suppressions.xml ├── .github │ └── workflows │ └── dependency-check.yml ├── src │ ├── main │ └── test └── target └── dependency-check-report.html
如果是多模块项目:
1 2 3 4 5 6 7 8 9 finance-parent ├── pom.xml ├── dependency-check-suppressions.xml ├── finance-api ├── finance-common ├── finance-domain ├── finance-application ├── finance-infrastructure └── finance-start
父 POM 负责扫描配置,子模块专注业务。
24. 一套可落地的最终 Maven 配置 下面是一套相对完整的配置,适合作为团队项目起点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <properties > <dependency-check.version > 12.2.2</dependency-check.version > </properties > <profiles > <profile > <id > security-scan</id > <build > <plugins > <plugin > <groupId > org.owasp</groupId > <artifactId > dependency-check-maven</artifactId > <version > ${dependency-check.version}</version > <configuration > <nvdApiServerId > nvd-api</nvdApiServerId > <failBuildOnCVSS > 7</failBuildOnCVSS > <formats > <format > HTML</format > <format > JSON</format > <format > JUNIT</format > <format > SARIF</format > </formats > <prettyPrint > true</prettyPrint > <skipTestScope > true</skipTestScope > <skipProvidedScope > true</skipProvidedScope > <suppressionFiles > <suppressionFile > ${project.basedir}/dependency-check-suppressions.xml</suppressionFile > </suppressionFiles > <outputDirectory > ${project.build.directory}</outputDirectory > </configuration > <executions > <execution > <goals > <goal > check</goal > </goals > </execution > </executions > </plugin > </plugins > </build > </profile > </profiles >
执行:
1 2 export NVD_API_KEY=你的_api_key mvn clean verify -Psecurity-scan
多模块项目可以把 check 改成 aggregate。
25. 最佳实践总结 最后总结一下 Dependency-Check 的最佳实践:
实践
建议
接入方式
Maven 项目优先使用 Maven Plugin
执行时机
PR、主分支、发布前、定时任务
报告格式
HTML 给人看,JSON/JUNIT/SARIF 给工具用
阈值策略
老项目先报告,后逐步提高门禁
API Key
使用 NVD API Key,不要写进 pom.xml
缓存
CI 必须缓存 Dependency-Check 数据
多模块
使用 aggregate 聚合报告
误报
使用 suppression 文件并保留审计说明
修复方式
优先升级平台版本,再考虑覆盖单包版本
自动化
配合 Renovate、OpenRewrite 形成闭环
参考资料
启示录 依赖漏洞治理不是一次性任务,而是一种工程习惯。
真正成熟的团队,不是等安全事故来了才开始升级依赖,而是在日常开发、代码合并、持续集成、上线发布中,把依赖安全变成自动化流程的一部分。
OWASP Dependency-Check 的价值不在于生成一份漂亮的 HTML 报告,而在于让项目团队看到隐藏在依赖树里的风险。
它不能替你做所有安全决策,但它能及时提醒你:
有些包,该升级了。
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。