代码安全学习手记(二):SCA 软件成分分析原理与 OWASP Dependency-Check 集成实战
写在前面 :上一篇手记里,我折腾了基于 Semgrep 的 SAST 静态代码安全扫描,解决了"自己写的代码里有漏洞"的问题。
但在整理项目依赖时,我发现了一个更严峻的事实:现代软件开发中,我们自己写的业务代码可能只占 10%~20%,剩下 80% 以上都是引用的第三方开源组件(npm、PyPI、Maven、Go Modules 等)。
Log4j2 漏洞(CVE-2021-44228)就是一个典型例子------业务代码一行没变,但依赖的第三方库有漏洞,整个系统就沦陷了。为了补齐这个安全短板,我决定深入研究 SCA(Software Composition Analysis,软件成分分析) ,并在 CI/CD 中集成 OWASP 官方出品的开源神器 OWASP Dependency-Check!
01. 深入理解 SCA:软件供应链安全与依赖树解析
在动手配置前,我先梳理了 SCA 的核心原理与痛点。
1.1 SCA 的工作原理:它是怎么发现漏洞的?
如果说 SAST 是给代码做"体检",那么 SCA 就是给软件做"食品成分检测"。SCA 的核心逻辑可以拆解为三步:
- 依赖清点(Bill of Materials, BOM / SBOM) :解析项目中的包管理文件(如
pom.xml、package-lock.json、requirements.txt、go.mod),提取出所有直接依赖与间接依赖的组件名称、版本号与 Hash 值。 - 知识库比对(Vulnerability Matching):将提取出的组件信息,与公开发布的漏洞数据库(如 NVD、CVE、GHSA、OSV 等)进行匹配。
- 许可证合规检查(License Compliance):顺便检查开源组件的授权协议(GPL、Apache 2.0、MIT 等),防止高风险的开源协议污染(如开源传染风险)。

1.2 传递性依赖(Transitive Dependency):最隐蔽的安全死角
在学习 SCA 时,我发现最容易踩坑的是传递性依赖(或称间接依赖)。
比如你的 Python 项目只在 requirements.txt 里写了一个 requests==2.28.0(直接依赖),但 requests 本身又依赖了 urllib3、chardet、certifi(间接依赖)。
- 致命痛点:攻击者往往不需要直接攻击你的业务代码,只要你引入的某一个 N 层间接依赖库存在漏洞,攻击者就能通过 Supply Chain Attack(供应链攻击)完成入侵。
- SCA 工具的修养 :一个合格的 SCA 工具必须能够深度递归展开完整的依赖树,而不能只查第一层。
02. 主流 SCA 工具横向对比
为了选择最适合个人学习与本地 CI/CD 集成的方案,我横向对比了几款常见的 SCA 工具:
| 维度 | OWASP Dependency-Check (本篇选择) | Snyk | Trivy | Grype |
|---|---|---|---|---|
| 开源/商业 | 完全免费且开源 | 商业软件(有免费额度) | 开源 (Aqua Security) | 开源 (Anchore) |
| 主打场景 | 应用层依赖包 (Maven/npm/PyPI等) | 全栈 (SCA + SAST + Container) | 容器镜像 + SCA | 容器镜像 + 依赖包 |
| 部署方式 | CLI / Docker / CI 插件 | 云端 SaaS / CLI | 单文件 CLI / Docker | 单文件 CLI |
| 优点 | 官方背靠 NVD,权威且规则透明 | 修复建议极强,提供自动 PR | 速度极快,容器/SCA 通吃 | 配合 Syft 生成 SBOM 极强 |
| 缺点 | 首次下载 NVD 漏洞库非常慢 | 免费版有扫描次数限制 | 对某些语言的 Lock 文件解析较浅 | 依赖数据库更新频率 |
终上所述,OWASP Dependency-Check 作为 OWASP 的旗舰项目,不仅免费开源、对应用层依赖分析深刻,而且直接对接美国国家漏洞数据库(NVD),非常适合拿来深入学习 SCA 的工作流程。
03. 环境搭建与 NVD 漏洞库本地化缓存
在实际折腾 Dependency-Check 时,我遇到了全书最大的一个坑:网络与 NVD API Rate Limit(速率限制)。
Dependency-Check 默认需要从美国 NVD 官方下载 CVE 数据库(数 GB 的 JSON 字典)。由于 NVD 在 2023~2024 年更新了 API Key 策略,如果没有申请 API Key 或者直连网络抖动,扫描任务会在拉取 CVE 数据库时卡死几小时甚至直接报错!
为了解决这个问题,我采用了 Docker + NVD API Key + 本地 Persistent Volume 缓存 的策略。

3.1 申请 NVD API Key
前往 NVD 官网申请免费的 NVD API Key。拿到 Key 后,更新速率限额会从每 30 秒 5 次提升到 50 次,更新速度提升数十倍!
3.2 挂载本地数据卷运行 Dependency-Check
通过 Docker 预先初始化本地数据卷:
bash
# 创建本地 CVE 数据持久化目录
mkdir -p $(pwd)/odc-data
# 预拉取并更新 CVE 漏洞库缓存
docker run --rm \
-e NVD_API_KEY="YOUR_NVD_API_KEY_HERE" \
-v $(pwd)/odc-data:/usr/share/dependency-check/data \
owasp/dependency-check:latest \
--updateonly
04. 编写 GitLab CI/CD 自动化 SCA 流水线
环境搞定后,接下来就是在项目中编写 .gitlab-ci.yml,将 SCA 自动化扫描集成进 CI/CD 管道中,并设置安全卡点(例如:存在 CVSS Score ≥ \ge ≥ 7.0 的高危漏洞时阻断 Pipeline)。
yaml
stages:
- test
- sast
- sca
variables:
# 开启 NVD API KEY 提高更新速度
NVD_API_KEY: "YOUR_NVD_API_KEY_HERE"
# 阶段一:SAST 静态代码扫描(上一篇手记内容)
semgrep_sast:
stage: sast
script:
- echo "Executing SAST..."
# 阶段二:SCA 软件成分分析扫描卡点
dependency_check_sca:
stage: sca
tags:
- sca-docker # 具有 Docker 执行能力的 Runner
image:
name: owasp/dependency-check:latest
entrypoint: [""]
variables:
# 挂载宿主机的预热缓存目录,避免每次 CI 重新全量下载 CVE 库
DATA_DIR: "/builds/data/dependency-check-cache"
script:
- echo "=========================================================="
- echo "======== 开始执行 OWASP Dependency-Check SCA 扫描 ========="
- echo "=========================================================="
# 执行 SCA 扫描
# --failOnCVSS 7: 当发现 CVSS 评分大于等于 7.0 (High/Critical) 的漏洞时,返回非 0 退出码,触发构建失败
/usr/share/dependency-check/bin/dependency-check.sh \
--project "Demo-Security-App" \
--scan "./" \
--format "ALL" \
--out "./sca-reports" \
--data "$DATA_DIR" \
--nvdApiKey "$NVD_API_KEY" \
--failOnCVSS 7 \
--suppression "./dependency-check-suppression.xml"
artifacts:
name: "sca-report-${CI_COMMIT_REF_SLUG}"
when: always
paths:
- ./sca-reports/dependency-check-report.html
- ./sca-reports/dependency-check-report.json
expire_in: 1 week
# 强卡点设置:一旦存在 High / Critical 级别组件漏洞,终止 CI 流水线
allow_failure: false
05. 漏洞验证与踩坑调优经验
为了测试流水线能否精准识别漏洞,我在测试项目中故意引入了两个带有历史知名漏洞的旧版本依赖:
- Java Maven (
pom.xml):引入了存在 Remote Code Execution (RCE) 的经典老包:
xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version> <!-- 存在 Log4Shell CVE-2021-44228 -->
</dependency>
- Python (
requirements.txt) :引入了旧版requests及其依赖:
text
urllib3==1.24.2 # 存在 CRLF 注入/ Header 注入风险 (CVE-2019-11324)
将代码提交到 GitLab 后,流水线被触发。
图片 3 描述:GitLab CI 控制台抛出 SCA 高危漏洞卡点失败日志示意图
(绘制建议:画一个黑底绿字/红字的终端控制台界面。日志上方显示 Dependency-Check 正在扫描
./pom.xml与./requirements.txt。中间突出标红显示警告信息:[HIGH] log4j-core-2.14.1.jar: CVE-2021-44228 (CVSSv3: 10.0) - Log4Shell RCE与[MEDIUM] urllib3-1.24.2: CVE-2019-11324 (CVSSv3: 7.5)。底部用醒目的红字打印出Analysis Complete. Failed CVSS threshold 7.0 with score 10.0. Exiting with code 1,标识 CI 管道被成功打断。)
图片 4 描述:OWASP Dependency-Check 生成的 HTML 交互式 HTML 报告截图
(绘制建议:画一个 Web 浏览器报告页面。顶部显示项目概览(包总数、高危漏洞数饼图);主区域列表列出
log4j-core-2.14.1.jar,展开后显示详细的 CVE-2021-44228 信息、CVSS 10.0 评分分值、漏洞描述、关联 CPE 编号,以及官方给出的修复建议:Update to version 2.17.1 or higher。)
踩坑调优实战经验
在折腾 SCA 流水线的过程中,我也总结了几条非常宝贵的高效落地经验:
经验 1:误报治理------使用 Suppression XML 文件
在实际项目中,有时 SCA 会产生误报(例如某组件包含某漏洞代码,但在我们的业务场景下根本不会调用到相关函数),或者某个中危漏洞已被公司安全团队认定为"不适用/暂缓修复"。
如果不做处理,CI 就会一直卡死失败。此时可以通过 Suppression(抑制/白名单机制) 来忽略特定 CVE:
在根目录下创建 dependency-check-suppression.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
<!-- 抑制指定组件的特定 CVE 误报 -->
<suppress>
<notes><![CDATA[经过安全团队评估,该 CVE 在本地内部隔离模块中不触发,暂时抑制告警]]></notes>
<packageUrl regex="true">^pkg:maven/com\.example/internal-core@.*$</packageUrl>
<cve>CVE-2022-99999</cve>
</suppress>
</suppressions>
在 CI 命令中带上 --suppression ./dependency-check-suppression.xml 即可实现灵活管控。
经验 2:针对 CI 运行速度极慢的终极调优
- 原因 :Dependency-Check 在扫描 Node.js
node_modules文件夹时,会递归扫描数以万计的小文件,导致耗时暴增到半小时以上! - 优化解法 :绝对不要扫描
node_modules目录 !SCA 只需要扫描声明依赖的文件(如package.json和package-lock.json)即可。在命令中增加排除参数:
bash
--exclude "**/node_modules/**" --exclude "**/target/**" --exclude "**/dist/**"
06. 总结与应用安全进阶路线
到这里,我的 DevSecOps 自动化安全测试体系已经搭建完成了两大核心支柱:
- SAST (Semgrep) :静态检查自己写的代码中的逻辑漏洞(如 SQL 注入、硬编码密钥)。
- SCA (Dependency-Check) :动态比对引入的第三方库中的已公开漏洞(如 CVE 漏洞、Log4j2 等)。
两者互为补充,在 CI/CD 流水线中构建起了一道坚固的代码安全防线(Shift Left Security)。