LabHub
学习 学习路径 课程

容器安全

清单、SBOM,以及门禁

在 LabHub 中继续学习

本实验在真正的 VM 中运行

这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker execdocker logs 也都能照常工作。

过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。

有两点需要了解。

目标

手动重现扫描器的实际流程(软件包清单→数据库比对)。提取基础镜像和运行时镜像的软件包列表,用数字统计增加的攻击面,亲自制作 SBOM、编写策略门禁脚本,并找出和修复固化在镜像中的密钥。

为什么重要

把扫描器当成魔法黑箱会得出两种错误结论:“扫描干净就是安全”和“1,247 项全都要修”。扫描器既不运行镜像,也不分析代码,只把已安装软件包的名称和版本与漏洞数据库比对。因此,通过 curl | tar xz 放入的二进制文件完全不可见,从未调用的软件包漏洞却仍会列出。手动重现这一原理后,就会明白首先要问的不是“修什么”,而是**“这个包为什么会在镜像中”**。如果应用只链接 8 个共享库,镜像却装了 432 个包,问题就在镜像构成而不是漏洞。

步骤

  1. 创建 /root/sec2 目录,将 alpine:3.20 中已安装的软件包以每行一个 이름-버전 的格式保存到 /root/sec2/alpine-pkgs.txt。至少 10 行,且必须包含 musl
  2. 以相同格式将 nginx:1.27-alpine 的软件包列表保存到 /root/sec2/nginx-pkgs.txt。至少 10 行,必须包含 nginx,且行数多于第 1 步。
  3. 去掉版本的软件包名称比较两个列表,将 nginx 独有的软件包数量以纯数字保存到 /root/sec2/extra-count.txt(允许误差 2)。
  4. 创建 /root/sec2/sbom.json。顶层 image 值为 nginx:1.27-alpinepackages 为数组,且项目数必须等于第 2 步文件的行数。每项都必须含有非空的 nameversion
  5. /root/sec2/deny.txt 中每行写一个禁止的软件包名称(必须包含 curl)。再编写有执行权限的 /root/sec2/gate.sh:第一个参数指定的软件包列表文件中没有禁用包时退出码为 0;存在时以非 0 退出码结束,并输出命中的软件包名称
  6. 使用把 ARG 接收的值传给 ENV APP_TOKEN 的 Dockerfile 构建 labhub/leak:v1,并将能从镜像配置中直接读出的令牌值保存到 /root/sec2/leak.txt
  7. 不带令牌构建同一应用,生成 labhub/leak:v2(镜像 Env 中不得有 APP_TOKEN)。再用 labhub/leak:v2 启动 sec-runtime 容器,并在运行时注入 APP_TOKEN
  8. /root/sec2/scan.md 中严格按此格式写三行。
    • package_count=<2단계 파일의 줄 수>
    • denied_hits=0
    • secret_in_image=no

参考

基础镜像软件包清单

创建 /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-alpinepackages 为数组,且项目数必须等于第 2 步文件的行数。每项都必须含有非空的 nameversion

此步骤不是使用工具,而是理解结构。将第 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 中严格按此格式写三行。

三行都采用 키=값 格式。软件包数必须等于第 2 步文件行数;最终镜像中没有禁用包和密钥,因此其余两个值分别为 0 和 no。与实际状态不符时评分会发现。