清单、SBOM,以及门禁
本实验在真正的 VM 中运行
这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker exec 和 docker logs 也都能照常工作。
过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。
有两点需要了解。
- **首次启动大约需要 1 分钟。**因为 VM 需要启动并安装 Docker,比 Pod 实验(通常 40 秒)更慢。
- **没有浏览器预览。**进入 VM 的连接只开放评分端口。如果启动了 Web 服务器,请在 VM 内使用
curl检查。
目标
手动重现扫描器的实际流程(软件包清单→数据库比对)。提取基础镜像和运行时镜像的软件包列表,用数字统计增加的攻击面,亲自制作 SBOM、编写策略门禁脚本,并找出和修复固化在镜像中的密钥。
为什么重要
把扫描器当成魔法黑箱会得出两种错误结论:“扫描干净就是安全”和“1,247 项全都要修”。扫描器既不运行镜像,也不分析代码,只把已安装软件包的名称和版本与漏洞数据库比对。因此,通过 curl | tar xz 放入的二进制文件完全不可见,从未调用的软件包漏洞却仍会列出。手动重现这一原理后,就会明白首先要问的不是“修什么”,而是**“这个包为什么会在镜像中”**。如果应用只链接 8 个共享库,镜像却装了 432 个包,问题就在镜像构成而不是漏洞。
步骤
- 创建
/root/sec2目录,将alpine:3.20中已安装的软件包以每行一个이름-버전的格式保存到/root/sec2/alpine-pkgs.txt。至少 10 行,且必须包含musl。 - 以相同格式将
nginx:1.27-alpine的软件包列表保存到/root/sec2/nginx-pkgs.txt。至少 10 行,必须包含nginx,且行数多于第 1 步。 - 按去掉版本的软件包名称比较两个列表,将 nginx 独有的软件包数量以纯数字保存到
/root/sec2/extra-count.txt(允许误差 2)。 - 创建
/root/sec2/sbom.json。顶层image值为nginx:1.27-alpine,packages为数组,且项目数必须等于第 2 步文件的行数。每项都必须含有非空的name和version。 - 在
/root/sec2/deny.txt中每行写一个禁止的软件包名称(必须包含curl)。再编写有执行权限的/root/sec2/gate.sh:第一个参数指定的软件包列表文件中没有禁用包时退出码为 0;存在时以非 0 退出码结束,并输出命中的软件包名称。 - 使用把
ARG接收的值传给ENV APP_TOKEN的 Dockerfile 构建labhub/leak:v1,并将能从镜像配置中直接读出的令牌值保存到/root/sec2/leak.txt。 - 不带令牌构建同一应用,生成
labhub/leak:v2(镜像 Env 中不得有APP_TOKEN)。再用labhub/leak:v2启动sec-runtime容器,并在运行时注入APP_TOKEN。 - 在
/root/sec2/scan.md中严格按此格式写三行。package_count=<2단계 파일의 줄 수>denied_hits=0secret_in_image=no
参考
- Alpine 镜像中的已安装包可用
docker run --rm <이미지> apk info -v逐行输出为이름-버전。该命令读取镜像中的普通文本文件/lib/apk/db/installed;直接打开也能看到相同内容——漏洞扫描器的第一步正是读取此文件。 - 去掉版本可使用
sed 's/-[0-9].*$//',在第一个数字前截断。排序后可用comm -13统计仅一侧存在的项目。 - 使用
docker image inspect labhub/leak:v1 | jq -r '.[0].Config.Env[]'查看镜像环境变量。 - 构建参数使用
docker build --build-arg APP_TOKEN=<값>,运行时注入使用docker run -e APP_TOKEN=<값>。 - 常见错误 1:第 1、2 步采用不同格式会导致第 3 步比较全部错位。
- 常见错误 2:第 5 步违规时若不输出任何内容,就无法通过。门禁必须说明原因。
- 常见错误 3:第 7 步若用
labhub/leak:v1启动容器会失败,必须使用labhub/leak:v2。
基础镜像软件包清单
创建 /root/sec2 目录,将 alpine:3.20 中已安装的软件包以每行一个 이름-버전 的格式保存到 /root/sec2/alpine-pkgs.txt。至少 10 行,且必须包含 musl。
这是扫描器最先做的事。alpine 可查询 apk 安装的软件包,并以名称和版本组合的一行格式输出。它只读取本地数据库,无需网络,离线也能工作。
与运行时镜像比较
以相同格式将 nginx:1.27-alpine 的软件包列表保存到 /root/sec2/nginx-pkgs.txt。至少 10 行,必须包含 nginx,且行数多于第 1 步。
必须以与第 1 步完全相同的格式输出,下一步才能比较。同为 alpine 基础镜像,nginx 镜像的行数应更多;差值就是增加的攻击面。
统计增加的攻击面
按去掉版本的软件包名称比较两个列表,将 nginx 独有的软件包数量以纯数字保存到 /root/sec2/extra-count.txt(允许误差 2)。
带版本字符串时,同一个包也会看起来不同。请从 이름-버전 中去掉版本,只按名称比较。排序后有标准工具可统计单侧项目。文件中只保留数字。
亲手制作 SBOM
创建 /root/sec2/sbom.json。顶层 image 值为 nginx:1.27-alpine,packages 为数组,且项目数必须等于第 2 步文件的行数。每项都必须含有非空的 name 和 version。
此步骤不是使用工具,而是理解结构。将第 2 步列表的每行拆分为名称和版本,组成 JSON 数组。项目数必须与第 2 步行数完全相同;只含名称而版本为空的项目无法比对,会导致失败。
策略门禁脚本
在 /root/sec2/deny.txt 中每行写一个禁止的软件包名称(必须包含 curl)。再编写有执行权限的 /root/sec2/gate.sh:第一个参数指定的软件包列表文件中没有禁用包时退出码为 0;存在时以非 0 退出码结束,并输出命中的软件包名称。
检查参数指定的列表文件:通过时返回 0,违规时返回非 0。违规时若不输出具体命中的软件包,就没人知道原因。还要按去掉版本后的名称精确匹配——curl 规则不能误报 libcurl。
通过 ENV 传入的令牌会固化在镜像中
使用把 ARG 接收的值传给 ENV APP_TOKEN 的 Dockerfile 构建 labhub/leak:v1,并将能从镜像配置中直接读出的令牌值保存到 /root/sec2/leak.txt。
创建将 ARG 值传给 ENV 的 Dockerfile,通过构建参数传入令牌。然后确认该值可从镜像配置中直接读取,并写入文件。关键在于,即使不是镜像制作者也能读取。
保持镜像干净,在运行时注入密钥
不带令牌构建同一应用,生成 labhub/leak:v2(镜像 Env 中不得有 APP_TOKEN)。再用 labhub/leak:v2 启动 sec-runtime 容器,并在运行时注入 APP_TOKEN。
不带令牌重新构建同一应用,使镜像 Env 中不留下任何内容;需要的值在启动容器时注入。若留在镜像中,本步骤会失败。
扫描摘要
在 /root/sec2/scan.md 中严格按此格式写三行。
package_count=<2단계 파일의 줄 수>denied_hits=0secret_in_image=no
三行都采用 키=값 格式。软件包数必须等于第 2 步文件行数;最终镜像中没有禁用包和密钥,因此其余两个值分别为 0 和 no。与实际状态不符时评分会发现。