Exploit of SUDO vulnerability Heap-Based Overflow [CVE-2021-3156 ]

CVE-2021-3156 is a Heap-Based-Buffer overflow in sudo, in this blog I will share my walkthrough of this CVE and my final exploit for it, let's start with POC.

First, I used AddressSanitizer(ASan) which is used to detect memory access errors such as use-after-free and memory leaks this is a sample output of it, and we can see the file which cause the crash with the line number

and this is the command used to compile sudo with ASan

We can check the root cause of the crash in gdb by passing the payload and breakpoint on line number 868 in file sudoers.c

if we used the next instruction(ni) to follow the code and we can see when we reach the backslash(\) the code is passing the next value after it but our backslash is at the end of the string so what it will be passed? yes, it will be the Null Byte that is used to end the string, what do you think will happen if we do not have the end of our string? it will continue writing into the allocated memory and overflow it.

The vulnerable code with better view

to reach the above vulnerable code we have to pass some conditions, the following screenshot shows the first condition

this screenshot shows the second condition

to reach these two conditions we have to do 2 things first is to use argument (s) which will set MODE_SHELL which will pass the second condition

to reach the first condition we have to use (-edit) any word that should end with (edit) so we can use (sudoedit) which will set MODE_EDIT

so now we have to fuzz onto sudo the idea is simple we will generate random values with random size, and we will pass these values to ENV variables and sudo arguments and if we have a crash we will pass this crash to gdb and save the backtrace and the name of crashed function into the output file (doing it is not easy 😂) so the great playlist from LiveOverflow about the same CVE was useful 

this JSON file includes the arguments used to cause the crash

this file includes the backtrace of the crash

sample of the output file of a crash the JSON file has the used argument second file includes the backtrace of the crash

let's try arguments to cause the crash and see, the first file is a Python file which will pass the arguments to the next file which is (test.c) the following screenshot shows the file with arguments but there is a missing line which it will be 
args = ["./test"] + arg
os.execv("./test", args)

the (test.c) file is taking the arguments and pass it to the (sudoedit) binary but why we used this method? if we try to pass the backslashes as ENV variables it will be parsed as one ENV variable but if we used this method by passing the args to the test binary it will be parsed as the way we want (which is each backslash individual)

the following screenshot shows the output of passing arguments into GDB and the values saved into the ni struct, and we could control the values into this struct so its time to do some code review 

Code Time

I could know the glibc version by running (ldd --version) it is 2.31, I used bootlin elixir to check the code of this version of glibc

the crash happens into (tsearch) function which comes from nss_lookup_function when we search for it we will see that this function is called in nss_lookup it call (nss_lookup_function) 4 times

we have to know that this is the struct of our (ni) variable which we will modify

the (nss_lookup_function) have the (tsearch) in this function too we can find the (nss_load_library) function which take (ni) 

nss_load_library has some interesting thing which is this part use (dlopen) in line 359 which load an external library using the name and if check line 353 it creates a name of the shared library and passes it to (dlopen) to include it with this format (libnss_<name_from_ni>.so.<version> (i.e libnss_flex.so.2)) you can read the man page of dlopen
If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname

but how we can set (ni->library->lib_handle) to NULL we can not control it but we can NULL the (ni->library) itself but this will not pass the second condition it will jmp into the first one which will call the (nss_new_service) function and save its return into (ni->library) so we can check it

in nss_new_service we can see in line 812 the function set some arguments of struct (sevice_library) which have (lib_handle) and when the function return (currentp) and it is saved to (ni->library) now it have lib_handle with NULL value so we can reach second condition

Exploit Time

We modified the arguments a bit, and we added the name of the lib we want to include with "/" (note that) after adding the padding to reach the vulnerable code we used some backslashes which replace the NULL bytes (we can not use null bytes remember that) we set (ni->next) & (ni->library) to (0x00) this will reach the condition from (nss_load_libbrary) which will call (nss_new_service) to create & set (lib_handle) to NULL and using that we will reach the second condition will will load library with our controlled name

breakpoint into (nss_load_library) and use (ni) until reaching the (dlopen) call and when we see the arguments of the function we can see that we controlled the library name.

let's write the library and convert the Python code into C and write our shared library.


we have 4 files for our exploit
  • flex.c : shared library which we will load
  • exploit.c : include the arguments(payload) which will be passed to (root) file
  • root.c : will take the arguments(payload) from (exploit) file and pass it to (sudoedit)
  • run.sh : bash file to compile the C files and run the exploit
the following screenshots show the above files with their contents


That's it, Tschüss.


Popular posts from this blog

Exploit & Debug Looney Tunables CVE-2023-4911 Local Privilege Escalation in the glibc's ld.so

Lets Analysis STM32F103 Chip Firmware from Attify

Using CSRF I Got Weird Account Takeover