HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

admin 2024年5月31日10:50:14评论4 views字数 5554阅读18分30秒阅读模式

Qualys 团队在 Glibc 库的 __vsyslog_internal 函数中发现了一个堆溢出,这个问题允许攻击者仅通过更改程序名称来提升权限。本文介绍堆溢出是什么,它们是如何工作的,以及开发 CVE-2023-6246 利用程序来提升权限 [1] 。

一、基本概念

什么是堆?堆是计算机内存中用于动态内存分配的区域。它是全局可访问的,这意味着在堆上分配的内存不与特定函数的作用域绑定,可以从程序的任何部分访问。与栈不同,栈通常具有固定大小,并遵循后进先出(LIFO)模型用于函数调用和局部变量,堆更加灵活,可以在程序执行期间动态分配和释放内存。

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

栈是按照特定的顺序进行分配和回收的,而堆则不同,堆的分配和回收没有特定的顺序或偏好。图二展示了堆是如何工作的:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

  1. 使用像malloc这样的函数来分配特定大小的内存空间A。
  2. 同样的过程也适用于另一块大小不同的数据B,它被存储在堆中。
  3. A中的数据被释放,因此,它所使用的空间现在可用。
  4. 另一块数据C要存储在堆中,但A留下的空间不够,因此,它将被分配在B之后的内存空间。
  5. 最后,当分配另一组较小的数据D,B和C之前的空间满足要求,所以它可以分配在B和C之前。

什么是堆溢出

堆溢出是一种内存损坏漏洞,当程序在堆中写入长度超出分配的内存块的大小时,就会产生这种问题。

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

在上图中,D 是动态分配的内存,但由于堆溢出,可以写入超出 D 边界的数据,这意味着它将覆盖 B 和 C 并破坏堆结构。这种情况将会导致各种崩溃:

  1. malloc(): invalid size:这表示在调用 malloc 函数时传递了一个无效的大小参数。可能是负数、零或其他不合法的值;
  2. malloc(): corrupted top size:这通常是由于堆溢出或其他内存错误导致的。堆被破坏,导致 malloc 函数无法正确管理内存;
  3. double free or corruption (out) :这表示尝试释放已经被释放的内存块,或者堆被破坏;
  4. free(): corrupted unsorted chunks:这也是堆破坏的迹象。堆的数据结构被破坏,导致 free 函数无法正确释放内存;
  5. free(): invalid size:类似于 malloc 中的错误,这表示在调用 free 函数时传递了无效的大小参数。

以攻击者视角来看,可以通过两种不同的方式利用这种情况:

  1. 攻击堆元数据:堆是一种带有元数据的数据结构。这些元数据包括当前堆块的大小、前一个块、下一个块、是否正在使用等信息。基于这些知识,可以尝试利用堆的功能算法;
  2. 攻击堆数据:另一方面,根据之前显示的堆溢出,可以考虑覆盖其他内存块。如果在当前内存之后的堆块中存储了有用的数据,此时可以覆盖并利用它。在本文中就是采用的这种利用方式。

在上面提到过,堆分配时难以预测。在不同执行过程中,缓冲区位置可能会因大小、环境变量和其他因素不同,导致缓冲区分配在堆的不同位置。

那么,问题来了:应该如何布局,将缓冲区定位在特定“有用”数据之前呢?这就需要用到堆风水。

堆风水/堆喷

堆风水是一种复杂的内存利用技术,涉及操纵堆内存的布局,以实现有利于利用的特定内存条件。堆风水的主要目标是操纵堆内存的布局,通过策略性地分配和释放内存块,影响内存块分配到堆中特定位置的过程。这可能涉及反复分配和释放块,以控制敏感数据或函数指针的放置位置。

二、GLIBC 堆溢出漏洞(CVE-2023-6246)

Qualys 安全团队发现了 GLIBC 堆溢出漏洞。在他们的研究中,发现某些版本 GLIBC 的 __vsyslog_internal 函数在处理动态内存时方式不当,攻击者可以更改程序名称(argv[0]),以操纵缓冲区大小并将超长的数据写入堆中导致堆溢出。

以下命令可以测试是否容易受到此堆溢出的影响:

$ (exec -a "`printf '%0128000x' 1`" /usr/bin/su < /dev/null)Password: Segmentation fault (core dumped)

该漏洞影响Debian、Ubuntu、Fedora等多个操作系统,但具体利用方法可能有所不同。

利用分析

根据 Qualys 的披露信息,攻击者可以根据不同大小的程序名称来触发漏洞,并通过 ‘-w’ 白名单选项来构造堆风水。通过堆布局,可以覆盖模块的字段名称,并注入自己的共享库以提升权限。

首先,必须确保在堆溢出发生后,能够访问操纵模块结构的程序函数。这一点至关重要,因为如果配置或操作系统在程序的执行流程中进行了更改,并且没有调用这些函数,那么利用就不会成功。

好消息是,我们知道我们需要什么。因此,使用了gdb-peda来调试“su”二进制文件。强烈建议使用调试符号来与源代码一起进行调试。为了方便设置程序名称,定义了一个类似下面的 wrapper.sh 文件:

#!/bin/bashp=""for i in {1..buff_length}; do    p+="A"donep=$(echo -e "$p")exec -a "$p" "$@"

然后,在 gdb 中,设置了exec-wrapper作为命令将程序名称更改为大于 1024 的值。并在一些有趣的函数中设置了断点,以了解它们的上下文、工作原理、调用方式和时间。

gdb-peda$ set exec-wrapper ./wrapper.shgdb-peda$ set env A=agdb-peda$ b __vsyslog_internalgdb-peda$ b __nss_lookup_functiongdb-peda$ b __nss_module_get_functiongdb-peda$ b module_loadgdb-peda$ b env_whitelist_from_stringgdb-peda$ r -w`python -c “print(‘A’)”`

经过调试分析后得出了一些结论:

  1. 堆溢出发生在 vsyslog_internal 中,在用户输入密码后;
  2. 在程序执行期间,__nss_lookup_function__nss_module_get_function 都被多次调用,即使在 vsyslog_internal 之后也是如此;
  3. 它们只在模块尚未加载的情况下会调用 module_load
  4. 白名单选项(-w)允许根据用户输入和环境变量的存在来分配和释放一些数据,稍后会进一步探讨这个问题。

接下来进行模糊测试。

Fuzzing

以下 Fuzzer 侧重于 fuzz 程序名称大小、白名单选项及其关联的环境变量。

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

这个 fuzzer 非常基础,因为它只迭代测试了不同长度的程序名称和环境变量。fuzzer 监视每次执行产生的核心转储文件, 如果之前没有记录过核心转储,fuzzer 就会存储它,这使得能够跟踪具有不同错误的核心转储文件,及其相应的参数组合。

然而,这个 fuzzer 产生了大量的案例。因为对于每组环境变量和白名单选项,fuzzer 每次都将程序名称长度从 1000 增长到 120000。而且它并没有涵盖所有可能的场景,如果想在白名单选项中使用重复值,例如“-w A,A,A,B,B,C,C”,该怎么办?

在手动测试期间观察到,如果用户手动输入密码,而不是从 /dev/null 重定向,堆分配会发生变化。因此创建了另一个版本的 fuzzer 来测试,就是使用 forkpty 使用文件描述符向密码发送换行符,这次发现了一些有意思的输出,例如可以找到 Qualys 在其披露中提到的一些案例,比如对“RAX”的调用:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

这种情况可能很有意思,但如果使用这种方式进行利用,那么需要在我们的输入中加入内存地址,这会使得程序名称必须包含至少一个 x00 字节(空字节),但是我们不能在程序名称中插入空字节。而且由于ASLR 的存在,没有内存泄漏使得利用变得非常困难,便放弃了这种利用方式,尽管也许可以利用。

除此之外,fuzzer 还生成了几个值得注意的输出:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

这些结果非常有意思,因为这些情况与 sudo baron SameEdit (CVE-2021-3156,sudo堆溢出漏洞)堆溢出利用非常相似,可以通过使用包含斜杠的库名称来注入共享库。例如,如果在名称字段中写入“X/X”,则会加载 libnss_X/X.so.2库文件。

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

想要在执行时覆盖 ni 和 module 结构体指针,二者实际上会使用相同的环境变量和白名单选项,只是程序名称的长度不同:

program_name_length = 9000   # Case overwrite moduleprogram_name_length = 12000  # Case overwrite Nigdb-peda$ env A=agdb-peda$ run -w`python -c "print('A,'*2500)"`

经过调试和比较后发现,module.name 字段非常靠近输入的缓冲区,只要输入更长的程序名称即可覆盖它:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

由于我们的缓冲区放置在 module 内容之前,因此可以很容易进行堆块布局,但现在还存在两个问题:

  1. 我们没办法在程序名称中包含斜杠。即使我们能够覆盖名称字段,它也不会被加载,因为我们的堆溢出(程序名称)不能包含斜杠。为了解决这个问题,观察了堆溢出的缓冲区, 我们的堆附加了身份验证失败的错误消息,其中在 tty=/dev/pts/1 中会包含斜杠(参见图十)。因此,现在我们知道,如果我们创建一个与错误消息结束的缓冲区同名的共享库,这种利用很可能成功。(这里用户名是kali,但操作系统是fedora,kali只是使用的用户名)
  2. 第二个问题是程序在执行“__nss_module_get_function”之前,程序需要使用 module 指针和 ni 结构,由于它们被我们的缓冲区覆盖,因此程序发生了崩溃。

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

这时候就需要使用堆风水对堆块进行布局了。

三、Fengshui

在图六中,可以观察到我们实际上已经覆盖了module指针。但现在要搞清楚堆布局,弄清楚在这儿发生了什么,就不能使其发生溢出。现在设置一些断点,并使用相同的白名单选项和环境变量重新运行:

gdb-peda$ set env A=agdb-peda$ r -w`python -c “print(‘A,’*2500)”`

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

现在可以看到环境变量被放置在堆中。经过仔细观察,可以注意到的是这些间隙的尺寸正在增大,形成了一种规律。这种现象是有原因的,因为我们重复执行了大量的 malloc 和 free 操作,这些间隙很可能是按这种分配和释放模式产生的。

在堆溢出时,其中一些间隙可能被程序用于其他目的,当缓冲区覆盖这些间隙时,就会出现问题,导致有用数据被破坏。这就是我们覆盖 module 指针和 ni 结构的两种情况所发生的情况,导致了程序的崩溃。那应该如何处理呢?

试想一下, 如果我们根据破坏程序的块的大小创建更多环境变量会怎样?如下图:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

在上图的左侧部分,描述了当前的堆布局。module 指针和 ni 结构体在这些缓冲区中间,随意的溢出将会破坏这些缓冲区的数据。但是,如果我们控制一些其他环境变量来占用它们的位置呢?在这种情况下,它们将无法再占用这些位置,因此将会在不同内存位置进行分配。这正是在上图右边堆图中实现的,我们又定义了两个环境变量B和C,其内容与 module 指针块和 ni 结构堆块的大小相同,新的环境变量占用了先前执行中 module 指针和 ni 结构间隙所占用的空间,从而迫使它们重新定位到不同的位置。

首先尝试使用 module 指针将猜想付诸实践,为了避免覆盖到它,声明了一个具有适当大小的环境变量并分配了 5 次:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

在上图中,我们可以看到左侧的 RAX 是模块的指针 0x55555557c6c0,底部有一个 0x60 大小的块,与存储该指针的位置相匹配。在图右侧部分,我们将环境变量“F”设置为值 f*0x55,因为我们想要占用块大小为 0x60 的空间。正如所看到的,模块指针现在是 0x5555555bc2e0,因为环境变量中的“f”占用了 0x60 块。

接下来对 ni 结构重复相同的过程,设置与 ni 结构大小相同的堆块。此时可能会注意到一些事情:更改环境变量中的大小,甚至使用更多环境变量,可能会改变堆的整个布局,这可能导致我们的缓冲区被放置在不同的位置。为了防止缓冲区被分配到意想不到的位置,调整环境变量的数量和白名单选项非常重要。

经过一番尝试,最终实现了所需的布局,程序不再崩溃,并且成功覆盖了 module 数据:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

接下来就可以尝试权限提升了。

四、权限提升

如果想加载自己的共享库,则需要稍微调整一下缓冲区消息:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

此时程序正在尝试加载名为“libnss_AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= uid=$uid euid=0 tty=/dev/pts/2 ruser=$user rhost= user=roo.so.2”的共享库。

此处“uid”、“ruser”和“tty”中的值可能会有所不同,具体取决于 tty 和用户名,这不难获取。此时可以创建一个与程序正在查找的文件名相匹配的文件夹结构,并编译具有预期名称的提权代码:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

最终效果如下:

HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)

利用成功,获得root权限   可以在这里 [2] 找到整个脚本。

注意:运行 ./run.sh 时,会提示输入密码,此提示来自请求密码的 su 二进制文件。在此过程中必须手动输入密码,但不必是正确的密码。

参考

[1] https://medium.com/@elpepinillo/heap-heap-hooray-unveiling-glibc-heap-overflow-vulnerability-cve-2023-6246-0c6412423269

[2] https://github.com/elpe-pinillo/CVE-2023-6246

原文始发于微信公众号(山石网科安全技术研究院):HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023–6246)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月31日10:50:14
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   HEAP HEAP HOORAY - 揭示 GLIBC 堆溢出漏洞 (CVE-2023-6246)https://cn-sec.com/archives/2798600.html

发表评论

匿名网友 填写信息