Skip to main content
Pwnkit: Linux Privilege Escalation (CVE-2021-4034)
  1. Blog Posts/

Pwnkit: Linux Privilege Escalation (CVE-2021-4034)

·1362 words·7 mins
Exploitation N-Day CVE-2021-4034
Table of Contents

This was a Linux Priviledge Escalation (LPE) vulnerability in polkit’s pkexec that affected almost every linux distribution. Today we are going to look at the effects of OOB (out-of-bounds) read and write bugs and how they can be critical.

Overview #


What is an OOB vulneraility?

An out-of-bounds write (OOBW) vulnerability is when a process or a software writes data past the end or before the beginning of the allocated buffer in memory. An out-of-bounds read vulnerability (OOBR) is when a process reads data from outside the bounds of an allocated buffer in memory.These vulnerabilities can therefore lead to memory corruption, a system crash or chained for RCE.We are going to see in play the effect of these vulnerabilities chained leading to a local privilege escalation.

What is this polkit pkexec in linux?

This is a tool from the polkit or policy kit software package and it is a common component for modern unix-based systems. It is used to control system-wide privileges and provides an organized way for non-privileged processes to communicate with privileged processes. It can also be used to execute commands with elevated privileges much like sudo using the command pkexec command.

According to the linux manual page

man polkit

PolicyKit provides an authorization API intended to be used by privileged 
programs (“MECHANISMS”) offering service to unprivileged programs (“CLIENTS”) 
through some form of IPC mechanism such as D-Bus or Unix pipes.

man pkexec

pkexec allows an authorized user to execute a PROGRAM as another user and 
If that username is not specified, then the program will be executed as 
the admin super user that is root.

Root Cause Analysis (RCA) #


As started earlier the vulnerability is an out-of-bounds read and write vulnerability and this can be seen from the source code snippet of pkexec that was presented by Qualys Security Company during their disclosure.

Below are source code snippets that are used to describe the vulnerability in detail.

  1. The main() entry function that processes the command-line arguments. (pkexec takes in different arguments from the command-line)
int main(int argc, char *argv[]){
	....
	for(n=1, n < (guint) argc, n++){
		// do something
	}
	
	path = g_strdup(argv[n]);
	
	if (path[0] != "/"){
		s = g_find_program_in_path(path);
		argv[n] = path = s;
	}
}
  • From the above snippet and as it is in every C application, the number of arguments are stored in argc variable and the actual arguments passed stored in the pointer array char *argv[]. Therefore when we pass no arguments to the /usr/bin/pkexec the value of argc will be 1 (this is the calling process’s name) which is also the value in argv followed by a null value to terminate the array.

  • This doesn’t make sense at all we need argv to be NULL:(, its still not vulnerable to OOB, well remember we can initiate any process with the execve() syscall and control args passed to the process. What if we pass a NULL value to to execve() as follows:

    execve(/usr/bin/pkexec, NULL, char **envp)

The value of argc will now be 0 and the value of char *argv[] will be NULL, terminating the array.

  • As from the code snippet above, the following will be our values during execution.

    1. The for loop initializes int n to 1.
    2. The pointer path points to a malloced() address that is argv[n], since the value of n == 1, argv[1] reads passsed our terminated array leading to an OOBR.
    3. The pointer (s) writes into argv[1] again and this leads to an OOB-write vulnerability.
  • Since we did not provide any arguments when calling the process, what is read and written into these pointers?. When a new process is called with the execve() syscall or when you run a new process from the terminal with command-line arguments, the kernel copies out arguments and all eviroment variable strings and pointers to the end of the new programs stack. That is to the end of the main() function stack.

  • Therefore argv and envp pointers are contagious in memory in that they are next to each other as shown in the image below.

image

  • This means that when we pass no arguments to pkexec, argv[1] now points to out first enviroment variable that is set. Let us assume the following scenarion:

    1. We add an environment variable called FOO by passing it to execve().
    2. When calling pkexec with not command-line arguments as above, the code snippet will allocate argv[1] that is now FOO dynamically on the heap using g_strdup() and the address returned to the (path) pointer.
    3. It will then check if path[0] begins with / to determine if its an absolute path or not which in this case it doesn’t .
    4. Since FOO does not contain / it will execute g_find_program_in_path() and the value returned to s will the absolute path of value and this will overwrite argv[1] which is now envp[0] (our environment variable) OOBW.

Exploitation #


How can this be exploited to gain root? According to the Qualys Security team, Since we control envp[0], that is an environment variable using the OOBW vulnerability, we can write anything to argv[1] (i.e envp[0]). Therefore we need to look at some of the dangerous enviroment variables that we can exploit.

(Note that after the out-of-bounds write above, the process clears out its enviroment variables.)

LD_PRELOAD

This is an enviroment variable that specifies the first shared library to load. We can therefore use the OOB-write to write this into our env variables but unfortuanetly, (ld.so) clears these types of variables out when running suid processes and before calling the main() function during execution.

GCONV_PATH

This is an enviroment variable somewhat similar to LD_LIBARY_PATH in functionality and this is part of GLib that is the GNOME Library. GLib is a library that is build on top of the C standard library and provides functions like error logging,authentication and PATH lookups.

Since certain functions return messages to users, encoding and decoding to and fro different formats must be considered and this is where GCONV_PATH comes into play.

When a process encounters e.g “CHARSET=UTF-8” enviroment variable, GLib loads $GCONV_PATH/gconv-modules or when this variable is not set the lookup is done from ("/usr/lib/gconv/gconv-modules"). This is a file that contains something like “BS_4730// UTF-8 ISO646 2” and this is used to define a module located at “ISO646.so” that will allow “BS_4730” to be re-encoded as “UTF-8”. (this is just a simple example).

When we control the GCONV_PATH, we can therefore cause this SUID process to run with full root privilleges by loading a shared library file that we control in the .so file.

Proof Of Concept #


Below I have provided a snippet of my POC and the full code can be found at my github page pwnkit.c there are also other different POC payloads out there with the same functionality so be sure to check them out.

int main(int argc, char **argv){
	char *pkexec_envp[] = {"tmp", "CHARSET=xxx", "PATH=GCONV_PATH=."};
	char *pkexec_args[] = {NULL};
	
	mkdir("GCONV_PATH=.", 0755);
	open("GCONV_PATH=./tmp", O_CREAT, 0755);
	mkdir("tmp", 07555);
	open("tmp/gconv-modules", O_WRONLY, 0755);

	execve("/usr/bin/pkexec", pkexec_args, pkexec_envp);
}

The above snippet works in the following way

  1. Since we provided no arguments when calling pkexec via the execve() syscall, argv[1] now becomes whatever we loaded in our enviroment variable that is tmp.

  2. Since tmp does not start with /, the process will look for its absolute path and will check from the paths provided by the PATH enviroment variable.

  3. Since PATH variable contains our spoofed directory GCONV_PATH=. the value that will be returned by g_find_program_in_path() will be GCONV_PATH=./tmp as the absolute path.

  4. GCONV_PATH=./tmp is a valid environment variable therefore using the oob-write we have an arbitrary write into envp[0] = argv[1] = "GCONV_PATH=./tmp", since we also included a CHARSET=xxx , GLib now comes into play and looks for GCONV_PATH from the enviroment variables that contains ./tmp as its value.

  5. It will then go to out ./tmp directory to look for gconv-modules and based on the contents, it will load a library from “./tmp” that we control and when its loaded this will lead to our privilege escalation.

  6. Basically the attack is based on the introduction of an insecure environment variable via the OOBW.

Mitigations #

  1. Update pkexec to the latest version.
  2. Remove the SUID bit in the executable.

References #