The Qualys Research Team has discovered a memory corruption vulnerability in polkit’s pkexec, a SUID-root program that is installed by default on every major Linux distribution. This easily exploited vulnerability allows any unprivileged user to gain full root privileges on a vulnerable host by exploiting this vulnerability in its default configuration.
About Polkit pkexec for Linux
Polkit (formerly PolicyKit) is a component for controlling system-wide privileges in Unix-like operating systems. It provides an organized way for non-privileged processes to communicate with privileged processes. It is also possible to use polkit to execute commands with elevated privileges using the command pkexec followed by the command intended to be executed (with root permission).
Potential Impact of PwnKit Vulnerability
Successful exploitation of this vulnerability allows any unprivileged user to gain root privileges on the vulnerable host. Qualys security researchers have been able to independently verify the vulnerability, develop an exploit, and obtain full root privileges on default installations of Ubuntu, Debian, Fedora, and CentOS. Other Linux distributions are likely vulnerable and probably exploitable. This vulnerability has been hiding in plain sight for 12+ years and affects all versions of pkexec since its first version in May 2009 (commit c8c3d83, “Add a pkexec(1) command”).
As soon as our Research Team confirmed the vulnerability, Qualys engaged in responsible vulnerability disclosure and has coordinated with both vendor and open-source distributions to announce the vulnerability.
Vulnerability Disclosure Timeline
- 2021-11-18: Advisory sent to [email protected]
- 2022-01-11: Advisory and patch sent to [email protected]
- 2022-01-25: Coordinated Release Date (5:00 PM UTC).
Proof of Concept Video of PwnKit Exploit
View this video of a potential exploit path.
Technical Details of PwnKit Vulnerability
What follows is an explanation of how the PwnKit vulnerability works.
The beginning of pkexec’s main() function processes the command-line arguments (lines 534-568), and searches for the program to be executed, if its path is not absolute, in the directories of the PATH environment variable (lines 610-640):
------------------------------------------------------------------------
435 main (int argc, char *argv[])
436 {
...
534 for (n = 1; n < (guint) argc; n++)
535 {
...
568 }
...
610 path = g_strdup (argv[n]);
...
629 if (path[0] != '/')
630 {
...
632 s = g_find_program_in_path (path);
...
639 argv[n] = path = s;
640 }
------------------------------------------------------------------------
Unfortunately, if the number of command-line arguments argc is 0 – which means if the argument list argv that we pass to execve() is empty, i.e. {NULL} – then argv[0] is NULL. This is the argument list’s terminator. Therefore:
- at line 534, the integer n is permanently set to 1;
- at line 610, the pointer path is read out-of-bounds from argv[1];
- at line 639, the pointer s is written out-of-bounds to argv[1].
But what exactly is read from and written to this out-of-bounds argv[1]?
To answer this question, we must digress briefly. When we execve() a new program, the kernel copies our argument, environment strings, and pointers (argv and envp) to the end of the new program’s stack; for example:
|---------+---------+-----+------------|---------+---------+-----+------------|
| argv[0] | argv[1] | ... | argv[argc] | envp[0] | envp[1] | ... | envp[envc] |
|----|----+----|----+-----+-----|------|----|----+----|----+-----+-----|------|
V V V V V V
"program" "-option" NULL "value" "PATH=name" NULL
Clearly, because the argv and envp pointers are contiguous in memory, if argc is 0, then the out-of-bounds argv[1] is actually envp[0], the pointer to our first environment variable, “value”. Consequently:
- At line 610, the path of the program to be executed is read out-of-bounds from argv[1] (i.e. envp[0]), and points to “value”;
- At line 632, this path “value” is passed to g_find_program_in_path() (because “value” does not start with a slash, at line 629);
- Then, g_find_program_in_path() searches for an executable file named “value” in the directories of our PATH environment variable;
- If such an executable file is found, its full path is returned to pkexec’s main() function (at line 632);
- Finally, at line 639, this full path is written out-of-bounds to argv[1] (i.e. envp[0]), thus overwriting our first environment variable.
So, stated more precisely:
- If our PATH environment variable is “PATH=name”, and if the directory “name” exists (in the current working directory) and contains an executable file named “value”, then a pointer to the string “name/value” is written out-of-bounds to envp[0];
OR
- If our PATH is “PATH=name=.”, and if the directory “name=.” exists and contains an executable file named “value”, then a pointer to the string “name=./value” is written out-of-bounds to envp[0].
In other words, this out-of-bounds write allows us to re-introduce an “unsecure” environment variable (for example, LD_PRELOAD) into pkexec’s environment. These “unsecure” variables are normally removed (by ld.so) from the environment of SUID programs before the main() function is called. We will exploit this powerful primitive in the following section.
Last-minute note: polkit also supports non-Linux operating systems such as Solaris and *BSD, but we have not investigated their exploitability. However, we note that OpenBSD is not exploitable, because its kernel refuses to execve() a program if argc is 0.
Solution: How to Patch the PwnKit Vulnerability
Given the breadth of the attack surface for this vulnerability across both Linux and non-Linux OS, Qualys recommends that users apply patches for this vulnerability immediately.
Current Qualys customers can search the vulnerability knowledgebase for CVE-2021-4034 to identify all the QIDs and assets vulnerable for this vulnerability.
Other interested parties can start a free Qualys VMDR trial to get full access to the QIDs (detections) for CVE-2021-4034, where all vulnerable assets can be identified.
Qualys QID Coverage
Qualys is releasing the QIDs in the table below as they become available starting with vulnsigs version VULNSIGS-2.5.387-2 and in Linux Cloud Agent manifest version lx_manifest-2.5.387.2-1.
QID | Title | VulnSigs Version |
376287 | Polkit pkexec Local Privilege Escalation Vulnerability (PwnKit) | VULNSIGS-2.5.387-2 / lx_manifest- VULNSIGS-2.5.387.2-1 |
Discover Vulnerable Linux Servers Using Qualys VMDR
Identify Assets Running Linux Kernel
The following instructs current Qualys customers on how to detect PwnKit in their environment.
The first step in managing this critical vulnerability and reducing risk is the identification of all assets running Linux OS. Qualys VMDR makes it easy to identify such assets.
Query: operatingSystem.category1:`Linux`
Once the hosts are identified, they can be grouped together with a ‘dynamic tag’, let’s say: “Linux Servers”. This helps by automatically grouping existing hosts with the above vulnerabilities as well as any new Linux assets that spin up in your environment. Tagging makes these grouped assets available for querying, reporting, and management throughout the Qualys Cloud Platform.
Prioritize Based on RTIs
Using Qualys VMDR, the PwnKit vulnerability can be prioritized using the following real-time threat indicators (RTIs):
- Predicted_High_Risk
- Privilege_Escalation
- Easy_Exploit
- High_Lateral_Movement
Patch With Qualys VMDR
We expect vendors to release patches for this vulnerability in the short term. Qualys Patch Management can be used to deploy those patches to vulnerable assets, when available.
Using the same prioritization based on the RTI method as described above, customers can use the “patch now” button found to the right of the vulnerability to add PwnKit to a patch job. Once patches are released, Qualys will find the relevant patches for this vulnerability and automatically add those patches to a patch job. This will allow customers to deploy those patches to vulnerable devices, all from Qualys Cloud Platform.
Detect Impacted Assets with Threat Protection
VMDR also enables you to automatically map assets vulnerable to PwnKit vulnerabilities using Threat Protect.
Track Vulnerability with VMDR Dashboard
With VMDR Unified Dashboard, you can track this vulnerability, impacted hosts, status, and overall management in real time. With trending enabled for dashboard widgets, you can keep track of these vulnerability trends in your environment using the “PwnKit” Dashboard.
Leverage Qualys XDR Identifying Exploit Attempts
Qualys XDR customers can use the rule name titled – “T1068 – Linux: Polkit pkexec Local Privilege Escalation Vulnerability Detected (CVE-2021-4034)” to detect post-exploitation activity on affected systems. Post enablement, customers can also search for vulnerable systems using the following QQL query:
eventName:” The value for the SHELL variable was not found the /etc/shells file“ or “contains suspicious content“
Customers will be able to see output similar to the following screenshot:
Interested customers can contact Qualys Support for a copy of the XDR rule until it is available in the rule library.
Vendor References
Read the PwnKit security advisory
Frequently Asked Questions (FAQs)
What versions are vulnerable?
All Polkit versions from 2009 onwards are vulnerable.
Will the Qualys Research Team publish exploit code for this vulnerability?
No. But given how easy it is to exploit the vulnerability, we anticipate public exploits to become available within a few days of this blog’s post date.
Are there any mitigations for this vulnerability?
If no patches are available for your operating system, you can remove the SUID-bit from pkexec as a temporary mitigation; for example:
# chmod 0755 /usr/bin/pkexec
Is this vulnerability remotely exploitable?
No. But if an attacker can log in as any unprivileged user, the vulnerability can be quickly exploited to gain root privileges.
Is it possible to check for evidence of exploitation?
Yes, this exploitation technique leaves traces in the logs (either “The value for the SHELL variable was not found the /etc/shells file” or “The value for environment variable […] contains suspicious content”). However, please note that this vulnerability is also exploitable without leaving any traces in the logs.
Why is the vulnerability named “PwnKit”?
This is a pun intended on the name of the vulnerable application Polkit.