一个针对CentOS / RHEL 10的可靠的未特权容器/监狱逃逸概念验证。
它利用了Linux内核中一个现已修复的IPv6分段漏洞,位于__ip6_append_data()函数中(上游通过 38becddc 修复,无CVE),这是一个 slab 内的线性溢出,溢出到数据包自身头部对象尾部的 skb_shared_info 结构中。本README仅记录利用链,不涵盖触发条件。
6.12.0-242.el10)。这是一个概念验证,故意不是即开即用的武器。可靠性支撑框架被有意省略:没有每CPU的PCP整理,没有跨缓存SLUB加固,且skb头部被放置在一个共享但并非安静的缓存中。其所提供的功能经常触发以证明利用链的可行性,但仅此而已。仅限于CentOS / RHEL 10。
CONFIG_INIT_ON_ALLOC_DEFAULT_ON 关闭(RHEL / CentOS 默认设置)。此PoC在未初始化的slab字节中放置一个过期指针;若启用 init_on_alloc=1,该槽位会被清零,从而导致该特定技术仅会使内核崩溃(NULL解引用)。这是该技术的局限性,而非漏洞本身的局限:一个完整的、也能覆盖那些内核的、采用不同技术的漏洞利用将在修复和CVE公开后发布。/sys/kernel/btf/vmlinux 存在且全局可读(RHEL / CentOS 默认情况)。| 发行版 | 内核 | 结果 |
|---|---|---|
| CentOS Stream 10 | 6.12.0-242.el10 | root权限,容器逃逸 |
| RHEL 10 | 6.12.0-228.el10 | httpd_t上下文逃逸 |
slab内溢出实现自UAF。 该漏洞允许控制 skb_shared_info 中单个 nr_frags 字节。释放路径(skb_release_data())会遍历 frags[0 .. nr_frags) 并对每个条目执行 put_page(),而 frags[] 从未被初始化。我们通过受控的缓存复用,预先将一个管道缓冲区的 struct page * 指针植入该slab槽位,然后将 nr_frags 设置为1,这样销毁过程会对一个我们仍然持有的页面减少引用计数。线性溢出的结果变成了页面释放后重用。溢出从不触及 frags[0],因此无需针对内存喷洒进行计时,这也是该技术宽容性的主要来源。
页面UAF实现脏页表(Dirty-Pagetable)。 释放的管道页面通过在一个新的匿名映射上触发缺页异常,被回收用作最后一级页表。于是,一个物理页面同时是活跃的叶子页表,并且管道仍然可以读写它。向管道写入八个字节可以安装伪造的PTE;从管道读取则可以读回该页表。这是一个有限的任意物理读写能力,每个页表大约有460个PTE窗口。
打败KASLR。 利用物理读取能力,我们扫描固定的低内存SMP跳板页表,该表KASLR从未重定位;其内核半部分条目指向 level4_kernel_pgt,即内核的物理基址。从那里,init_top_pgt(可通过其自引用条目511识别)是一个通用的虚拟到物理地址转换器,能够还原内核虚拟基址,并将我们的地址空间与物理内存关联起来。
从有限读写到无限读写。 我们在叶子页表中伪造一个PTE,使其指向该页表自身的物理地址。于是,相匹配的虚拟窗口就成为页表本身的别名,使得PTE变成普通的内存:无限制、随机访问的内核读写,且不再需要管道参与。Ring 3的TLB一致性通过使用一个超大的强制进行完整的mm刷新来处理。
mprotect()解析偏移量并获取凭据。 结构体成员偏移量在运行时从 /sys/kernel/btf/vmlinux 读取,而非硬编码。我们通过 vmemmap 中 struct page 的遍历以及 mm_struct.owner 来定位我们自己的 task_struct,清空凭证ID,并填充 cred 和 real_cred 的所有能力集。符号地址来自内部解析器,该解析器通过我们的任意读取来解析内核自身的 kallsyms 表;只有在此失败时,我们才回退到将 cred.user_ns 指向 init_user_ns(这样在初始命名空间中就拥有 CAP_SYSLOG 权限),并从 /proc/kallsyms 读取真实地址。
在不翻转强制模式的情况下禁用SELinux。 我们将 avc_denied() 的前导代码覆盖为 xor eax, eax ; ret 桩代码(在IBT下跳过 endbr64)。从此每个拒绝都返回允许(granted),进程范围内有效,而 getenforce 仍然报告 Enforcing。这也是逃逸的先决条件:核心转储处理器的跨域重新执行否则将被拒绝。
通过core_pattern逃逸。 我们将全局的 core_pattern 覆盖为一个以 | 为前缀的处理程序,通过 /proc/<PID>/root(崩溃任务的容器chroot)指向我们自己的二进制文件,然后使一个子进程崩溃。内核将该处理程序作为用户态辅助程序运行,在初始命名空间中以 root 身份执行,我们通过绑定在容器内的Unix套接字传递一个交互式root shell。处理程序起始于初始命名空间,因此无需进行原地命名空间手术,也无需处理PID命名空间的任何风险。