Trivy 离线漏洞扫描
> 场景:不能联网的银河麒麟 V10 高级服务器(aarch64 / ARM64) 做系统包漏洞扫描。 > 本文命令可直接复制粘贴执行,下载地址、文件大小、输出数据均经实测。
1. 这是干嘛的
Trivy 是 Aqua Security 开源的漏洞扫描器。它用一个本地漏洞库(trivy-db)去比对机器上装的软件包版本,输出「哪个包、哪个 CVE、当前版本、修复版本」。
为什么需要"离线包":Trivy 默认每次运行都会联网更新漏洞库。目标服务器不能联网,所以要把「二进制 + 漏洞库 + 配置」整套打进去,并让它彻底不联网。
它扫什么:本方案只做系统包(RPM)的 CVE。配置缺陷、密钥泄露、语言依赖(pip/npm/jar)不在范围内。
2. 去哪下载
2.1 主程序 trivy
官方发布页:https://github.com/aquasecurity/trivy/releases
直接下载(在能联网的机器上执行,下载机和目标机架构可以不同):
VER=0.74.0
ARCH=ARM64 # 目标机是 aarch64 用 ARM64;x86_64 改成 AMD64
curl -fL -o trivy.tar.gz \\
"https://github.com/aquasecurity/trivy/releases/download/v${VER}/trivy_${VER}_Linux-${ARCH}.tar.gz"
tar -xzf trivy.tar.gz trivy如果这一步卡住或下下来是 0 字节(企业网关拦截 Release CDN 的典型表现:HTTP 200 但内容是空的),换 ghcr.io 的容器镜像通道:ghcr.io/aquasecurity/trivy 的镜像层里就有 /usr/local/bin/trivy,用 oras 或 OCI Distribution API 拉下来再解层取二进制。验证方式见 2.3。
2.2 漏洞库 trivy-db
只有 OCI 通道,没有传统下载链接。以下命令已实测可跑通:
TOKEN=$(curl -s "https://ghcr.io/token?scope=repository:aquasecurity/trivy-db:pull&service=ghcr.io" \\ | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])') DIGEST=$(curl -s -H "Authorization: Bearer $TOKEN" \\ -H 'Accept: application/vnd.oci.image.manifest.v1+json' \\ https://ghcr.io/v2/aquasecurity/trivy-db/manifests/2 \\ | python3 -c 'import sys,json;print(json.load(sys.stdin)["layers"][0]["digest"])') curl -L -H "Authorization: Bearer $TOKEN" -o trivy-db.tar.gz \\ "https://ghcr.io/v2/aquasecurity/trivy-db/blobs/$DIGEST" mkdir -p db && tar -xzf trivy-db.tar.gz -C db ls -lh db/ # 应看到 trivy.db(解压后约 1.4 GB)和 metadata.json
实测当前层大小约 121 MB(压缩),解压后约 1.4 GB。
2.3 务必校验
# ① 是不是正确的架构(不装 file 也能查)
python3 - <<'PY'
import struct
d = open('trivy','rb').read(20)
mach = struct.unpack_from('<H', d, 18)[0]
print('ELF' if d[:4]==b'\\x7fELF' else '!! 不是 ELF',
{0xb7:'aarch64', 0x3e:'x86_64'}.get(mach, '未知架构'))
PY
# 期望输出:ELF aarch64
# ② 漏洞库完整性
sha256sum trivy-db.tar.gz3. 怎么部署
3.1 关键一步:修掉漏洞库的时间戳
Trivy 判断「需不需要更新」靠 metadata.json 里的 DownloadedAt。用 OCI 方式拉下来的库这个字段是零值,Trivy 无法区分「首次运行 / OCI 下载 / 文件损坏」,于是每次运行都警告 Trivy DB may be corrupted 并去联网更新——离线机上就是直接失败。必须改掉:
python3 - <<'PY'
import json
p = "db/metadata.json"
m = json.load(open(p))
if str(m.get("DownloadedAt","")).startswith("0001-"):
m["DownloadedAt"] = m["UpdatedAt"]
json.dump(m, open(p, "w"), separators=(",", ":"))
print("DownloadedAt =", m.get("DownloadedAt"))
PY
cat db/metadata.json3.2 打包带到离线机
mkdir -p trivy-offline-pkg && cd trivy-offline-pkg cp ../trivy . && cp ../trivy-db.tar.gz . && chmod 755 trivy tar -czf ../trivy-offline-pkg-aarch64.tar.gz -C .. trivy-offline-pkg sha256sum ../trivy-offline-pkg-aarch64.tar.gz
把 tar.gz 拷到离线机(U 盘 / 内网传输都行),然后:
tar -xzf trivy-offline-pkg-aarch64.tar.gz && cd trivy-offline-pkg
3.3 安装并让 sudo 也生效
sudo mkdir -p /usr/local/lib/trivy /etc/trivy /var/lib/trivy sudo cp trivy /usr/local/lib/trivy/trivy sudo chmod 755 /usr/local/lib/trivy/trivy # 铺开漏洞库 sudo mkdir -p /var/lib/trivy/db sudo tar -xzf trivy-db.tar.gz -C /var/lib/trivy # 写配置文件(注意 key 名和命令行 flag 不一样) sudo tee /etc/trivy/trivy-offline.yaml >/dev/null <<'YAML' cache: dir: /var/lib/trivy db: skip-update: true java-skip-update: true scan: offline: true pkg: types: [os] YAML # 装一层 wrapper:把配置写进命令行,绕开 sudo 丢环境变量的问题 sudo tee /usr/local/bin/trivy >/dev/null <<'SH' #!/bin/sh exec /usr/local/lib/trivy/trivy --config /etc/trivy/trivy-offline.yaml "$@" SH sudo chmod 755 /usr/local/bin/trivy # 校验 trivy --version sudo trivy --version # sudo 下也必须能跑
为什么必须装 wrapper:sudo 会重置环境变量,/etc/profile.d 里设的 TRIVY_* 传不进被 sudo 执行的命令,Trivy 就会走「检查更新」分支去联网。wrapper 把参数写死在命令行里,任何调用方式都生效。
如果要用 TRIVY_* 环境变量,再补一个 sudoers 放行(写完必须校验,写坏会导致 sudo 整体不可用):
sudo tee /etc/sudoers.d/trivy-offline >/dev/null <<'EOF' Defaults env_keep += "TRIVY_CACHE_DIR TRIVY_CONFIG TRIVY_DISTRO" EOF sudo chmod 0440 /etc/sudoers.d/trivy-offline sudo visudo -c -f /etc/sudoers.d/trivy-offline
4. 部署完怎么用
# 体检:看版本、配置、漏洞库、发行版识别是否就绪 trivy --version # 扫本机(最小可用命令) sudo trivy rootfs --scanners vuln --pkg-types os / # 导出成文件 sudo trivy rootfs --scanners vuln --pkg-types os -f json -o /root/cve.json / sudo trivy rootfs --scanners vuln --pkg-types os -f table -o /root/cve.txt /
国产系统必须补一个参数:麒麟的 /etc/os-release 里 ID=kylin,不在 Trivy 的发行版列表里,不加处理会输出 Unsupported os family="none"、什么都扫不出。麒麟 V10 属 RHEL8/CentOS8 生态,直接映射:
sudo trivy rootfs --scanners vuln --pkg-types os --distro redhat/8 -f table /
结果怎么看
kernel-headers (redhat 8.4) \================================ Total: 3 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 1) ┌───────────────┬─────────────────┬──────────┬───────────────────┬───────────────┬─────────────────┐ │ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │ ├───────────────┼─────────────────┼──────────┼───────────────────┼───────────────┼─────────────────┤ │ openssl-libs │ CVE-2023-5678 │ HIGH │ 1.1.1k-7.ky10 │ 1.1.1k-12.el8 │ openssl: ... │ │ kernel │ CVE-2026-72319 │ MEDIUM │ 4.19.90-25.ky10 │ │ kernel: ipvs... │ └───────────────┴─────────────────┴──────────┴───────────────────┴───────────────┴─────────────────┘
Fixed Version是最关键的一列:有值 = 厂商已出修复版本;空白 = 漏洞库里没有修复版本信息。⚠
Fixed Version是 RHEL 的版本号,不是你的目标版本。 国产系统的包版本号体系不同(.ky10后缀、rebase 过的功能版本),不能照抄。要拿 CVE 编号去厂商官方通道查实际修复版本。UNKNOWN一大堆不是"低风险",是库里没有该 CVE 的 CVSS 评分,要单独人工看。
三个必踩的坑
| 报错 | 原因 | 解决 |
|---|---|---|
Trivy DB may be corrupted and will be re-downloaded | metadata.json 的 DownloadedAt 是零值 + sudo 丢了环境变量 | 见 3.1 + 3.3 |
'--skip-java-db-update' cannot be specified on the first run | 传了跳过 java-db 但 java-db 元数据不存在。只在 jar 分析器被调用时才检查 | 加 --pkg-types os(禁用全部语言分析器) |
Unsupported os family="none" | 发行版不在 Trivy 列表里 | 加 --distro redhat/8 |
5. 实测例子:麒麟 V10 上的「假的 0 漏洞」
环境:银河麒麟 V10 高级服务器,aarch64,rpm -qa 共 1454 个包。Trivy v0.74.0,漏洞库更新于 2026-09-28。
第一次扫描的结果:
$ sudo trivy rootfs --scanners vuln --pkg-types os --distro redhat/8 -f table / INFO Detected OS family="redhat" version="8" INFO [redhat] Overriding detected OS with provided distro detected="none" provided="redhat/8" Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
退出码 0、不报错、1 秒结束、0 漏洞。看起来系统很干净。
真相:加上 --debug 才看到
DEBU [redhat] Skipping third-party packages packages=[...几乎全部包名...]
根因(读源码确认):Trivy 的 RPM 分析器默认把所有包标记为 third-party,只有厂商名命中一份硬编码白名单才改判为 official。这份白名单只有十来家上游厂商(Red Hat、CentOS、Fedora、AlmaLinux、Rocky、Oracle、SUSE、Amazon、VMware、Microsoft 等),一个国产发行版都没有。于是本地包全被判 third-party,被 DropThirdPartyPackages 整批丢弃,0 个包进入 CVE 匹配,自然 0 漏洞。
本机的厂商分布正好说明问题:
%{VENDOR} | 包数 | 命中白名单 |
|---|---|---|
KylinSoft | 1117 | 否 |
KylinOS | 311 | 否 |
| (空) | 19 | 否 |
kylin | 4 | 否 |
| Xiph.org / OCI / JingJiaMicro | 3 | 真第三方,本就该丢 |
教训:0 漏洞 不等于安全,它可能意味着「扫描器根本没看见你的包」。交付前必须做阳性对照——先确认扫描器认出了你的资产。修法见《给 Trivy 打国产系统适配补丁》一篇。