逆向工程实战手册 第 54 章

第 54 章 云原生与容器逆向(K8s / 镜像 / 逃逸)

现代应用一半跑在容器里、编排在 K8s 上。"云原生逆向" = 分析镜像内容、容器运行时行为、K8s 控制面与工作负载、以及"容器逃逸"这一云安全核心课题。云原生的一切都是"文件+配置+运行时"三层,逆向就顺着这三层走。

📍 知识点地图 | 主题:云原生容器 | 前置:第33章 | 后续:第87章 | 核心概念:镜像、K8s、逃逸、SA token、元数据

54.1 云原生攻击面地图

┌─────────────────────────────────────────────┐
│  K8s 控制面(API Server / etcd / Scheduler) │ ← 控制权所在
├─────────────────────────────────────────────┤
│  工作负载(Pod / Deployment / CronJob)      │ ← 业务所在
├─────────────────────────────────────────────┤
│  容器运行时(containerd / CRI-O / Docker)   │ ← 隔离所在
├─────────────────────────────────────────────┤
│  镜像仓库(Registry)                        │ ← 供应链所在
├─────────────────────────────────────────────┤
│  节点/宿主(OS / Kernel)                    │ ← 逃逸目标
└─────────────────────────────────────────────┘

攻击路径: 应用漏洞 → 容器 → 节点 → 集群 → 云账户
         (每一层跳跃 = 一个"逃逸/横向"动作)

54.2 镜像逆向(供应链起点)

镜像结构

镜像 = 分层的只读文件系统(每层 = 一个 Dockerfile 指令)
Registry 存储: manifests + blobs(层)
运行时: 层叠加 + 可写层(容器层)

逆向对象:
  每层的内容(文件/二进制/配置)
  构建历史(哪层加了什么)
  基础镜像(OS/依赖)
  ENTRYPOINT/CMD(启动命令)
  ENV/ARG(环境变量,常藏密钥!)

镜像分析工具与命令

# 1. 拉取与导出
docker pull <image>
docker save <image> -o image.tar
# 或 skopeo 复制(无需 docker daemon)
skopeo copy docker://<image> dir:output

# 2. 解层查看
tar -xf image.tar
# layers/ 目录每个 tar 是一层,按序叠加
# 查看每层文件清单
tar -tf layer.tar | head -50

# 3. 深度分析(推荐工具链)
trivy image <image>              # 漏洞 + 秘密扫描
syft <image> -o spdx-json        # SBOM 生成
dive <image>                     # 逐层交互式分析(哪些层加了什么)
docker history <image>           # 构建历史(含指令泄露!)
docker inspect <image>           # 配置(Env/Entrypoint/Volumes)

镜像逆向检查清单

□ 基础镜像版本(是否过时/已知 CVE)
□ 层内文件(意外的二进制/脚本/后门)
□ 环境变量中的密钥/凭证(ENV 明文是常见泄露)
□ ENTRYPOINT/CMD(启动时执行什么)
□ 镜像层历史(有无可疑构建步骤)
□ 多阶段构建残留(构建器阶段的工具/源码)
□ 与 Dockerfile 声明不一致(漂移,第 33 章)
□ 非必要工具(curl/wget/nc 在最终镜像 = 攻击者瑞士军刀)

54.3 容器运行时逆向

运行时隔离机制(逃逸课题的知识基础)

Linux 容器隔离三件套:
  Namespaces: 隔离视图(PID/NET/MNT/UTS/IPC/USER)
  Cgroups: 限制资源(CPU/内存/设备)
  Capabilities: 裁剪权限(CAP_* 集合)

逃逸的本质: 突破 Namespace/Cgroup/Capability 的边界
  → 获得宿主机视角或宿主机执行能力

运行时分析工具

# 当前容器环境
cat /proc/1/cgroup                # 容器层级
cat /proc/self/status | grep Cap  # 当前能力
ls -la /proc/1/root               # 宿主视角试探
mount | head -30                  # 挂载点(危险挂载信号)
cat /etc/hostname                 # 主机名(容器 vs 节点)
env                              # 环境变量(凭证)

# 逃逸相关检查(蓝队/分析视角)
find / -perm -4000 2>/dev/null   # SUID 文件
ls -la /var/run/docker.sock       # Docker socket 暴露?
cat /proc/self/mountinfo | grep -E "host|proc"  # 敏感挂载

常见危险配置(逃逸的入场券)

□ Docker socket 挂载进容器 → 直接操作宿主机 Docker
□ privileged 容器 → 全部 Capability + 设备访问
□ hostPID / hostNetwork / hostIPC → 共享宿主命名空间
□ /proc 可写挂载 → 内核利用跳板
□ 服务账户 token 挂载(K8s 特有)→ 集群凭证
□ 设备直通(/dev/sd*)→ 磁盘访问

54.4 K8s 控制面逆向(集群权限游戏)

核心组件与信任路径

API Server: 一切操作的入口(认证/授权/准入)
etcd: 集群数据存储(所有秘密!)
kubelet: 节点代理(Pod 生命周期)
Controller Manager / Scheduler: 控制回路

信任链:
  客户端(kubectl/CI)→ API Server → etcd/工作负载
  ServiceAccount token → RBAC → 权限

K8s 权限枚举与利用(逆向视角)

# 1. 拿到 Pod 内的 serviceaccount token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
ls /var/run/secrets/kubernetes.io/serviceaccount/

# 2. 用 token 探测 API 权限
curl -k -H "Authorization: Bearer $(cat token)" \
     https://kubernetes.default.svc/api/v1/namespaces

# 3. 枚举 RBAC
#    whoami / auth can-i 等(利用 kubectl 或 API)
kubectl auth can-i --list --token=<token>

高价值目标(拿到权限后)

□ Secrets(etcd 里全有): 数据库密码/云凭证/证书
□ Pod 创建权限 → 创建 privileged Pod → 节点逃逸
□ ConfigMap: 配置泄露
□ 集群角色绑定: 提权到 cluster-admin
□ CronJob 篡改: 持久化(周期性执行恶意负载)
□ ServiceAccount 提权: 从低权限 SA 到高权限

典型提权链(CTF/真实攻击通用)

Pod 内代码执行
  → 读取 SA token
  → 探测 API 权限(list pods/secrets?)
  → 创建 privileged Pod(若允许)或直接读 Secrets
  → 节点逃逸(privileged + hostPID)
  → 横向(节点 → 其他节点/集群凭证)
  → 云凭证(metadata API,第 54.6 节)

54.5 容器逃逸技术族谱

逃逸技术 原理 前置条件
Docker socket 利用 挂载 socket → 起特权容器 socket 挂载
Privileged 逃逸 设备直通 + 内核利用 privileged
CVE 内核逃逸 内核漏洞(Overlayfs/cgroups 系列) 有漏洞内核
挂载逃逸 hostPID → 注入宿主进程 hostPID
进程注入逃逸 nsenter/ns 进入宿主命名空间 hostPID/权限
cgroup release_agent cgroup 写 release_agent → 宿主执行 cgroup 可写
/proc/sysrq-trigger 内核魔法键 可写 /proc/sys
K8s 层面逃逸 SA token → 提权 → 新 Pod RBAC 漏洞
核心思路: 容器内获得"能触及宿主"的能力
  → 能力来源: 权限(privileged/能力集)、挂载(socket/dev)、
             漏洞(内核 CVE)、编排(K8s 权限)

54.6 云元数据与凭证(SSRF 的延伸)

元数据服务(云厂商专属):
  AWS: http://169.254.169.254/latest/meta-data/
  Azure: 169.254.169.254/metadata/instance?api-version=...
  GCP: metadata.google.internal
  (凭证: AWS 的 /iam/security-credentials/ 等)

利用链: SSRF/容器内代码执行 → 请求元数据 → 拿临时凭证
  → 凭证操作云 API(S3 读取/创建资源/横向)

防护: IMDSv2(AWS)、托管身份(Azure)、metadata 限制
逆向要点: 云原生应用里找"谁在调元数据"→ 它就是凭证持有者

54.7 云原生逆向工作流(综合)

目标: 分析一个云原生应用/集群

1. 镜像层: 拉取 → 解层 → 枚举文件/配置/秘密(54.2)
2. 配置层: Dockerfile/K8s manifest/Helm chart 审计
   (权限/挂载/特权/网络策略)
3. 运行时: 容器内环境枚举(54.3)
4. 权限层: SA token → RBAC 探测 → 可达资源(54.4)
5. 数据层: Secrets/ConfigMap/etcd 暴露评估
6. 逃逸评估: 危险配置清单 → 逃逸可行性(54.5)
7. 凭证层: 元数据/环境变量中的云凭证(54.6)
8. 报告: 从应用到云账户的完整攻击路径

54.8 检查清单

□ 镜像层内容与秘密扫描
□ 构建历史与多阶段残留
□ 容器危险配置(privileged/socket/挂载/能力)
□ SA token 与 RBAC 权限枚举
□ Secrets/ConfigMap 暴露
□ 逃逸可行性(配置/漏洞)
□ 云元数据/凭证可达性
□ 集群横向路径(Pod→节点→集群→云)
□ 报告: 完整攻击路径图

动手练习

  1. dive 分析一个本地镜像,找出"哪些层加了什么"(含一层故意加的测试文件)。
  2. 在 kind/minikube 集群里创建一个"privileged 容器"(演练环境),枚举它能看到的宿主资源。
  3. 写一个 Pod 里的脚本:读取 SA token → 调用 API Server 枚举当前权限。
  4. 用 trivy 扫描一个镜像并分类结果:哪些漏洞只在"非可达路径"上(学 33.4 的可达性思维)。

深入阅读

  • 仓库:CTF-Sandbox-Orchestrator/competition-k8s-control-plane/(K8s 控制面 CTF)
  • 仓库:CTF-Sandbox-Orchestrator/competition-container-runtime/(容器运行时 CTF)
  • 仓库:CTF-Sandbox-Orchestrator/competition-agent-cloud/(Agent+云)
  • 仓库:CTF-Sandbox-Orchestrator/competition-cloud-metadata-path/(元数据路径)
  • 仓库:skills/supply-chain-security/SKILL.md(镜像/供应链)
  • 实战案例:skills/field-journal/seed-016_k8s-container-escape.md(K8s 容器逃逸)