sgkdev/ipv6_frag_escape

GitHub: sgkdev/ipv6_frag_escape

针对 CentOS/RHEL 10 的非特权容器逃逸 PoC,利用已修复的 IPv6 分段 slab 溢出漏洞实现从容器内非特权进程到宿主机 root shell 的完整利用链。

Stars: 51 | Forks: 14

# IPV6_FRAG_ESCAPE 一个针对 CentOS / RHEL 10 的可靠非特权容器 / 沙箱逃逸概念验证。 它利用了 `__ip6_append_data()` 中一个现已修复的 IPv6 分段漏洞(上游已通过 `38becddc` 关闭,无 CVE),这是一个位于数据包自身 head 对象尾部的 `skb_shared_info` 中的 slab 线性溢出。本 README 仅记录了漏洞利用链。 并不涵盖触发过程。 ## 范围 - 目标:CentOS / RHEL 10(内核 6.12.x,例如 `6.12.0-242.el10`)。 - 起点:位于网络隔离容器内的非特权进程,并具有访问 非特权 user namespace 的权限。 - 结果:在宿主机的初始 namespace 和 root 文件系统中获得一个交互式 root shell。 ## 注意事项 这是一个概念验证,刻意并非一键式武器。为了可靠性的脚手架 被有意省略:没有针对每 CPU 的 PCP 内存整理,没有跨缓存 SLUB 加固,并且 `skb` head 被放置在一个共享的、并非那么安静缓存中。所提供的代码触发频率 足以证明该利用链,仅此而已。它仅限于 CentOS / RHEL 10。 ## 要求 - `CONFIG_INIT_ON_ALLOC_DEFAULT_OFF`,这是 RHEL / CentOS 的默认设置。此 PoC 在未初始化的 slab 字节中植入一个过期 指针;如果设置了 `init_on_alloc=1`,该槽位会被清零,并且这种 特定技术只会导致内核崩溃(一个 NULL 解引用)。这是该 技术的限制,而不是漏洞本身的限制:一旦修复和 CVE 公开,一个涵盖这些内核的完整漏洞利用程序,将会使用不同的 技术随之而来。 - 5 级分页(LA57,57 位线性地址)。页表遍历是为五级 硬编码的,并且启动检查在缺少它时会拒绝运行。4 级 CPU 需要重构 遍历逻辑。 - 存在 `/sys/kernel/btf/vmlinux` 且对所有用户可读(RHEL / CentOS 默认提供)。 ## 已测试的内核 | 发行版 | 内核版本 | 结果 | | ----------------- | ------------------ | ---------------------- | | CentOS Stream 10 | `6.12.0-242.el10` | root,容器逃逸 | | RHEL 10 | `6.12.0-228.el10` | httpd_t 上下文逃逸 | ## 漏洞利用链 1. **Slab 内溢出转为 self-UAF。** 该漏洞允许控制 `skb_shared_info` 中的单个 `nr_frags` 字节。释放路径 (`skb_release_data()`) 会遍历 `frags[0 .. nr_frags)` 并对每个条目执行 `put_page()`,而 `frags[]` 从未被初始化。 我们通过受控的缓存复用,将一个指向 pipe buffer page 的 `struct page *` 预先植入到该 slab 槽位中, 然后将 `nr_frags` 设置为 1,因此销毁过程会释放一个我们仍然拥有的 页面的引用。线性溢出变成了一个 page use-after-free。该溢出从不触碰 `frags[0]`,因此不需要针对内存喷射进行精确计时,这也是使其具有高容错性的主要原因。 2. **Page UAF 转为 Dirty-Pagetable。** 被释放的 pipe page 通过触发一个新的匿名映射,被重新分配为最后一级页表。一个物理页面现在既是活跃的叶子 页表,也是 pipe 仍然进行读写的页面。向 pipe 写入八个字节 会安装一个伪造的 PTE;读取 pipe 则会读回该表。这是一个有限的任意 物理读写,每个表大约有 460 个 PTE 窗口。 3. **绕过 KASLR。** 借助物理读取功能,我们扫描了固定在低内存的 SMP trampoline 页表,KASLR 从未重定位它;其内核半区的条目指向 `level4_kernel_pgt`,即内核的物理基地址。从那里开始,`init_top_pgt`(可通过其自引用的条目 511 识别)成为了一个通用的虚拟到物理转换器, 恢复了内核虚拟基地址并将我们的地址空间与物理内存绑定在一起。 4. **从有限到无限读写。** 我们在叶子表中伪造了一个指向 该表自身物理地址的 PTE。匹配的虚拟窗口随后别名为页表 本身,因此 PTE 变成了普通内存:无限的、随机访问的内核读写,且循环中不需要 pipe。Ring 3 的 TLB 一致性是通过使用超大的 `mprotect()` 强制进行完整的 mm 刷新来处理的。 5. **解析偏移量并获取凭证。** 结构体成员偏移量是在运行时从 `/sys/kernel/btf/vmlinux` 读取的,而不是硬编码的。我们通过 vmemmap `struct page` 遍历和 `mm_struct.owner` 定位自身的 `task_struct`, 将 credential id 清零,并填充 `cred` 和 `real_cred` 上的所有 capability 集。符号地址来自一个内部解析器,该解析器通过我们的任意读取 解析内核自身的 `kallsyms` 表;只有在该操作失败时,我们才会回退到将 `cred.user_ns` 指向 `init_user_ns`(这样 `CAP_SYSLOG` 在初始 namespace 中仍然有效),并从 `/proc/kallsyms` 读取真实地址。 6. **在不翻转 enforcing 的情况下禁用 SELinux。** 我们使用 `xor eax, eax ; ret` 存根覆盖了 `avc_denied()` 的序言(在 IBT 下跳过 `endbr64`)。 现在,每个拒绝请求都会在整个进程范围内返回已批准,而 `getenforce` 仍然报告 `Enforcing`。这也是逃逸的先决条件:否则,核心转储处理程序的跨 域重新执行将被拒绝。 7. **通过 core_pattern 逃逸。** 我们用一个通过 `/proc//root`(崩溃任务的容器 chroot)指向 我们自己二进制文件的 `|` 前缀处理程序覆盖全局 `core_pattern`,然后使一个子进程崩溃。内核将处理程序作为 usermodehelper(初始 namespace 中的 root)运行,我们通过绑定在容器内部的 Unix socket 中继一个交互式 root shell。处理程序诞生于初始 namespace 内部,因此不需要原位 namespace 修改,也没有任何 PID namespace 带来的风险。
标签:VX技术, Web报告查看器, 内核漏洞, 安全, 安全渗透, 客户端加密, 容器逃逸, 本地提权, 超时处理