Grype 是 Anchore 开源的一款漏洞扫描工具,主要用来扫描容器镜像、文件系统和 SBOM 中的已知漏洞。SBOM(Software Bill of Materials,软件物料清单)是一份描述软件由哪些组件构成的清单,通常会记录依赖包的名称、版本、类型和来源等信息,可以把它理解为软件的 配料表。Grype 会先识别目标中包含的软件包,再将软件包名称、版本、发行版等信息与漏洞数据库进行匹配,最后输出漏洞等级、修复版本、EPSS(Exploit Prediction Scoring System,漏洞利用预测评分系统)、风险分数等信息。
Grype 支持 Alpine、Debian、Ubuntu、RHEL(Red Hat Enterprise Linux,红帽企业 Linux)等常见 Linux 发行版,同时也支持 npm、Maven、Go、Python、Ruby、Rust 等语言生态。对于日常开发来说,可以用它扫描当前项目;对于容器场景,可以直接扫描镜像;如果已经有 Syft、CycloneDX 或 SPDX(Software Package Data Exchange,软件包数据交换格式)格式的 SBOM,也可以跳过软件包发现阶段,直接进行漏洞匹配。
Grype 简介
传统的依赖漏洞检查工具通常只关注某一种语言,例如 npm audit 主要检查 npm 依赖,govulncheck 主要检查 Go 模块。如果一个容器镜像中同时包含 Alpine 系统包、Node.js 依赖和 Go 二进制,就需要多个工具分别处理。
Grype 的目标是把这些输入统一起来。无论输入的是目录、镜像还是 SBOM,最终都会转换成一组软件包,再根据不同的软件包类型使用对应的版本规则进行漏洞匹配。
Grype 的核心流程如下:
1 | 容器镜像 / 文件系统 / SBOM |
这里需要注意,Grype 做的是已知组件漏洞匹配。它不会运行应用程序,也不会主动发送攻击请求,因此它和 DAST(Dynamic Application Security Testing,动态应用安全测试)、端口扫描、Web 漏洞扫描不是一类工具。一条扫描结果表示 某个软件包版本落在某条漏洞记录的影响范围内,至于漏洞代码在当前业务中是否真的可达,还需要结合代码和运行环境进一步判断。
Grype 的核心特性如下:
| 特性 | 说明 |
|---|---|
| 多种扫描目标 | 支持容器镜像、目录、文件、归档、SBOM、PURL 等输入 |
| 多种软件包生态 | 支持 apk(Alpine Linux 软件包)、deb(Debian 软件包)、rpm(RPM Package Manager 软件包)、npm、Maven、Go、Python、Ruby 等 |
| 发行版感知 | 根据 Alpine、Debian、Ubuntu、RHEL 等发行版的版本规则匹配漏洞 |
| 修复信息 | 展示漏洞是否已经修复,以及可以升级到哪个版本 |
| 风险排序 | 综合 Severity、CVSS(Common Vulnerability Scoring System,通用漏洞评分系统)、EPSS、KEV(Known Exploited Vulnerabilities,已知被利用漏洞)等信息计算 Risk |
| 多种输出格式 | 支持 table、JSON、SARIF(Static Analysis Results Interchange Format,静态分析结果交换格式)、CycloneDX 和自定义模板 |
| 策略控制 | 支持严重级别门禁、ignore 规则和 VEX(Vulnerability Exploitability eXchange,漏洞可利用性交换)文档 |
Grype 实际使用
安装 Grype
在 Linux 上,可以使用官方安装脚本:
1 | curl -sSfL https://get.anchore.io/grype | sudo sh -s -- -b /usr/local/bin |
安装完成后查看版本:
1 | grype version |
第一次扫描时,Grype 会自动下载漏洞数据库。可以通过下面的命令查看数据库状态:
1 | grype db status |
本文实际测试使用的是 Grype v0.115.0 对应代码,内嵌 Syft 版本为 v1.46.0,漏洞数据库 schema 为 v6.1.7。漏洞数据库构建时间为 2026-07-15T06:58:38Z。由于漏洞数据库会持续更新,以后使用相同输入进行扫描时,漏洞数量、EPSS 和 Risk 可能发生变化。
基础命令速览
Grype 最基本的使用方式如下:
1 | grype <扫描目标> |
可以直接扫描当前目录:
1 | grype dir:. |
也可以扫描本地 Docker 镜像:
1 | grype docker:alpine:3.10.2 |
如果本地没有 Docker,也可以直接从镜像仓库读取镜像:
1 | grype registry:alpine:3.10.2 |
如果已经有 SBOM,可以直接扫描 SBOM:
1 | grype sbom:./bom.cdx.json |
Grype 支持的常见目标如下:
| 目标 | 示例 | 说明 |
|---|---|---|
| 目录 | dir:./my-project |
扫描项目目录或解压后的根文件系统 |
| Docker 镜像 | docker:nginx:latest |
从本地 Docker daemon 读取镜像 |
| 远程镜像 | registry:nginx:latest |
直接从镜像仓库读取镜像 |
| 单个文件 | file:./app.jar |
扫描单个文件或归档 |
| SBOM | sbom:./sbom.json |
扫描已有的 SBOM |
| PURL 文件 | purl:./packages.txt |
扫描逐行保存的 Package URL |
| OCI 归档 | oci-archive:./image.tar |
扫描 OCI(Open Container Initiative,开放容器倡议)格式归档 |
也可以省略 dir:、docker: 等前缀,让 Grype 自动判断目标类型。例如:
1 | grype . |
不过在 CI(Continuous Integration,持续集成)中建议显式指定目标类型,这样命令的含义更清楚,也可以避免本地目录和镜像名称产生歧义。
示例:扫描一个有漏洞的 npm 项目
接下来通过一个实际例子,直观感受一下 Grype 的扫描结果。
我们准备一个简单的 npm 项目,其中包含两个故意保持在旧版本的依赖:
1 | { |
项目的 package-lock.json 同样将 lodash 固定为 4.17.20,将 minimist 固定为 0.0.8。进入项目目录后执行:
1 | grype dir:. |
结果中的漏洞编号主要是 GHSA(GitHub Security Advisory,GitHub 安全公告)编号。
程序运行结果如下:
1 | NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK |
从结果中可以看到,Grype 一共发现了 7 条漏洞匹配:
1 | Critical 1 |
表格中各个字段的含义如下:
| 字段 | 说明 |
|---|---|
| NAME | 软件包名称 |
| INSTALLED | 当前安装的版本 |
| FIXED IN | 漏洞得到修复的版本,为空表示当前数据中没有修复版本 |
| TYPE | 软件包类型,这里是 npm |
| VULNERABILITY | 漏洞或安全公告 ID |
| SEVERITY | 漏洞严重级别 |
| EPSS | 漏洞被利用的概率及其百分位 |
| RISK | Grype 计算的综合风险分数 |
这个结果有几个值得注意的地方:
- 7 条结果并不表示存在 7 个有漏洞的软件包。项目中只有 2 个依赖,但一个软件包可以命中多条漏洞记录。
- 同一个软件包的不同漏洞可能有不同的修复版本。
lodash 4.17.20对应的修复版本分别有4.17.21、4.17.23和4.18.0,只升级到第一行显示的版本并不一定能解决全部问题。 - 漏洞 ID 不一定是 CVE(Common Vulnerabilities and Exposures,通用漏洞披露)。npm 生态的结果通常优先显示 GHSA。如果希望尽量以 CVE 展示,可以增加
--by-cve。 - 默认结果不是简单地按照 Critical、High、Medium 排序,而是按照 Risk 排序,所以 High 漏洞可能出现在 Critical 漏洞之前。
Severity、EPSS、KEV 和 Risk
在传统漏洞报告中,我们通常只关注 Severity 或 CVSS。Grype 除了显示严重级别,还会显示 EPSS 和 Risk,并可以使用 KEV 数据辅助排序。
这几个指标的含义如下:
| 指标 | 回答的问题 |
|---|---|
| Severity / CVSS | 漏洞被成功利用后,影响有多严重 |
| EPSS | 漏洞在现实世界中被利用的可能性有多高 |
| CISA(Cybersecurity and Infrastructure Security Agency,美国网络安全和基础设施安全局)KEV | 漏洞是否已经存在已知的在野利用 |
| Risk | Grype 综合威胁信息和严重级别后计算的排序分数 |
当前版本的 Risk 计算会综合字符串严重级别、CVSS 和 EPSS。如果漏洞存在于 KEV 中,Grype 会优先采用已经存在利用证据这一事实;如果还涉及已知勒索软件活动,会进一步提高风险修正值。
来看 npm 示例中的前两条结果:
1 | lodash GHSA-35jh-r3h4-6jhm High EPSS 22.4% Risk 16.5 |
第一条漏洞的严重级别是 High,第二条是 Critical,但第一条的 EPSS 明显更高,所以 Risk 也更高,最终排在第一行。这说明严重级别和现实利用风险是两个不同的维度。
Grype 支持以下几种常见排序方式:
1 | # 综合风险优先,也是默认排序方式 |
Risk 适合帮助我们决定“先处理哪一条”,但它并不知道应用是否暴露在公网、漏洞代码是否可达、资产中是否存放敏感数据。因此 Risk 不能完全代替业务风险分析。
JSON 输出
默认的 table 格式适合在终端中查看。如果要把结果接入 CI、漏洞平台或数据分析流程,通常需要使用 JSON:
1 | grype dir:. -o json > grype.json |
JSON 中的核心结构如下:
1 | matches[] |
例如,可以使用 jq 提取漏洞 ID、软件包、当前版本和修复版本:
1 | jq -r ' |
输出结果如下:
1 | GHSA-35jh-r3h4-6jhm lodash 4.17.20 4.17.21 High |
相比只保存终端表格,JSON 还包含软件包位置、匹配依据和数据库版本等信息,更适合作为后续审计和结果对比的输入。
示例:扫描 CycloneDX SBOM
Grype 扫描目录或镜像时,需要先识别其中包含的软件包。如果构建阶段已经生成了 SBOM,就可以直接让 Grype 使用这份软件包清单。
CycloneDX 是由 OWASP(Open Worldwide Application Security Project,开放全球应用安全项目)维护的一种 BOM(Bill of Materials,物料清单)标准,主要面向软件供应链安全。它可以使用 JSON 或 XML 描述软件组件、版本、哈希、许可证、组件之间的依赖关系以及已知漏洞等信息。SBOM 是软件物料清单这一类数据的统称,CycloneDX 则是描述和交换这类数据的一种具体标准格式。
下面是一份简化后的 CycloneDX SBOM,其中包含前面使用的两个 npm 组件:
1 | { |
这几个字段的含义如下:
| 字段 | 说明 |
|---|---|
bomFormat |
BOM 使用的标准,这里固定为 CycloneDX |
specVersion |
CycloneDX 规范版本,这里使用 1.6 |
version |
当前 BOM 文档自身的版本号 |
components |
软件包含的组件列表 |
type |
组件类型,例如 application、library、framework 或 container |
name / version |
组件名称和版本 |
purl |
PURL(Package URL,软件包 URL),用于跨工具标识软件包 |
真实的 CycloneDX 文档还可以包含 metadata、dependencies、services、licenses、hashes 和 vulnerabilities 等字段。本例只保留 Grype 完成软件包漏洞匹配所需的核心组件信息。
执行下面的命令扫描 SBOM:
1 | grype sbom:./bom.cdx.json |
程序运行结果同样是 7 条漏洞匹配:
1 | NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY |
从这次结果可以看到,目录扫描和 SBOM 扫描最终识别出了相同的软件包名称、类型和版本,因此得到了相同的漏洞结果。
不过在真实项目中,两种方式不一定永远一致。例如容器镜像中可能同时存在 OS 包、Go 二进制、Java 归档和多份锁文件,而 SBOM 的内容又取决于生成工具及其配置。如果在生产中采用 SBOM 扫描,应该固定 SBOM 生成工具和配置,并定期对比“直接扫描产物”和“扫描 SBOM”的差异。
示例:扫描容器镜像
接下来扫描一个真实的容器镜像。这里选择 alpine:3.10.2,先将镜像拉取到本地:
1 | docker pull alpine:3.10.2 |
然后使用 Grype 扫描:
1 | grype docker:alpine:3.10.2 |
Grype 首先给出了 EOL(End of Life,生命周期结束)告警:
1 | 14 packages from EOL distro "alpine 3.10.2" - vulnerability data may be |
这个告警表示 Alpine 3.10.2 已经结束生命周期,意味着该版本的漏洞数据可能不完整,官方也可能不再提供安全更新。对于 EOL 发行版,逐条修复 CVE 通常不是最好的方案,更合理的方式是升级基础镜像。
完整扫描一共得到 150 条匹配,涉及 8 个软件包,严重级别分布如下:
1 | Critical 8 |
结果中风险较高的部分如下:
1 | NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK |
为什么 150 条匹配只涉及 8 个软件包?因为一条漏洞可能同时命中多个包。例如 OpenSSL 被 Alpine 拆分为 libcrypto1.1 和 libssl1.1,同一个 CVE 会分别出现在两个包上;同一个包本身也可能受到多条 CVE 影响。因此扫描结果的行数表示“软件包实例与漏洞记录的匹配数量”,不能直接理解成独立漏洞数量。
本次扫描解析到的镜像信息如下:
1 | image ID: |
镜像 tag 可能被覆盖,digest 则对应具体的镜像内容。在 CI 中最好扫描镜像 digest,并将 digest、SBOM 和扫描报告关联起来,这样才能准确知道报告对应的是哪一个构建产物。
CI 漏洞门禁
Grype 默认只负责输出报告。即使发现漏洞,只要扫描过程本身成功,命令通常仍然返回 0。如果希望在 CI 中阻止存在高危漏洞的构建,可以使用 --fail-on:
1 | grype dir:. --fail-on high |
high 表示只要存在 High 或 Critical 漏洞,就让命令以非零状态结束。支持的严重级别包括:
1 | negligible |
如果只希望阻止已经存在修复版本的漏洞,可以结合 --only-fixed:
1 | grype dir:. --fail-on high --only-fixed |
在前面的 npm 项目中运行这条命令,结果如下:
1 | NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY |
查看退出码:
1 | echo $? |
实际输出为:
1 | 2 |
这说明 Grype 完成了扫描,但扫描结果没有通过设定的安全策略。CI 可以将退出码 2 识别为漏洞门禁失败,而参数错误、数据库下载失败等其他非零状态应该按照工具执行失败处理。
需要注意,--only-fixed 只是降低 CI 噪声的一种策略。没有修复版本的漏洞仍然可能需要隔离、缓解或接受风险,因此完整报告中仍然应该保留这些结果。
输出格式
Grype 支持以下常见输出格式:
| 格式 | 使用场景 |
|---|---|
| table | 终端中快速查看 |
| json | 自动化分析、结果存档、漏洞平台 |
| sarif | GitHub Code Scanning 等 SARIF 消费端 |
| cyclonedx-json | CycloneDX 工具链 |
| template | 使用 Go template 自定义报告 |
输出 table:
1 | grype docker:alpine:3.10.2 -o table |
输出 JSON:
1 | grype docker:alpine:3.10.2 -o json > grype.json |
输出 SARIF:
1 | grype docker:alpine:3.10.2 -o sarif > grype.sarif |
在日常使用中,可以在终端查看 table,同时在 CI 中保存 JSON 或 SARIF。只保留终端日志会丢失软件包位置、匹配依据和数据库版本等信息,不方便后续分析。
漏洞数据库
Grype 会在扫描前自动检查漏洞数据库。常用数据库命令如下:
1 | # 查看当前数据库状态 |
漏洞报告会受到数据库版本影响。同一个镜像在不同日期扫描,可能因为新增漏洞公告、别名关系变化、修复版本更新或 EPSS 更新而得到不同结果。因此需要复现一次扫描时,至少应该记录:
- Grype 版本
- 漏洞数据库构建时间
- 镜像 digest 或原始 SBOM
- Grype 配置和 ignore/VEX 规则
- 原始 JSON 或 SARIF 报告
完全关闭数据库更新虽然可以让结果暂时保持不变,但也会使扫描结果快速过期。更合理的做法是定期更新数据库,并在报告中记录本次实际使用的数据库信息。
结果过滤和 VEX
当项目中存在较多历史漏洞时,可以使用过滤选项缩小当前关注的范围。
只显示已经有修复版本的漏洞:
1 | grype dir:. --only-fixed |
只显示尚无修复版本的漏洞:
1 | grype dir:. --only-notfixed |
排除不需要扫描的目录:
1 | grype dir:. --exclude './vendor/**' |
尽量使用 CVE 作为漏洞 ID:
1 | grype dir:. --by-cve |
如果产品团队已经通过 VEX 声明某条漏洞不影响当前产品,可以在扫描时传入 VEX 文档:
1 | grype sbom:./bom.cdx.json --vex ./openvex.json |
查看被 ignore 或 VEX 抑制的结果:
1 | grype dir:. --show-suppressed |
VEX 和 ignore 的作用是表达已经分析过的例外,而不是为了让报告数字变小。每条例外都应该有明确原因,并在依赖版本、产品结构或运行环境发生变化后重新检查。
小结
这篇文章我们学习了 Grype 的基本用法。Grype 可以直接扫描目录、容器镜像和 SBOM,并根据软件包类型、版本和发行版信息匹配已知漏洞。
通过实际扫描可以看到,Grype 的一行结果表示“软件包与漏洞记录的一次匹配”,并不等于一个独立的软件包或独立漏洞。FIXED IN 可以帮助判断升级目标,Severity、EPSS、KEV 和 Risk 则从不同角度帮助我们确定修复优先级。
在实际项目中,可以使用 table 在本地查看结果,使用 JSON 或 SARIF 保存完整报告,再通过 --fail-on 将漏洞策略接入 CI。对于已经结束生命周期的发行版,优先升级基础镜像通常比逐条处理漏洞更有效。