
PoC educativo e analisi della vulnerabilità di escalation dei privilegi locale CVE-2021-4034 (PwnKit) in pkexec di polkit, con un laboratorio basato su Docker per la pratica pratica di sfruttamento e difesa.
🔗 Progetto originale: berdav/CVE-2021-4034
Questo progetto è una versione analizzata e modificata a scopo didattico basata sull'originale.
Conforme alla Licenza MIT | Compito formativo White Hat School
CVE-2021-4034 è una vulnerabilità di escalation dei privilegi locale in policykit-1 (PolicyKit) su Linux. Un utente normale può eseguire pkexec senza argomenti, sfruttando una debolezza nella struttura della memoria del processo, facendo sì che glib faccia nuovamente riferimento a stringhe di variabili d'ambiente che avrebbero dovuto essere filtrate, e caricando un file .so malevolo, ottenendo così privilegi di root.
⚠️ Solo a scopo didattico: questo codice dovrebbe essere usato solo su sistemi modificati.
Usarlo per attaccare sistemi reali può comportare responsabilità legali.
| Elemento | Contenuto |
|---|---|
| ID CVE | CVE-2021-4034 |
| Nome vulnerabilità | PwnKit |
| Versioni interessate | Tutte le versioni di polkit precedenti alla patch 0.105 (ambiente di test: policykit-1 0.105-26ubuntu1 su Ubuntu 20.04) |
| Tipo vulnerabilità | Escalation di privilegi locale (LPE) |
| Gravità | Critica (CVSS 7.8) |
| Versione con patch | policykit-1 >= 0.105-26ubuntu1.1 |
| Data di scoperta | Giugno 2021 (divulgata a gennaio 2022) |
È facile fraintendere e pensare che sia solo un problema di Ubuntu, ma essendo un difetto logico di pkexec stesso, la maggior parte delle distribuzioni che usano polkit sono vulnerabili. Nell'ambiente di test Docker con Ubuntu 20.04 ho indicato quella versione nella tabella.
Inizialmente pensavo fosse "un problema dovuto alla mancata validazione delle variabili d'ambiente", ma dopo aver visto il codice sorgente e il commit della patch ho capito che l'ordine è diverso. La causa reale è altrove, e il problema delle variabili d'ambiente è più una conseguenza. Di seguito è riportato il flusso in ordine di causa.
pkexec è un programma SUID-root che richiede l'escalation dei privilegi tramite PolicyKit.
# Esempio: eseguire un comando con privilegi di root
pkexec /bin/id
pkexec systemctl restart servizio
}
Viene usato quando un utente normale deve eseguire un'operazione con privilegi amministrativi.
---
### **2. La vera causa principale: pkexec non gestisce il caso `argc == 0`**
La funzione `main()` di pkexec, nella parte che gestisce gli argomenti della riga di comando, **non verifica il caso in cui venga eseguito senza alcun argomento (argc == 0).** Questo è il vero punto di partenza della vulnerabilità.
- Esecuzione normale: `argv = {"pkexec", "comando", NULL}` → argc >= 1
- Esecuzione d'attacco: `execve("/usr/bin/pkexec", {NULL}, env)` → **argc == 0**
Se argc è 0, nella lista argv rimane solo un NULL che ne indica la fine. Tuttavia la logica interna di pkexec tenta di leggere e scrivere in un `argv[1]` inesistente. Il problema è che Linux, quando esegue un processo, dispone l'array `argv` e l'array `envp` (variabili d'ambiente) **uno accanto all'altro** in memoria. Quindi `argv[1]`, fuori dai limiti, in realtà punta a `envp[0]`, cioè alla prima variabile d'ambiente.
Situazione normale: argv = [ "pkexec" | NULL ] Situazione d'attacco: argv = [ NULL ] ← argc = 0 ↑ Accesso a argv[1] inesistente ↓ Legge e scrive su envp[0] immediatamente dopo in memoria (out-of-bounds)
**Perché questo è pericoloso:**
- Normalmente ld.so, prima dell'esecuzione di un programma SUID (pkexec), rimuove variabili d'ambiente pericolose come `GCONV_PATH`, `LD_PRELOAD` in quanto non sicure.
- Tuttavia, a causa dell'OOB di cui sopra, quelle stringhe già rimosse non vengono "ripristinate come variabili d'ambiente", ma **il puntatore argv** ricomincia a farvi riferimento. Cioè il valore non rinasce, ma si ristabilisce un collegamento del puntatore a quel valore.
- Il commit della patch effettiva corregge questa parte aggiungendo un controllo per termina immediatamente se `argc < 1`. (CWE-125 out-of-bounds read, CWE-787 out-of-bounds write)
> 📌 In sintesi: **la mancata validazione delle variabili d'ambiente è la "condizione che permette l'attacco", mentre la vera vulnerabilità (root cause) è che pkexec non gestisce il caso argc == 0**. Il punto 3 di seguito è una conseguenza di questa causa.
---
### **3. Conseguenza: stringhe che dovevano essere filtrate diventano nuovamente accessibili**
Grazie al comportamento OOB descritto al punto 2, durante l'inizializzazione di glib da parte di pkexec, queste stringhe vengono riutilizzate **senza essere state verificate**.
```c
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // originariamente ld.so avrebbe dovuto filtrarlo
"CHARSET=PWNKIT", // encoding inesistente
};
execve("/usr/bin/pkexec", args, env); // argv vuoto per creare argc=0
Problema:
Qui è importante notare che glib di per sé non ha colpe. Se GCONV_PATH è impostata, cercare i convertitori in quel percorso è il comportamento normale di glib. Il problema è che pkexec ha già infranto lo stato di esecuzione sicura (con le variabili d'ambiente pericolose rimosse) — glib ha semplicemente fatto il suo dovere, ma quel comportamento normale viene sfruttato.
Verifica della variabile d'ambiente CHARSET
CHARSET=PWNKIT
Ricerca della definizione del convertitore in gconv-modules
module UTF-8// PWNKIT// pwnkit 1
Caricamento del file .so da GCONV_PATH
GCONV_PATH=. → cerca pwnkit.so nella directory corrente
Esecuzione automatica della funzione di inizializzazione del file .so
// pwnkit.c - eseguita automaticamente quando il file .so viene caricato
void gconv_init(void *step)
{
setuid(0); // ottenimento privilegi di root
setgid(0);
execve("/bin/sh"); // esecuzione di una shell root!
}
gconv_init viene spesso chiamata "funzione costruttore", ma tecnicamente è diversa da __attribute__((constructor)) in C. Più precisamente è una funzione di inizializzazione definita nell'interfaccia del modulo gconv, che glib chiama esplicitamente dopo il dlopen del .so.