4、容器逃逸与权限提升:Docker/K8s环境下的逃逸技术、Capabilities滥用、Seccomp绕过
各位同学,咱们今天聊点硬核的——容器逃逸。说实话,我在做NFV安全评估那几年,见过太多人觉得“容器嘛,就是个轻量级进程,能有多大事?”嗯,这种想法很危险。你想想看,一旦攻击者从容器里跳出来,宿主机就裸奔了。在K8s环境里,甚至可能直接控制整个集群。
这一章,我带你走一遍攻击者的视角。咱们看看Docker和K8s环境下,容器逃逸到底怎么玩,Capabilities怎么被滥用,Seccomp又怎么被绕过。说白了,就是搞清楚“墙”是怎么被凿穿的。
核心观点:容器不是虚拟机。它共享宿主机内核。所以“逃逸”的本质,就是突破内核层面的隔离机制。Capabilities、Seccomp、Namespace,这些是墙,也是攻击面。
4.1 容器逃逸的三大路径
我个人习惯把逃逸手法分成三类。这样好记,也方便做防御。
- 内核漏洞逃逸:利用宿主机内核的CVE。比如Dirty Pipe (CVE-2022-0847),我记得当时很多没及时打补丁的集群都中招了。
- 配置不当逃逸:这是最常见的。比如挂载了宿主机目录,或者给了特权模式。
- 组件漏洞逃逸:比如runc、containerd这些容器运行时本身的漏洞。CVE-2019-5736就是个经典案例。
咱们重点讲第二种和第三种。因为第一种靠打补丁,后两种靠的是安全配置和意识。
4.2 Capabilities滥用:你给多了
Linux Capabilities,说白了就是把root权限拆成小块。Docker默认给容器一些Capabilities,比如CHOWN、DAC_OVERRIDE。但很多运维图省事,直接给--privileged,或者给了SYS_ADMIN、NET_ADMIN这种高危权限。
为什么会这样?因为有些应用说“我要挂载FUSE文件系统”,或者“我要改网络配置”。嗯,给了就出事了。
警告:永远不要在生产环境给容器--privileged。这等于告诉攻击者:“欢迎来我家做客,钥匙在门口垫子下面。”
我给大家看一个典型的滥用场景。攻击者拿到一个容器shell,检查Capabilities:
# 在容器内执行
capsh --print
# 输出示例:Current: = cap_chown,cap_dac_override,cap_sys_admin,cap_net_admin+ep
看到cap_sys_admin了吗?有这个,攻击者就可以尝试挂载cgroup,然后利用cgroup notify_on_release特性逃逸。我在项目中遇到过,攻击者就靠这一个Capability,直接拿到了宿主机root。
攻击流程大致是这样:
- 在容器内创建一个cgroup子系统。
- 设置
notify_on_release=1。 - 把release_agent指向一个恶意脚本。
- 触发cgroup释放,恶意脚本在宿主机上以root执行。
代码示例(简化版):
# 在容器内执行
mkdir /tmp/cgrp
mount -t cgroup -o memory cgroup /tmp/cgrp
mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
echo "/tmp/exploit.sh" > /tmp/cgrp/release_agent
# 触发释放
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
你看,就这几行命令。所以我说,Capabilities管理是容器安全的第一道防线。
避坑指南:我曾经在客户现场看到,他们给容器加了SYS_PTRACE权限,说是为了调试。结果攻击者用ptrace直接注入进程,绕过了所有应用层防护。记住,生产环境只给最小权限。用--cap-drop=ALL再按需添加。
4.3 Seccomp绕过:规则总有漏洞
Seccomp是Linux内核的安全机制,用来限制进程能调用的系统调用。Docker默认有一个Seccomp配置文件,会封禁掉大约44个危险系统调用,比如mount、ptrace、kexec_load这些。
但问题来了。Seccomp规则是静态的。攻击者可以绕过。怎么绕?
- 利用未封禁的系统调用:比如
ioctl。如果Seccomp没封ioctl,攻击者可以通过它操作设备文件。 - 利用内核漏洞:有些漏洞不需要被封禁的系统调用。比如利用
userfaultfd(如果没封的话)做竞态条件攻击。 - Seccomp配置错误:比如管理员自己写了一个白名单,但漏掉了某个关键调用。
我记得有一次,一个客户自己定制了Seccomp策略,只允许read、write、open等基础调用。听起来很安全对吧?但他们忘了封clone和unshare。攻击者利用unshare创建新的Namespace,然后在新Namespace里重新挂载文件系统,成功逃逸。
关键点:Seccomp不是银弹。它只能限制系统调用,但限制不了逻辑漏洞和配置错误。而且,如果攻击者已经拿到了容器内的root,他们可以尝试修改Seccomp策略本身——前提是Seccomp的SECCOMP_SET_MODE_FILTER没有被禁用。
4.4 综合攻击链:从容器到宿主机
咱们把上面这些串起来,看一个完整的攻击链。假设攻击者已经通过Web漏洞进入了容器。
- 信息收集:检查
/proc/1/cgroup,看是不是在容器里。检查Capabilities和Seccomp状态。 - 利用Capabilities:如果发现
SYS_ADMIN,尝试cgroup逃逸。如果没有,看看有没有挂载/var/run/docker.sock。 - 利用挂载点:如果宿主机
/被挂载到了容器里,直接chroot进去,写SSH公钥。 - 绕过Seccomp:如果Seccomp限制了
mount,但没限制open_by_handle_at,可以用这个系统调用访问宿主机文件系统。
下面这张图,我画了攻击者视角的决策流程。你一看就明白。
4.5 防御建议:别等出事了再补
讲完了攻击,咱们聊聊怎么防。我总结了几条铁律:
| 攻击手法 | 防御措施 | 我踩过的坑 |
|---|---|---|
| Capabilities滥用 | 使用--cap-drop=ALL,按需添加。定期审计容器权限。 |
曾经有个团队给容器加了SYS_RAWIO,说是要访问硬件。结果攻击者直接读写磁盘。 |
| Seccomp绕过 | 使用Docker默认Seccomp策略。不要轻易自定义。如果必须自定义,用seccomp-tools做压力测试。 |
我自己写过一个Seccomp策略,漏掉了socketcall。攻击者利用它创建了原始套接字。 |
| 挂载逃逸 | 永远不要挂载/var/run/docker.sock到容器内。宿主机目录挂载要谨慎,使用只读模式。 |
见过最离谱的,把/挂载进去了,还给了读写权限。这等于把家门钥匙给了陌生人。 |
| 内核漏洞 | 及时打补丁。使用GKE、EKS等托管服务,它们会自动更新节点。 | Dirty Pipe漏洞爆发时,很多客户没及时更新,我们熬了好几个通宵帮他们应急。 |
个人建议:在K8s环境里,用Pod Security Admission (PSA) 或者 OPA Gatekeeper 来强制安全策略。别靠人肉检查,自动化才是王道。另外,运行时安全工具比如Falco、Tracee,能帮你实时检测异常系统调用。
好了,这一章的内容就到这。记住一句话:容器安全不是配出来的,是设计出来的。从镜像构建到运行时,每一步都要考虑攻击面。你想想看,如果攻击者已经进了容器,你还有多少时间反应?
公众号:蓝海资料掘金营,微信deep3321