Hunters’ Team Axon has researched and developed detection methods based on Pwnkit research. The team has also hunted for exploitation attempts of the Pwnkit vulnerability over Hunters’ customers relevant data sources and ensured no attack has taken place.
This blog will describe our research approach and will review the detection methods that we developed for security teams to implement today.
Background
In January, the CVE-2021-4034 vulnerability, dubbed Pwnkit, was discovered by Qualys research team. Pwnkit is a memory corruption vulnerability in polkit’s pkexec SUID binary. Polkit is an application-level toolkit for defining and handling the policy that allows unprivileged processes to communicate with privileged processes.
The successful exploitation of the Pwnkit vulnerability allows any unprivileged user to gain root privileges on the vulnerable host. Since polkit is installed by default on most of the Linux popular distributions, the attack surface across different systems could be enormous.
It’s also possible to use polkit with the pkexec command, which allows an authorized user to execute a program as another user. If the username is not specified, then the program will be executed as the administrative superuser, root.
Vulnerability Technical Review
Polkit’s pkexec binary has a memory corruption vulnerability leading to a local privilege escalation. An initial foothold is required on the target server in order to perform the attack; however, any person having access to a non-privileged user in a system can gain root privileges in no time.
The vulnerability occurs due to an unsafe handle of arguments validation, which leads to out-of-bound write to the envp (environment variables parameters). Since pkexec is a SUID binary, it runs with root privileges by default if no other user explicitly specifies. Hence, the attacker can manipulate it to execute code with root privileges.
In order to better understand this, let’s first take a look at the process creation internals. The system starts a C program. By calling the function ‘main’, in UNIX systems, one can define ‘main’ using three arguments:
The value of the argc argument is the number of command line arguments, hence a counter of the argument list, starting from 1 as the program name will be part of the command line.
The argv argument is a vector of C strings; its elements are the individual command line argument strings, with the binary name being executed is also included in the vector as the first element.
The third argument envp gives the program’s environment variables: it is the same as the value of environ. An example can be the PATH variable.
The pointer arrays for the argv elements and environment variables are contiguous in the memory call stack, and stored one after the other. Since the array index starts from zero (argv[0]), the end will be argv[argc-1] followed by argv[argc] as null before the start of envp[0]. So, the call stack of a legitimate execution of pkexec with two arguments ‘arg1’ and ‘arg2’ will look like the following:
The main{} function in pkexec has a for loop for command-line arguments, the loop initialization statement of n start from 1, and an expression of argc being lowered than n(=1):
Having said that, if we pass to execve() syscall with NULL argv list, argc would be equal to 0. The loop will be terminated and n will be set to 1.
But where does the read and write out of bound happen? Let’s see line 610 which reads from argv[n=1], and at line 639 the pointer s is written out-of-bounds to argv[1]:
But wait a minute, do we control argv[1]? How come, if we pass a NULL argument list?
Well, we do. Because argv[argc=0), it means that argv[1] points to envp[0] as illustrated below:
A possible exploitation is to use the GCONV_PATH environment variable which is a common envp used for exploitation in Linux due to its ability to have references to modules that are also can be executed.
However, since GCONV_PATH is known as “unsecured”, it was restricted from the environment of SUID programs. The Qualys research team found that it can still be initiated with exploiting input validation functionality using manipulation of g_printerr() function which will get an unexpected encoding (not UTF-8) and call the iconv_open() function which will eventually load an external module from GCONV_PATH.
Detection Methods
Our understanding of the vulnerability led us to define a proper detection logic that detects exploitation attempts of the Pwnkit vulnerability.
As shown earlier, pkexec has to have the binary name itself at argv[0] as part of a regular execution flow of the kernel, even if you execute it from the CLI without any arguments.
In the following example, we’re executing pkexec without arguments. It means that argc should be equal to 1, since argv[0] = pkexec, it will print us pkexec's usage function.
Tracing the execve syscall, we can indeed see pkexec being argv[0] of the execution:
Since endpoint security products mainly use kernel modules to track new process creations by intercepting execve syscalls and by that rely on the argv C elements as the “CommandLine” attribute, we would expect to see pkexec in the “CommandLine” attribute in the EDR logs even if we didn’t specify any arguments as we did above.
And here is a snippet of our example execution in the EDR logs. As expected, ‘pkexec’ exists in the command line:
But, what will happen if we execute the vulnerability which will define the argument list as NULL in an execve syscall? argv will be empty, indeed.
And since we know that argv[0] will always be interpreted as ‘pkexec’ itself in a regular execution flow, we can assume that pkexec execution with the commandline being NULL is a clear indication for exploitation.
To summarize the thesis, we want to filter process creation events with the following filters:
Investigation Flow
Upon hunting and developing our detection thesis, we noticed that specific known vulnerability scanner products actively try to exploit the vulnerability on a variety of our customers. Catching those scanners makes us more confident with the detection thesis since these are real exploitation attempts but simply not malicious. This kind of activity results in benign alerts creation.
The Hunters platform classifies malicious attacks and scanner activity based on auto-investigation of the signals and inspection of related artifacts.
Examples of scoring models implemented in Hunters:
An example of detected scanner activity that scored 6 out of 100
Pwn4Shell: Zero-to-root Attack Model
The Pwnkit vulnerability takes advantage of a flaw in pkexec that is installed by default on most of the Linux popular distributions and is very easy to exploit. Due to this fact, Team Axon sees the potential of an attack that combines it with the Log4Shell attack.
Since Log4Shell exploits the Log4j library that has been loaded by a java process, the malicious payload will be executed with the privileges of the java process, which mostly runs with non-root privileges.
[Read more about Log4Shell on our dedicated blog post]
We’ve modeled the exploitation of Log4Shell followed by Pwnkit, in order to demonstrate “zero-to-root” attack on an external-facing server, with no time. The Hunters platform detects both of the attacks and correlates them together as can be seen in the Attack Story below.
An example of an Attack Story in the Hunters portal demonstrating the “zero-to-root” attack combining Pwnkit and Log4Shell
Summary
In this blog post we’ve explained the Pwnkit vulnerability, learned how it works, and offered detection methods for security teams to take immediate action.
Subscribe to our blog for more information about future detection methods for new vulnerabilities.