0%

Grype 源码分析 01:快速使用

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
2
3
4
5
6
7
8
9
10
11
12
13
容器镜像 / 文件系统 / SBOM


识别软件包和发行版


获取包名、版本、PURL(Package URL)、CPE(Common Platform Enumeration)等信息


查询 Grype 漏洞数据库


生成漏洞、修复版本和风险信息

这里需要注意,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
2
grype .
grype alpine:latest

不过在 CI(Continuous Integration,持续集成)中建议显式指定目标类型,这样命令的含义更清楚,也可以避免本地目录和镜像名称产生歧义。

示例:扫描一个有漏洞的 npm 项目

接下来通过一个实际例子,直观感受一下 Grype 的扫描结果。

我们准备一个简单的 npm 项目,其中包含两个故意保持在旧版本的依赖:

1
2
3
4
5
6
7
8
9
{
"name": "grype-vulnerable-demo",
"version": "1.0.0",
"private": true,
"dependencies": {
"lodash": "4.17.20",
"minimist": "0.0.8"
}
}

项目的 package-lock.json 同样将 lodash 固定为 4.17.20,将 minimist 固定为 0.0.8。进入项目目录后执行:

1
grype dir:.

结果中的漏洞编号主要是 GHSA(GitHub Security Advisory,GitHub 安全公告)编号。

程序运行结果如下:

1
2
3
4
5
6
7
8
NAME      INSTALLED  FIXED IN  TYPE  VULNERABILITY        SEVERITY  EPSS          RISK
lodash 4.17.20 4.17.21 npm GHSA-35jh-r3h4-6jhm High 22.4% (97th) 16.5
minimist 0.0.8 0.2.4 npm GHSA-xvch-5gv4-984h Critical 4.6% (90th) 4.3
lodash 4.17.20 4.17.21 npm GHSA-29mw-wpgm-hmr9 Medium 7.3% (93rd) 3.8
lodash 4.17.20 4.18.0 npm GHSA-r5fr-rjxr-66jc High 1.7% (75th) 1.4
minimist 0.0.8 0.2.1 npm GHSA-vh95-rmgr-6w4m Medium 1.9% (77th) 1.0
lodash 4.17.20 4.17.23 npm GHSA-xxjr-mmjv-4gpg Medium 1.5% (71st) 0.9
lodash 4.17.20 4.18.0 npm GHSA-f23m-r3pf-42rh Medium 0.3% (23rd) 0.2

从结果中可以看到,Grype 一共发现了 7 条漏洞匹配:

1
2
3
Critical  1
High 2
Medium 4

表格中各个字段的含义如下:

字段 说明
NAME 软件包名称
INSTALLED 当前安装的版本
FIXED IN 漏洞得到修复的版本,为空表示当前数据中没有修复版本
TYPE 软件包类型,这里是 npm
VULNERABILITY 漏洞或安全公告 ID
SEVERITY 漏洞严重级别
EPSS 漏洞被利用的概率及其百分位
RISK Grype 计算的综合风险分数

这个结果有几个值得注意的地方:

  • 7 条结果并不表示存在 7 个有漏洞的软件包。项目中只有 2 个依赖,但一个软件包可以命中多条漏洞记录。
  • 同一个软件包的不同漏洞可能有不同的修复版本。lodash 4.17.20 对应的修复版本分别有 4.17.214.17.234.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
2
lodash    GHSA-35jh-r3h4-6jhm  High      EPSS 22.4%  Risk 16.5
minimist GHSA-xvch-5gv4-984h Critical EPSS 4.6% Risk 4.3

第一条漏洞的严重级别是 High,第二条是 Critical,但第一条的 EPSS 明显更高,所以 Risk 也更高,最终排在第一行。这说明严重级别和现实利用风险是两个不同的维度。

Grype 支持以下几种常见排序方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 综合风险优先,也是默认排序方式
grype dir:. --sort-by risk

# 严重级别优先
grype dir:. --sort-by severity

# EPSS 优先
grype dir:. --sort-by epss

# 已知在野利用优先
grype dir:. --sort-by kev

# 按软件包排序
grype dir:. --sort-by package

Risk 适合帮助我们决定“先处理哪一条”,但它并不知道应用是否暴露在公网、漏洞代码是否可达、资产中是否存放敏感数据。因此 Risk 不能完全代替业务风险分析。

JSON 输出

默认的 table 格式适合在终端中查看。如果要把结果接入 CI、漏洞平台或数据分析流程,通常需要使用 JSON:

1
grype dir:. -o json > grype.json

JSON 中的核心结构如下:

1
2
3
4
5
6
7
8
9
matches[]
├── vulnerability 漏洞 ID、严重级别、CVSS、EPSS、KEV、Risk、修复版本
├── artifact 软件包名称、版本、类型、PURL、CPE、发现位置
└── matchDetails 为什么会匹配,以及使用了哪一种 matcher

source 扫描目标信息
distro 识别出的 Linux 发行版
descriptor.db 漏洞数据库版本和构建时间
ignoredMatches[] 被 ignore 或 VEX 抑制的结果

例如,可以使用 jq 提取漏洞 ID、软件包、当前版本和修复版本:

1
2
3
4
5
6
7
8
9
10
jq -r '
.matches[] |
[
.vulnerability.id,
.artifact.name,
.artifact.version,
(.vulnerability.fix.versions | join(",")),
.vulnerability.severity
] | @tsv
' grype.json

输出结果如下:

1
2
3
4
5
6
7
GHSA-35jh-r3h4-6jhm  lodash    4.17.20  4.17.21  High
GHSA-xvch-5gv4-984h minimist 0.0.8 0.2.4 Critical
GHSA-29mw-wpgm-hmr9 lodash 4.17.20 4.17.21 Medium
GHSA-r5fr-rjxr-66jc lodash 4.17.20 4.18.0 High
GHSA-vh95-rmgr-6w4m minimist 0.0.8 0.2.1 Medium
GHSA-xxjr-mmjv-4gpg lodash 4.17.20 4.17.23 Medium
GHSA-f23m-r3pf-42rh lodash 4.17.20 4.18.0 Medium

相比只保存终端表格,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"components": [
{
"type": "library",
"name": "lodash",
"version": "4.17.20",
"purl": "pkg:npm/lodash@4.17.20"
},
{
"type": "library",
"name": "minimist",
"version": "0.0.8",
"purl": "pkg:npm/minimist@0.0.8"
}
]
}

这几个字段的含义如下:

字段 说明
bomFormat BOM 使用的标准,这里固定为 CycloneDX
specVersion CycloneDX 规范版本,这里使用 1.6
version 当前 BOM 文档自身的版本号
components 软件包含的组件列表
type 组件类型,例如 application、library、framework 或 container
name / version 组件名称和版本
purl PURL(Package URL,软件包 URL),用于跨工具标识软件包

真实的 CycloneDX 文档还可以包含 metadatadependenciesserviceslicenseshashesvulnerabilities 等字段。本例只保留 Grype 完成软件包漏洞匹配所需的核心组件信息。

执行下面的命令扫描 SBOM:

1
grype sbom:./bom.cdx.json

程序运行结果同样是 7 条漏洞匹配:

1
2
3
4
5
6
7
8
NAME      INSTALLED  FIXED IN  TYPE  VULNERABILITY        SEVERITY
lodash 4.17.20 4.17.21 npm GHSA-35jh-r3h4-6jhm High
minimist 0.0.8 0.2.4 npm GHSA-xvch-5gv4-984h Critical
lodash 4.17.20 4.17.21 npm GHSA-29mw-wpgm-hmr9 Medium
lodash 4.17.20 4.18.0 npm GHSA-r5fr-rjxr-66jc High
minimist 0.0.8 0.2.1 npm GHSA-vh95-rmgr-6w4m Medium
lodash 4.17.20 4.17.23 npm GHSA-xxjr-mmjv-4gpg Medium
lodash 4.17.20 4.18.0 npm GHSA-f23m-r3pf-42rh Medium

从这次结果可以看到,目录扫描和 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
2
14 packages from EOL distro "alpine 3.10.2" - vulnerability data may be
incomplete or outdated; consider upgrading to a supported version

这个告警表示 Alpine 3.10.2 已经结束生命周期,意味着该版本的漏洞数据可能不完整,官方也可能不再提供安全更新。对于 EOL 发行版,逐条修复 CVE 通常不是最好的方案,更合理的方式是升级基础镜像。

完整扫描一共得到 150 条匹配,涉及 8 个软件包,严重级别分布如下:

1
2
3
4
Critical   8
High 76
Medium 56
Low 10

结果中风险较高的部分如下:

1
2
3
4
5
6
7
8
9
NAME          INSTALLED  FIXED IN   TYPE  VULNERABILITY   SEVERITY  EPSS          RISK
libcrypto1.1 1.1.1c-r0 apk CVE-2022-2068 High 96.3% (99th) 79.6
libssl1.1 1.1.1c-r0 apk CVE-2022-2068 High 96.3% (99th) 79.6
libcrypto1.1 1.1.1c-r0 apk CVE-2021-3711 Critical 87.8% (99th) 77.5
libssl1.1 1.1.1c-r0 apk CVE-2021-3711 Critical 87.8% (99th) 77.5
libcrypto1.1 1.1.1c-r0 apk CVE-2022-1292 High 83.2% (99th) 68.8
libssl1.1 1.1.1c-r0 apk CVE-2022-1292 High 83.2% (99th) 68.8
libcrypto1.1 1.1.1c-r0 apk CVE-2022-0778 High 70.6% (99th) 50.0
libssl1.1 1.1.1c-r0 apk CVE-2022-0778 High 70.6% (99th) 50.0

为什么 150 条匹配只涉及 8 个软件包?因为一条漏洞可能同时命中多个包。例如 OpenSSL 被 Alpine 拆分为 libcrypto1.1libssl1.1,同一个 CVE 会分别出现在两个包上;同一个包本身也可能受到多条 CVE 影响。因此扫描结果的行数表示“软件包实例与漏洞记录的匹配数量”,不能直接理解成独立漏洞数量。

本次扫描解析到的镜像信息如下:

1
2
3
4
5
image ID:
sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4

manifest digest:
sha256:f0572185c4e54b3944acd894b5b8abf3db18d9adc28d62995779a09df4bda30b

镜像 tag 可能被覆盖,digest 则对应具体的镜像内容。在 CI 中最好扫描镜像 digest,并将 digest、SBOM 和扫描报告关联起来,这样才能准确知道报告对应的是哪一个构建产物。

CI 漏洞门禁

Grype 默认只负责输出报告。即使发现漏洞,只要扫描过程本身成功,命令通常仍然返回 0。如果希望在 CI 中阻止存在高危漏洞的构建,可以使用 --fail-on

1
grype dir:. --fail-on high

high 表示只要存在 High 或 Critical 漏洞,就让命令以非零状态结束。支持的严重级别包括:

1
2
3
4
5
negligible
low
medium
high
critical

如果只希望阻止已经存在修复版本的漏洞,可以结合 --only-fixed

1
grype dir:. --fail-on high --only-fixed

在前面的 npm 项目中运行这条命令,结果如下:

1
2
3
4
5
6
NAME      INSTALLED  FIXED IN  TYPE  VULNERABILITY        SEVERITY
lodash 4.17.20 4.17.21 npm GHSA-35jh-r3h4-6jhm High
minimist 0.0.8 0.2.4 npm GHSA-xvch-5gv4-984h Critical
...

ERROR discovered vulnerabilities at or above the severity threshold

查看退出码:

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
2
3
4
5
6
7
8
9
10
11
# 查看当前数据库状态
grype db status

# 检查是否存在新数据库
grype db check

# 更新数据库
grype db update

# 查看数据库包含的数据提供方
grype db providers

漏洞报告会受到数据库版本影响。同一个镜像在不同日期扫描,可能因为新增漏洞公告、别名关系变化、修复版本更新或 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。对于已经结束生命周期的发行版,优先升级基础镜像通常比逐条处理漏洞更有效。