Docker 逃逸中被忽略的 pid namespace
一、背景 最近在研究基于内核漏洞进行Docker逃逸的原理,发现漏洞利用中都会使用如下三行代码,用于切换Docker中exp进程的三种namespace: setns(open("/proc/1/ns/mnt",O_RDONLY),0); setns(open("/proc/1/ns/pid",O_RDONLY),0); setns(open("/proc/1/ns/net",O_RDONLY),0); 然而实际测试中,发现exp进程的 pid namespace 并未切换成功,具体表现为: 通过 echo $$ 获得的进程号跟执行exp前没有变化 通过 kill -9 无法终止任何host进程 通过 ls -al /proc/<exp-host-pid>/ns 查看pid项的值也没有发生变化。 是什么原因导致只有 pid namespace 切换失败?以及如何完成 pid namespace 的逃逸呢?这篇文章中记录了我对这些问题的理解。 二、Docker逃逸历史漏洞及分类 目前公开的Docker逃逸漏洞可以分为三种类型:Docker配置的问题,Docker实现的问题,和Linux内核的问题。 Docker配置的问题:主要是由于用户使用Docker时不规范,指定了不安全的启动参数(–privileged),给了不必要的启动权限(SYS_MODULE、SYS_PTRACE、SYS_ADMIN),或者挂载了特殊文件(/var/run/docker.sock),基于此可以轻易实现Docker逃逸。 Docker实现的问题:Docker架构中各个组件中可能出现一些漏洞,如: runc中的漏洞:CVE-2019-5736、CVE-2024-21626等; Docker cp/Docker build的漏洞:CVE-2019-14271、CVE-2019-13139等; containerd的漏洞:CVE-2020-15257等。 Linux内核的问题:Docker跟host共享同一个系统内核,因此内核中的漏洞也可能被用于容器逃逸。收集了一些用于容器逃逸的漏洞(不一定能用于Docker逃逸,或者即使能用于Docker逃逸也需要满足一些前提条件),如下: 通过传统内核漏洞ROP完成逃逸的有:CVE-2017-7308、CVE-2017-1000112、CVE-2020-14386、CVE-2021-22555、CVE-2022-0185; 通过容器机制漏洞完成逃逸的有:CVE-2018-18955(namespace)、CVE-2022-0492(cgroups); 通过文件读写类漏洞完成逃逸的有:CVE-2016-5195(DirtyCow)、CVE-2022-0847(DirtyPipe)。 本文基于传统内核漏洞已实现控制流劫持的场景下(通过植入内核ko实现),研究Docker逃逸过程及利用方法,从而加深对Linux内核中容器安全相关机制的理解。 三、Docker依赖的内核安全机制 Docker的本质是一个linux用户态进程,它呈现出来的隔离状态依赖于linux内核这个底座提供的几种安全机制 —— capability,namespace,seccomp,apparmor/selinux,cgroups。 capability:将普通用户和特权用户进一步区分,实现更细粒度的访问控制; namespace:资源隔离,使同一namespace中的进程看到相同的系统资源,并且对其他namespace不可见。目前共有8种namespace; seccomp:禁止进程调用某些系统调用; apparmor/selinux:强制访问控制; cgroups:资源限制,限制进程对计算机资源的使用(如CPU、memory、disk I/O、network等)。 3.1 查看状态 如何查看当前环境中这些安全机制的状态呢? 在Docker中起一个bash,host上找到它对应的pid号(8089),然后我们可以在系统命令行中观察这些安全机制作用到每个进程的状态。 capability 查看进程具备哪些capability: ➜ ~ cat /proc/8089/status | grep Cap CapInh: 0000000000000000 CapPrm: 00000000a80425fb CapEff: 00000000a80425fb CapBnd: 00000000a80425fb CapAmb: 0000000000000000 # 通过capsh解析cap ➜ ~ capsh --decode=00000000a80425fb WARNING: libcap needs an update (cap=40 should have a name)....