一个可靠的漏洞利用+分析文章,用于提权至root权限。(在Ubuntu 22.04上测试过)
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 反编译的伪代码比直接查看源码更令人愉悦(避免宏定义,也避免那些根据编译 OS 选择特定代码块的检查)。
那么,开始分析。
1- 操作二进制文件。
让我们运行该文件以查看关于我们目标的一些信息:

运行二进制文件没有任何输出:

使用 --help 参数得到以下输出:
抱歉,我将用它来获取 root 权限。
接下来,我们使用 strace 看看它是否会使用任何可疑的系统调用,比如 execve 或 openat:
strace ./enlightenment_sys 2>&1 | grep open
它仅仅打开了已知的库,这些位置我们没有权限篡改。
strace ./enlightenment_sys 2>&1 | grep exec

2- 让我们逆向这个二进制文件,然后利用它。
我创建了一个新的 Ghidra 项目,并加载了这个特定的二进制文件。 由于没有找到符号,我们可以通过 entry 点找到 main 函数。entry 函数的第一个参数就是 main 本身。我将其重命名为 main 以便后续引用。 向下滚动一点,我已经看到了 system() 函数被使用。
作为一个 pwn 手,我花了很多天在挑战上就是为了调用这个特定的函数 x)
我逆向这个二进制文件,寻找内存损坏漏洞或堆问题,但实际上它是一个奇怪的命令注入。
该二进制文件在运行 system 之前采取了所有安全预防措施,但遗憾的是我们总能将我们的输入注入进去。

好的,现在我们从头开始跟踪这个二进制文件直到我们的 system 函数,试图将我们的输入注入进去。
首先,二进制文件检查第一个参数是否为 --help 或 -h,并显示我们之前看到的消息。

其次,它将权限提升到 root。

接着,它几乎清除了所有环境变量(安全预防措施),以避免调用另一个非预期的二进制文件。

如果我们输入的第一个参数是 "mount",它将进入这个分支,检查一些给定的标志,这些标志将被设置在栈上。
接下来,它检查 mount 之后的下一个参数是否以 UUID= 开头,我们不想进入这里,所以我们给出了 "/dev/../tmp/;/tmp/exploit"。
这样我们就通过了第 410 行的检查,即 strncmp 检查。
因为如果不是以 /dev/ 开头,二进制文件就会退出。
接下来,它对提供的文件调用 stat64,注意我们可以创建一个名为 ";" 的文件夹,这将导致命令注入。
到目前为止,利用程序已经创建了文件 /dev/../tmp/;/tmp/exploit,但这并不是将要被调用的利用程序。

我们现在越来越接近 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。

该二进制文件尽力减轻任何非预期行为,但像往常一样,任何东西都可以被攻破。我没想到会用这样的逻辑漏洞来利用它。 我希望下一个 CVE 是内存损坏导致的本地权限提升至 root。
Twitter 披露:https://twitter.com/maherazz2/status/1569665311707734023