CVE-2021-3560
This bug is a combination of a race condition and bad error handling. Basically:
- Manually request an action that requires superuser access via DBus.
- Kill dbus-daemon after Polkit has received the message but before it responds (there’s the race).
- Polkit requests the user ID associated with the message, but since DBus has restarted it can’t reference the original message ID and responds with an error.
- Polkit mishandles the error, substituting 0 for the ID of the requesting user (there’s the botched error handling).
- Because the requesting user ID now appears to be root, Polkit just goes ahead and takes the action without issuing a challenge.
Ubuntu fixed with with version 0.105-26ubuntu1.1 of the policykit-1 package (the last vulnerable version was 0.105-26ubuntu1).
-
Construct a message similar to the following:
The three parameters are:
string:attacker
— the name of the user to create.string:"Pentester Account"
— the user “description” (GECOS field).int32:1
— grant sudo access to the user.
-
Begin by determining how long the message takes to execute on the target. This can be done with the
time
command. -
DBus needs to be killed approximately halfway through this execution period. We cannot wait for the application to return, so instead we background it.
Here
$TIME
is approximately half the time measured in the previous step. -
ID the created user.
-
Create a new password hash for the user.
-
Pull the same trick as in step 3, but with setting the created user’s password.
Here
$UID
is the user ID retrieved in step 4 (note that there’s no space betweenUser
and the ID, so you’ll be hitting an endpoint that looks something like /org/freedesktop/Accounts/User1003),$PASSWORD_HASH
is the hash returned in step 5, and$TIME
is the same timing determined from step 3. The secondstring
being passed in the user password hint. -
Log in as the new user (probably via su).
CVE-2021-4034 (“Pwnkit”)
The exploit is then as simple as gcc $FILE -o exploit; ./exploit
.
Caution
This version of the exploit will leave traces in the target’s logs!
Here’s what happens on execution:
-
The exploit creates the path
GCONV_PATH=.
in the current directory and adds an invalid executable file to it. -
The exploit creates a second directory called pwnkit and sets up a malicious shared library designed to be loaded by GLib to translate system messages to the made-up character set “PWNKIT”.
-
The exploit calls
pkexec
with a NULL argument list (this bit is important, since we need the length of the argument list to be 0 — so, not even to contain the name ofpkexec
itself) but with a correctly set up (albeit malicious) environment viaexecve()
. Importantly, the first “variable” in the environment is actually the name of the (invalid) executable in theGCONV_PATH=.
directory. -
Polkit just falls through the loop that it would normally use to walk through the passed-in arguments. This causes what would be pointing to an executable name to instead point to the first environment variable that’s passed into
execve()
, which happens to be string pwnkit. -
Polkit looks up the malicious executable, finds it in
GCONV_PATH=./pwnkit
(because we set the PATH to that directory), and then tries to replace the executable name with this full path. Except that it’s still writing to the first element of the environment, which causes pwnkit to be replaced byGCONV_PATH=./pwnkit
. -
Polkit the proceeds to sanitize its environment. When it comes to the invalid
SHELL
variable this sanitization fails and Polkit throws an error and dies. But! Before dying, Polkit tries to print the error using a GLib function that dutifully attempts to translate the message into the “PWNKIT” character set. To figure out how to do this, modules are loaded fromGCONV_PATH
… And we’ve defined a malicious module to do this that cleans up the exploit files and spawns a root shell (sincepkexec
is SUID root).