1. 容器镜像基础信息采集与安全基线检查
镜像安全基线检查的对象与数据流
对容器镜像做安全基线检查,第一步不是直接跑漏洞扫描,而是先建立“镜像身份档案”:镜像从哪来、由哪些层叠加、运行时暴露什么端口、注入了哪些环境变量、默认以什么用户启动。这些信息决定了后续漏洞扫描的上下文——同一个 CVE 在不同基础镜像、不同暴露面下的风险等级完全不同。
本案例以 nginx:1.25.3 为教学示例镜像,完成两条采集链路:
- 元数据采集:用
docker inspect提取镜像 ID、RepoDigests、层数量、暴露端口、环境变量、Entrypoint/Cmd、默认 User。 - 漏洞基线扫描:用
trivy image输出 OS 包与语言依赖的已知漏洞清单,并按严重级别过滤。
元数据采集:docker inspect 关键字段提取
直接执行 docker inspect nginx:1.25.3 会返回一个巨大的 JSON 数组,其中真正与安全基线相关的字段集中在少数几个路径。下面用 --format 模板一次性提取关键字段,避免人工翻找。
docker inspect nginx:1.25.3 --format '
镜像ID: {{.Id}}
摘要: {{index .RepoDigests 0}}
层数: {{len .RootFS.Layers}}
暴露端口: {{range $p, $conf := .Config.ExposedPorts}}{{$p}} {{end}}
环境变量: {{range .Config.Env}}{{println .}}{{end}}
默认用户: {{.Config.User}}
Entrypoint: {{.Config.Entrypoint}}
Cmd: {{.Config.Cmd}}
'
示例输出(教学示例,实际值以本地镜像为准):
镜像ID: sha256:4b8f...c3a1
摘要: nginx@sha256:9c2f...e7b4
层数: 7
暴露端口: 80/tcp
环境变量: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
环境变量: NGINX_VERSION=1.25.3
环境变量: PKG_RELEASE=1~bookworm
默认用户: (空)
Entrypoint: [/docker-entrypoint.sh]
Cmd: [nginx -g daemon off;]
关键判读:默认用户为空意味着容器以 root 运行,这是基线检查中的高风险项。暴露端口只有 80/tcp 属于正常范围;若出现 22/tcp 或 2375/tcp 等非必要端口,应标记为警告。
漏洞基线扫描:trivy 输出与过滤
trivy 对镜像的扫描会同时覆盖 OS 包(如 Debian 的 dpkg 包)和语言依赖(如 npm、pip 包)。使用 --severity 过滤出需要立即关注的级别,并用 --format json 保留结构化数据以便后续处理。
trivy image --severity CRITICAL,HIGH --format json -o trivy-nginx.json nginx:1.25.3
若只需快速查看表格结果,可省略 --format json:
trivy image --severity CRITICAL,HIGH nginx:1.25.3
示例输出片段(教学示例,实际漏洞以扫描时数据库为准):
nginx:1.25.3 (debian 12.2)
Total: 3 (CRITICAL: 1, HIGH: 2)
┌────────────┬────────────────┬──────────┬───────────────────┐
│ Library │ Vulnerability │ Severity │ Fixed Version │
├────────────┼────────────────┼──────────┼───────────────────┤
│ libssl3 │ CVE-2023-5678 │ CRITICAL │ 3.0.11-1~deb12u2 │
│ libc6 │ CVE-2023-4911 │ HIGH │ 2.36-9+deb12u4 │
│ zlib1g │ CVE-2023-45853 │ HIGH │ 1:1.2.13.dfsg-1 │
└────────────┴────────────────┴──────────┴───────────────────┘
基线检查结果汇总与判定
将两条链路的结果合并,形成一份可执行的基线检查结论。以下为教学示例的汇总表:
| 检查项 | 采集值 | 基线要求 | 判定 |
|---|---|---|---|
| 镜像摘要 | sha256:9c2f...e7b4 | 使用固定摘要而非 latest | 通过 |
| 层数量 | 7 | ≤ 15 层 | 通过 |
| 暴露端口 | 80/tcp | 仅暴露业务必需端口 | 通过 |
| 默认用户 | 空(root) | 非 root 用户 | 不通过 |
| CRITICAL 漏洞 | 1 个(libssl3) | 0 个 | 不通过 |
| HIGH 漏洞 | 2 个(libc6、zlib1g) | ≤ 0 个 | 警告 |
结论:该镜像存在 1 个 CRITICAL 漏洞且默认以 root 运行,基线检查不通过。优先处理 libssl3 的 CVE-2023-5678(升级到 3.0.11-1~deb12u2),并在 Dockerfile 中增加非 root 用户指令后重新构建镜像。
可复用的检查脚本
将上述两条链路封装为一个可重复执行的脚本,输出元数据摘要和漏洞统计,便于批量检查多个镜像。
#!/bin/bash
IMAGE="${1:-nginx:1.25.3}"
echo "=== 镜像元数据 ==="
docker inspect "$IMAGE" --format '
镜像ID: {{.Id}}
摘要: {{index .RepoDigests 0}}
层数: {{len .RootFS.Layers}}
暴露端口: {{range $p, $conf := .Config.ExposedPorts}}{{$p}} {{end}}
默认用户: {{.Config.User}}
'
echo ""
echo "=== 漏洞扫描(CRITICAL/HIGH)==="
trivy image --severity CRITICAL,HIGH --format table "$IMAGE"
echo ""
echo "=== 漏洞统计 ==="
trivy image --severity CRITICAL,HIGH --format json "$IMAGE" \
| python3 -c "
import json,sys
data=json.load(sys.stdin)
crit=high=0
for r in data.get('Results',[]):
for v in r.get('Vulnerabilities',[]):
if v['Severity']=='CRITICAL': crit+=1
elif v['Severity']=='HIGH': high+=1
print(f'CRITICAL: {crit}, HIGH: {high}')
"
执行方式:bash check-image.sh nginx:1.25.3。脚本输出可直接作为基线检查记录归档,CRITICAL 数量大于 0 或默认用户为空时判定为不通过。