CVE-2022-37706

大家好,这次我要讲的是我在 Linux 的一个主流窗口管理器 Enlightenment (https://www.enlightenment.org/) 中发现的一个最新 0-day 漏洞。
这个 0-day 可以非常轻松、即时地将任何用户提升至 root 权限。
该利用程序已在 Ubuntu 22.04 上测试,但应该在任何发行版上都能正常工作。
首先,Enlightenment 是一个窗口管理器、合成器和极简桌面环境,
适用于 Linux(主要平台)、BSD 以及任何其他兼容的 UNIX 系统。
我安装了这个窗口管理器,想稍微试验一下。它对我来说很有趣,
因为它包含了很多工具,而且说实话,它看起来相当精美。
在使用 apt install enlightenment 安装软件包后,我检查了系统上安装的文件和目录,
发现了很多模块和辅助二进制文件,
但最有趣的是:
➜ enlightenment cd /usr/lib/x86_64-linux-gnu/enlightenment/
➜ enlightenment find . -perm -4000
./utils/enlightenment_ckpasswd
./utils/enlightenment_system
./utils/enlightenment_sys
它安装了一些 SUID 二进制文件,然后我在想,能否利用其中一个来提升到 root 权限。
这些二进制文件看起来都很安全,代码编写得也很好。
我们要讨论的二进制文件是 enlightenment_sys。
与其他任何目标一样,我们在进行一些预评估后选择要应用的策略,
如果还没看过,可以看看我的博客(https://pwn-maher.blogspot.com/2020/10/vulnerability-assessment.html)
我采用自上而下的方法对代码进行了审计。
因为这个窗口管理器是开源的,所有这些二进制文件和模块的源代码都是可用的。
所以我的第一件事就是运行 apt source enlightenment 获取所有源代码,
再稍加挖掘,就能找到目标二进制文件的代码。
但为了调试这个二进制文件,我把它加载到 Ghidra 中进行分析,以便获得地址来设置断点等等。
第一次尝试时没有找到符号,但没关系,因为事实证明这是一个相对较小的二进制文件,并不需要符号。
令人惊讶的是,我发现看 Ghidra 的反编译伪代码比直接看源码更令人愉悦
(避免了宏定义,也避免了那些针对用于编译特定代码块的操作系统所做的检查)。
那么让我们开始分析吧。
1- 与二进制文件互动。
让我们运行这个文件,看看有关目标的一些信息:
Screenshot
运行该二进制文件不会产生任何输出:
Screenshot
传入 --help 参数后得到如下输出:
Screenshot
抱歉,我会利用它来获取 root 权限。
接下来,我们就用 strace 看看它是否会使用任何可疑的系统调用,比如
execve 或 openat:
strace ./enlightenment_sys 2>&1 | grep open
Screenshot
它只是在我们无权篡改的位置打开了一些已知的库。
strace ./enlightenment_sys 2>&1 | grep exec
Screenshot
2- 让我们对二进制文件进行逆向工程,然后利用它。
我创建了一个新的 Ghidra 项目,并加载了这个特定的二进制文件。
由于没有找到符号,我们可以通过 entry 找到 main 函数。
entry 函数的第一个参数就是 main 本身。
我把它重命名为 main,以便后续引用。
向下滚动一点,我就能看到 system() 函数正在被使用。
作为 pwner,我花了数天时间在各种挑战中调用这个特定的函数 x)
我对这个二进制文件进行了逆向,寻找内存破坏漏洞或堆问题,
但实际上这是一个奇怪的命令注入。
该二进制文件在运行 system 之前采取了所有安全预防措施,但遗憾的是,
我们总能把我们的输入注入其中。
Screenshot
好的,现在让我们从顶部开始逐步分析这个二进制文件,直到我们的 system 函数,
尝试将我们的输入注入其中。
首先,二进制文件只是检查第一个参数是否为 --help 或 -h,并显示
我们之前看到的那条消息。
Screenshot
其次,它会将自身权限提升到 root。
Screenshot
接下来,它会清除几乎所有环境变量(安全预防措施),以避免
调用其他非预期的二进制文件。
Screenshot
因此,如果我们输入的第一个参数是 "mount",它就会进入这个分支,检查给定的某些标志,
这些标志将被设置在堆栈上。
接下来,它检查 mount 之后的下一个参数是否为 UUID=,我们不想进入
这里,所以我们提供的是 "/dev/../tmp/;/tmp/exploit"。
Screenshot
这样我们就通过了第 410 行的检查,也就是 strncmp 检查。
因为如果它不是以 /dev/ 开头,二进制文件就会退出。
接下来会对我们提供的文件调用 stat64,注意我们可以
创建一个名为 ";" 的文件夹,这将导致命令注入。
到目前为止,exploit 已经创建了这个文件 /dev/../tmp/;/tmp/exploit,
但这并不是将被调用的那个 exploit。
Screenshot
Screenshot
我们现在离 system() 越来越近了。
现在 p(指针)会更新为我们传给 SUID 二进制文件的最后一个参数,
/tmp///net。
为什么我们明明可以传递 /tmp/net,却要提供 /tmp///net?
我们要绕过这个检查:
if (((next_next == (char *)0x0) || (next_next[1] == '\0')) || ((long)next_next - (long)p != 6))
我们需要 /tmp/net 存在,并且 /tmp/// 的长度为 6。
现在最后一个 stat64 会检查 "/dev/net" 是否存在。
__snprintf_chk(cmd,0x1000,1,0x1000,"/dev%s",next_next);
并且它会找到,所以我们通过了最后一项检查。
现在它会检查某些文件是否可用,但在这一点上并不重要,
因为我们已经准备好,即将触发任意命令执行。
现在 eina_strbuf_new() 只会初始化将要传递给 system 的命令,
问题在于我们输入的内容是:
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), "/dev/../tmp/;/tmp/exploit" /tmp///net
但该二进制文件多次调用 eina_strbuf_append_printf(),最终变成
/bin/mount -o noexec,nosuid,utf8,nodev,iocharset=utf8,utf8=0,utf8=1,uid=$(id -u), /dev/../tmp/;/tmp/exploit /tmp///net
注意双引号被移除了,这样我们就能够以 root 身份调用 /tmp/exploit。
Screenshot
该二进制文件已尽力缓解任何非预期行为,但像往常一样,任何东西都可能被攻破。
我没料到会通过这样一个逻辑漏洞来利用它。
我希望下一个 CVE 是能够导致 LPE root 的内存破坏漏洞。