Elevate your privileges with Polkit.
First, I want to thank MuirlandOracle wich created the room Polkit: CVE-2021–3560 on TryHackMe. I wrote this article after doing the room, to help me more understand this vulnerability and to share you the details about it.
What is Polkit ?
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 ones. Polkit allows a level of control of centralized system policy. It is developed and maintained by David Zeuthen from Red Hat and hosted by the freedesktop.org project.
According to the Polkit reference Manual, the system architecture of polkit is comprised of the Authority (implemented as a service on the system message bus) and an Authentication Agent per user session (provided and started by the user’s graphical environment). Actions are defined by applications. Vendors, sites and system administrators can control authorization policy through Authorization Rules.
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”.
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.
Polkit could be tricked into bypassing the credential checks for D-Bus requests, elevating the privileges of the requestor to the root user.
This flaw could be used by an unprivileged local attacker to, for example, create a new local administrator. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.
Different distributions of Linux (and even different versions of the same distributions) use different versions of the software, meaning that only some are vulnerable.
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”)
The original description of this vulnerability can be found in a post written by Kevin Backhouse, here.
How is Polkit Vulnerable ?
By manually sending dbus messages to the dbus-daemon (effectively an API to allow different processes the ability to communicate with each
other), then killing the request before it has been fully processed, we can trick polkit into authorising the command. If you are not familiar with daemons, they are effectively background services running on Linux. The dbus-daemon is a program running in the background which brokers messages between applications.
The attacker manually sends a dbus message to the accounts-daemon requesting the creation of a new account with sudo permissions (or latterly, a password to be set for the new user). This message gets given a unique ID by the dbus-daemon.
The attacker kills the message after polkit receives it, but before polkit has a chance to process the message. This effectively destroys the unique message ID. Polkit asks the dbus-daemon for the user ID of the user who sent the message, referencing the (now deleted) message ID.
The dbus-daemon can’t find the message ID because we killed it in step two. It handles the error by responding with an error code.
Polkit mishandles the error and substitutes in 0 for the user ID — i.e. the root account of the machine.
Thinking that the root user requested the action, polkit allows the request to go through unchallenged.
In short, by destroying the message ID before the dbus-daemon has a chance to give polkit the correct ID, we exploit the poor error-handling in polkit to trick the utility
into thinking that the request was made by the all-powerful root user.
The first 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 the int32:1 flag). With the time command, we determine how long our command will take to run. This takes 0.011 seconds, or 11 milliseconds. This number will be slightly different each time you run the command.
We need to kill the command approximately halfway through execution, here comes the second command. 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.
To explain the second 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.
Now, we need to give a password to “attacker”, so let’s encrypt one in SHA512-Crypt. With (Expl01ted) as a password.
Now let’s finish this! 5 milliseconds worked last time, so it should work here too. We just have to put the hash we created with the password Expl01ted.
We are root !