
# Bildungs-PoC und Analyse der lokalen Privilege-Escalation-Schwachstelle CVE-2021-4034 (PwnKit) in polkits pkexec, mit einem Docker-basierten Labor für praktische Übungen zu Exploitation und Defense.
🔗 Originalprojekt: berdav/CVE-2021-4034
Dieses Projekt ist eine auf Basis des Originals für Bildungszwecke analysierte und modifizierte Version.
MIT-Lizenz konform | White Hat School Bildungsaufgabe
CVE-2021-4034 ist eine Schwachstelle zur lokalen Rechteausweitung im Linux-policykit-1 (PolicyKit). Ein normaler Benutzer kann pkexec ohne Argumente ausführen und dabei eine Schwachstelle in der Prozessspeicherstruktur ausnutzen. Dadurch wird glib dazu gebracht, Umgebungsvariablen-Strings, die eigentlich hätten herausgefiltert werden müssen, erneut zu referenzieren und eine bösartige .so-Datei zu laden – auf diese Weise können root-Rechte erlangt werden.
⚠️ Nur für Bildungszwecke: Dieser Code darf ausschließlich auf modifizierten Systemen verwendet werden.
Ein Einsatz zum Angriff auf reale Systeme kann rechtliche Konsequenzen haben.
| Kategorie | Inhalt |
|---|---|
| CVE-ID | CVE-2021-4034 |
| Name der Schwachstelle | PwnKit |
| Betroffene Versionen | Alle Versionen von polkit vor 0.105, auf die kein Patch angewendet wurde (Testumgebung: policykit-1 0.105-26ubuntu1 unter Ubuntu 20.04) |
| Schwachstellentyp | Local Privilege Escalation (LPE) |
| Schweregrad | Critical (CVSS 7.8) |
| Patch-Version | policykit-1 >= 0.105-26ubuntu1.1 |
| Entdeckungsdatum | Juni 2021 (veröffentlicht im Januar 2022) |
Man könnte leicht annehmen, es handle sich um ein reines Ubuntu-Problem, doch da es sich um einen Logikfehler in pkexec selbst handelt, sind die meisten Distributionen betroffen, die polkit verwenden. Da die Docker-Testumgebung Ubuntu 20.04 ist, wurde diese Version in der obigen Tabelle mit aufgeführt.
Bei der ersten Analyse dachte ich, es sei ein Problem, das dadurch entsteht, dass Umgebungsvariablen nicht validiert werden. Als ich mir jedoch den Quellcode zusammen mit dem Patch-Commit ansah, wurde klar, dass die Reihenfolge eine etwas andere ist. Die eigentliche Ursache liegt woanders; das Umgebungsvariablen-Problem ist eher eine Folge dieser Ursache. Im Folgenden ist dieser Ablauf in der Reihenfolge der Ursachen zusammengefasst.
pkexec ist ein SUID-root-Programm, das über PolicyKit eine Rechteausweitung anfordert.
# Beispiel: Befehl mit root-Rechten ausführen
pkexec /bin/id
pkexec systemctl restart service
Es wird verwendet, wenn ein normaler Benutzer bestimmte Aufgaben mit Administratorrechten ausführen möchte.
argc == 0 nicht als SonderfallDie main()-Funktion von pkexec validiert in dem Teil, der die Befehlszeilenargumente verarbeitet, den Fall, dass das Programm ohne ein einziges Argument ausgeführt wird (argc == 0), nicht. Das ist der eigentliche Ausgangspunkt dieser Schwachstelle.
argv = {"pkexec", "Befehl", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0Wenn argc 0 ist, bleibt in der argv-Liste nur ein NULL als Abschluss übrig. Die interne Logik von pkexec versucht jedoch auch in dieser Situation, auf das nicht vorhandene argv[1] lesend und schreibend zuzugreifen. Das Problem ist, dass Linux beim Ausführen eines Prozesses das argv-Array und das envp-Array (Umgebungsvariablen) im Speicher direkt nebeneinander platziert. Dadurch zeigt das außerhalb des gültigen Bereichs liegende argv[1] tatsächlich auf envp[0], also auf die erste Umgebungsvariable.
Normale Situation: argv = [ "pkexec" | NULL ]
Angriffssituation: argv = [ NULL ] ← argc = 0
↑
Zugriff auf nicht vorhandenes argv[1]
↓
liest und schreibt envp[0] direkt dahinter im Speicher (out-of-bounds)
Warum ist das gefährlich?
GCONV_PATH oder LD_PRELOAD, da diese als unsicher eingestuft werden.argc < 1 sofort beendet. (Entspricht CWE-125 Out-of-Bounds Read und CWE-787 Out-of-Bounds Write)📌 Zusammengefasst: Die fehlende Validierung der Umgebungsvariablen ist die „Bedingung, unter der der Angriff funktioniert"; die eigentliche Schwachstelle (Root Cause) ist, dass pkexec den Fall argc == 0 nicht behandelt. Punkt 3 unten ist die Folge dieser Ursache.
Dank der unter Punkt 2 beschriebenen OOB-Operation wird dieser String beim Initialisierungsprozess von glib durch pkexec unvalidiert erneut verwendet.
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // hätte eigentlich von ld.so herausgefiltert werden müssen
"CHARSET=PWNKIT", // nicht existierende Kodierung
};
execve("/usr/bin/pkexec", args, env); // argv wird leer gelassen, um argc=0 zu erzeugen
Problem:
Wichtig hierbei ist, dass glib selbst nichts falsch gemacht hat. Wenn GCONV_PATH gesetzt ist, ist die Suche nach dem Converter in diesem Pfad das normale Verhalten von glib. Das Problem ist, dass pkexec den Secure-Execution-Zustand (den Zustand, in dem gefährliche Umgebungsvariablen entfernt wurden) bereits zerstört hat – glib hat lediglich normal funktioniert, und genau dieses normale Verhalten wird ausgenutzt.
CHARSET-Umgebungsvariable prüfen
CHARSET=PWNKIT
Converter-Definition in der Datei gconv-modules suchen
module UTF-8// PWNKIT// pwnkit 1
.so-Datei über GCONV_PATH laden
GCONV_PATH=. → pwnkit.so im aktuellen Verzeichnis suchen
Initialisierungsfunktion der .so-Datei wird automatisch ausgeführt
// pwnkit.c – wird beim Laden der .so-Datei automatisch ausgeführt
void gconv_init(void *step)
{
setuid(0); // root-Rechte erlangen
setgid(0);
execve("/bin/sh"); // root-Shell starten!
}
Man ist versucht, gconv_init als „Konstruktorfunktion" zu bezeichnen, doch streng genommen unterscheidet sie sich von __attribute__((constructor)) in C. Genauer gesagt handelt es sich um eine im gconv-Modulinterface definierte Initialisierungsfunktion, die glib nach dem Laden der .so-Datei per dlopen explizit aufruft.