仅用数据实现Google kCTF容器逃逸的技术解析

本文详细分析了如何利用io_uring中的UAF漏洞,通过纯数据攻击方式实现Google kCTF容器逃逸。文章涵盖了漏洞原理、跨缓存技术、任意读写原语构建以及最终权限提升的全过程,展示了无需控制流劫持的高级利用技术。

仅用数据实现Google kCTF容器逃逸的技术解析

引言

自去年秋季以来,我一直在断断续续地进行Linux内核漏洞利用开发/研究和漏洞研究。几个月前,我在度假期间有空闲时间坐下来挑战自己,为kCTF中实际被利用的一个漏洞编写我的第一个纯数据型漏洞利用。io_ring在该项目历史上一直是一个热门目标,因此我决定在那里找一个易于理解的、已被利用的漏洞作为漏洞利用开发创意的沃土。

我选择使用的漏洞是一个导致struct file UAF的漏洞,其中可以持有对已释放对象的打开文件描述符。关于文件UAF漏洞利用的详细分析已经有很多,因此我决定作为一个挑战,我的漏洞利用必须是纯数据型的。这个自我设定的挑战参数完全是任意的,但我只是想尝试编写一个不依赖劫持控制流的漏洞利用。

漏洞分析

该漏洞极其简单(为什么我找不到这样的漏洞?),并在去年11月在kCTF中被利用。我没有很努力地查找或在kCTF discord中询问,但未能找到这个特定漏洞利用的PoC。我能够找到几个利用类似漏洞的优秀分析文章,特别是pqlpql和Awarau的这篇:https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/。

我不会过多讨论漏洞细节,因为它对于发挥创意和编写新型漏洞利用(对我来说是新的)的练习并不重要;然而,从补丁中可以看出的,有一个对文件引用进行put(减少)的调用,而没有首先检查该文件是否是io_uring中的固定文件。

技术实现

跨缓存技术

我选择使用管道缓冲区(pipe buffer)的形式重新分配页面,而不是使用将整个受害者文件对象页面送回页面分配器,然后让该页面用作通用缓存对象的后备存储的技巧。这是一个极其强大的技术,因为我们控制管道缓冲区的所有内容(通过写入),并且可以读取100%的页面内容(通过读取)。

任意读取原语

我首先寻找的是数据泄漏的方法,因为我一直认为所有Linux内核漏洞利用都遵循相同的模式:实现泄漏以击败KASLR,在内存中找到一些有价值的对象,覆盖函数指针等等。

我发现的任意读取gadget在fs/fcntl.c中的fcntl系统调用中。在F_GET_RW_HINT情况下,一个u64(“h”)被复制回用户空间。该值来自inode->i_write_hint的值,而inode本身由file_inode(file)返回。

由于我们控制文件,因此我们也控制inode。通过设置管道缓冲区中的fake file结构,我们可以将inode成员设置为任意地址,从而实现任意字节读取。

写入原语构建

在io_msg_ring中,可以看到target_ctx(类型为io_ring_ctx)源自req->file->private_data。由于我们控制fake file,我们可以控制private_data内容(在这种情况下是指向fake io_ring_ctx的指针)。

在io_commit_cqring中,我们基本上执行了一个memcpy操作:将ctx->cached_cq_tail(100%用户控制)的内容写入&ctx->ring->cq.tail(100%用户控制)。这种情况下写入的大小是4字节,因此我们实现了一个任意的4字节写入。

利用计划

在kCTF环境中,您作为非特权用户在容器内运行,目标是逃逸容器并从主机文件系统读取标志值。

我计划找到主机Init task_struct在内存中的位置,并找到几个重要成员的值:real_cred、cred和nsproxy。通过任意读取能力,我们可以复制这些值,因为它们位于已知地址(从init任务符号的偏移量)。

实现细节

对象伪造

我利用task_struct具有系统上任务链表的事实。在漏洞利用的早期,我生成一个具有已知名称的子进程,该名称适合task_struct comm字段。当我遍历系统上的任务链表时,只需检查每个任务的comm字段以查找易于识别的子进程。

我还遍历了自己任务的文件描述符表,了解了管道缓冲区的地址。这是因为管道缓冲区页面显然是页面对齐的,因此我可以将文件描述符表中读取的地址页面对齐作为UAF文件的地址。

写入操作

现在可以使用相同的步骤覆盖三个成员real_cred、cred和nsproxy,现在我们的子进程具有与init相同的所有权限和能力,包括对主机根文件系统的可见性。

结论

这对我来说非常有趣,我学到了很多。我认为这类学习挑战很棒且风险低。它们也可以与朋友一起合作完成。完整的漏洞利用代码已发布在下方,如果您对其任何部分有理解困难,请告诉我。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 编译命令
// gcc sploit.c -o sploit -l:liburing.a -static -Wall

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/msg.h>
#include <sys/timerfd.h>
#include <sys/mman.h>
#include <sys/prctl.h>

#include "liburing.h"

// 完整漏洞利用代码...
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计