容器逃逸内核漏洞的原理与危害详解
扫描二维码
随时随地手机看文章
容器技术已经成为云计算时代的基础架构,从微服务部署到Serverless,几乎所有云服务都在使用Docker、Kubernetes管理容器。容器的核心能力之一就是隔离性:通过cgroup和namespace,让容器里的进程以为自己运行在独立的系统里,无法访问宿主和其他容器的资源。但这种隔离不是绝对的,一旦内核出现漏洞,攻击者就能突破容器的隔离限制,直接拿到宿主机的最高权限——这就是容器逃逸。近年来曝出的很多容器逃逸漏洞,几乎都和内核漏洞有关,很多开发者只知道“容器逃逸会拿宿主机权限”,却不清楚漏洞到底是怎么利用的,为什么内核出问题会导致容器逃逸?我们就从原理到实例,拆解内核漏洞引发容器逃逸的完整逻辑。
先理清基础:容器隔离的本质,为什么能被内核漏洞突破
容器不是完全虚拟化,它和宿主机共享同一个内核,这是容器轻量高效的原因,也是容器逃逸风险的根源:
容器通过namespace隔离进程、网络、文件系统,让容器内进程只能看到自己namespace内的资源,但这种隔离是逻辑隔离,依赖内核代码实现;
所有容器里的系统调用,最终都要交给同一个宿主机内核处理,如果内核存在漏洞,容器内的进程就能通过漏洞越权,直接操作宿主机的内核资源,突破namespace的隔离;
权限上来说,容器里的root用户,默认和宿主机的root用户是同一个UID,只是被namespace限制了权限,如果突破了内核的隔离限制,容器内root直接就能变成宿主机root,拿到全部权限。
简单来说:容器是“穿上了内核给的隔离外套”,如果内核有洞,外套就能被撕开,容器里的攻击者直接就能摸到宿主机的核心资源,这就是内核漏洞导致容器逃逸的核心逻辑。
容器逃逸内核漏洞的常见类型:漏洞怎么变成逃逸工具
内核漏洞千千万,不是所有内核漏洞都能用来容器逃逸,能用来逃逸的漏洞,一般都满足两个条件:第一,容器内进程可以触发;第二,漏洞能让攻击者获得越权读写内存或者执行代码的能力,常见的有这么几类:
第一类:权限绕过类漏洞,直接绕过隔离检查
这类漏洞非常直接,内核在做权限检查的时候逻辑有缺陷,容器内进程不需要提权,就能直接访问宿主机的资源。最典型的例子就是CVE-2019-5736,这个runc漏洞本质就是利用了procfs的权限绕过,容器内进程可以覆写宿主机的runc二进制,下次启动容器的时候就能拿到宿主机权限,虽然这个漏洞算容器运行时漏洞,但核心也是内核procfs的访问逻辑缺陷。
内核本身的权限绕过更常见,比如有些漏洞允许容器内进程打开/dev/kmem或者/dev/mem这些内核内存设备,这些设备默认能直接读写所有物理内存,容器内拿到dev的访问权限,直接就能修改内核内存,随便植入代码,拿到宿主机权限。
还有一类是namespace检查漏洞:内核在处理某些系统调用的时候,没有正确检查当前进程所在的namespace,容器内进程就能直接操作宿主机的namespace,比如挂载宿主机的根文件系统到容器内,直接就能读写所有宿主机文件,完成逃逸。
第二类:越权读写漏洞,篡改内核数据提权
这是最常见的一类容器逃逸漏洞:内核存在缓冲区溢出、越界访问之类的内存漏洞,容器内进程可以触发漏洞,实现对内核任意地址的读写,然后篡改内核的权限数据,把容器内进程的权限改成宿主机root权限,完成逃逸。
最典型的例子就是CVE-2021-33909,这是一个Linux文件系统的漏洞,攻击者在容器内构造特殊的文件路径,就能触发越界读写,然后篡改内核内存,获得宿主机的root权限。这个漏洞影响范围非常广,几乎所有主流Linux发行版都受影响,只要内核版本没升级,容器就能直接逃逸,不需要额外配置。
这类漏洞的利用逻辑也很清晰:
容器内构造特殊输入,触发内核的越界读写漏洞;
通过漏洞找到内核中存储进程权限的cred结构体;
把cred结构体中root用户的UID、GID改成0;
返回用户态之后,容器内进程就变成了宿主机的root用户,直接就能chroot到宿主机根目录,拿到完整控制权,完成逃逸。
整个过程只需要在容器内执行一段恶意代码,不需要额外依赖,非常容易利用,危害极大。
第三类:Use-After-Free漏洞,控制内核执行流
Use-After-Free(释放后使用)是内核中非常常见的内存漏洞,当内核释放了一块内存之后,没有把指针清空,后续还继续使用这个指针,攻击者就能通过内存布局,控制这块内存的内容,进而控制内核的执行流,执行任意代码,完成逃逸。
这类漏洞是容器逃逸中危害很高的一类,比如CVE-2017-1000112,就是Linux内核netfilter模块的UAF漏洞,容器内攻击者可以利用这个漏洞,在内核中执行任意代码,拿到宿主机root权限。这个漏洞当年影响了很多Docker容器,很多没升级内核的服务器直接被打穿。
UAF漏洞的利用逻辑比越界读写更灵活:攻击者可以在释放内存之后,申请一块相同大小的内存,把自己构造的伪造数据填进去,当内核调用函数指针的时候,就会跳到攻击者指定的地址,执行shellcode,然后直接修改进程权限,完成逃逸。
第四类:逻辑漏洞,不当暴露宿主机资源
这类漏洞不是内存破坏,而是内核的逻辑设计有问题,不小心把宿主机的资源暴露给了容器,攻击者直接就能利用暴露的资源提权。比如之前曝出的cgroups v1的漏洞CVE-2022-0492,容器内进程可以通过cgroups文件系统的不当权限,直接触发内核的写操作,修改宿主机的权限,不需要内存破坏就能逃逸。
还有一类漏洞是,当容器挂载某些特殊文件系统的时候,内核没有正确限制访问权限,容器内就能修改宿主机的内核参数,甚至插入内核模块,直接拿到权限。这类漏洞利用方式非常简单,有时候只需要几条命令就能完成逃逸,门槛很低。
完整利用流程:从容器内到拿到宿主机root,一步步怎么走
我们拿一个典型的越界读写内核漏洞举例,看看攻击者完成一次容器逃逸,完整的流程是什么样的:
第一步:信息收集,确认漏洞存在
攻击者先拿到一个容器的低权限或者root权限,然后第一步就是确认当前宿主机内核版本有没有存在未打补丁的漏洞:
用uname -r拿到内核版本,去漏洞库查这个版本有没有公开的可逃逸内核漏洞;
检查容器的配置:有没有开启特权模式?有没有限制内核能力?如果没开特权,很多漏洞利用会被阻挡,但如果内核漏洞足够严重,还是能绕过限制。
如果找到对应漏洞,就准备对应的利用代码,进入下一步。
第二步:触发漏洞,获得越权读写能力
攻击者在容器内运行利用代码,构造恶意输入触发内核漏洞:
如果是越界读写漏洞,就构造特殊的输入,让内核读出超出边界的数据,泄露内核内存的地址,绕过内核的地址随机化(KASLR)保护;
如果是UAF漏洞,就通过多次内存分配释放,布局好内存,控制目标内存块的内容;
完成之后,攻击者获得了内核任意地址读写的能力,接下来就可以修改内核数据了。
第三步:篡改内核权限数据,提升为宿主机root
拿到任意读写能力之后,攻击者会找到当前进程的cred结构体,这个结构体存了当前进程的所有权限信息,包括UID、GID、权限掩码这些:
把结构体中所有UID、GID都改成0,也就是root用户的ID;
如果需要,修改selinux的安全标签,绕过selinux的权限检查;
有些利用会直接修改内核的函数指针,让内核允许后续所有操作,或者直接插入一个root shell,等待连接。
第四步:突破文件系统隔离,拿到宿主机控制权
权限改完之后,攻击者已经是宿主机的root了,但还在容器的mount namespace里,只能看到容器内的文件系统,接下来只需要简单几步,就能拿到宿主机的文件系统访问权:
通过读写内核内存,找到宿主机根文件系统的vfsmount结构体;
把当前进程的根目录切换成宿主机的根目录,或者直接把宿主机根目录挂载到容器内某个目录;
之后就能直接读写宿主机的所有文件,比如写ssh公钥到root目录,或者创建反向shell,连接到攻击者的服务器,拿到完整的宿主机控制权,逃逸就完成了。
整个过程从触发漏洞到拿到shell,通常只需要几秒钟,自动化程度非常高,很多公开的exp直接就能运行,对未升级内核的容器集群威胁非常大。
内核漏洞容器逃逸的危害:为什么云厂商这么重视
很多人会问:不就是一个容器拿到宿主机权限吗,有什么大不了的?实际上,容器逃逸的危害远不止拿到一台服务器权限:
集群级别的沦陷:在Kubernetes集群中,如果一个容器逃逸拿到了宿主机权限,攻击者就能访问宿主机上的kubelet证书,进而控制整个Kubernetes集群,拿到所有容器的权限,整个集群都会被攻陷;
数据泄露风险:宿主机上可能存了多个用户的容器数据,攻击者拿到宿主机权限,就能直接读取所有容器的敏感数据,比如数据库密码、业务密钥、用户隐私数据,造成大规模数据泄露;
挖矿和僵尸网络:攻击者拿到宿主机权限之后,通常会植入挖矿木马,或者把服务器变成僵尸网络的节点,占用大量资源,给企业造成直接的经济损失;
横向渗透跳板:攻陷宿主机之后,攻击者可以以此为跳板,对内网进行横向渗透,拿下企业内部更多服务器,进一步扩大危害。
最典型的例子就是早年的Dirty Cow漏洞(CVE-2016-5195),这是一个内核页表的漏洞,容器内低权限用户就能利用它提权,完成容器逃逸,这个漏洞影响了几乎所有Linux内核,当年很多云厂商都紧急要求用户升级内核,就是因为危害太大。
怎么防御内核漏洞导致的容器逃逸?
内核漏洞导致的容器逃逸,防御思路其实很清晰,核心就是从“减少漏洞暴露”“限制漏洞利用”“及时修复漏洞”三个层面入手:
第一,及时升级内核,修复已知漏洞
这是最根本的防御方式:绝大多数能用来容器逃逸的内核漏洞,官方都会很快放出补丁,只要及时升级内核到最新的稳定版本,就能堵住已知的漏洞,避免被攻击者利用。很多企业喜欢用容器就不用管宿主机,常年不升级内核,这就给攻击者留下了可乘之机。
第二,减少容器权限,降低漏洞利用成功概率
即使内核有漏洞,也可以通过限制容器权限,提高漏洞利用的门槛:
不要让容器以root用户运行,用普通用户运行容器,即使漏洞利用成功,权限也会更低;
不要开启特权模式,除非业务必须需要,特权模式会开放几乎所有内核能力,大大降低漏洞利用的门槛;
限制容器的内核能力,去掉不必要的capabilities,比如CAP_SYS_ADMIN这类敏感能力,很多内核漏洞利用都需要这个能力,去掉之后就能挡住很多攻击;
开启内核安全模块,比如SELinux、AppArmor,给容器配置严格的安全策略,即使漏洞触发,也能限制攻击者的后续操作,避免拿到完整权限。
第三,开启内核防护机制,增加漏洞利用难度
现在Linux内核有很多针对内核漏洞利用的防护机制,一定要开启:
开启KASLR(内核地址空间随机化),让攻击者很难拿到内核地址,增加漏洞利用的难度;
开启SMAP、SMEP(用户态/内核态地址访问控制),阻止内核直接执行用户态的代码,增加代码执行的难度;
开启堆栈保护,比如栈溢出保护,挡住很多基础的内存破坏漏洞利用。
第四,隔离租户,减少横向渗透风险
在多租户的容器集群中,一定要做好租户之间的隔离:不要把不同租户的容器调度到同一台宿主机上,即使一个租户的容器逃逸,也不会影响其他租户;做好网络隔离,限制宿主机访问内网其他服务,即使逃逸也没法横向渗透。
结语
容器技术带来了轻量高效的部署体验,但是共享内核的架构,也让内核漏洞成为容器安全的最大威胁之一。从最早的Dirty Cow,到后来的CVE-2021-33909,几乎每年都会曝出能用来容器逃逸的内核漏洞,这类漏洞的本质就是共享内核带来的风险:容器依赖内核实现隔离,内核出了问题,隔离自然就被突破。
对开发者和运维来说,理解内核漏洞容器逃逸的原理,不是为了去攻击别人,而是为了更好地防御:知道攻击者怎么利用漏洞,就能针对性地做好防护,堵住安全漏洞。容器安全不是一劳永逸的事情,及时升级内核、最小化权限、开启安全防护,才能把逃逸的风险降到最低,让容器集群稳定安全地运行。





