
Analisi approfondita di CVE-2021-3560, una vulnerabilità di escalation dei privilegi locali in Linux PolKit. Include l'analisi della causa principale, le meccaniche dell'exploit e una dimostrazione della condizione di gara che porta all'accesso root.
[toc]
ID vulnerabilità: CVE-2021-3560
Punteggio vulnerabilità:
Prodotto vulnerabile: linux PolKit (polkitd)
Versione interessata: introdotta nel codice sorgente 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
Condizioni di sfruttamento: linux locale; dbus + polkitd presenti
Effetto dello sfruttamento: elevazione dei privilegi locali
Ottenere il codice sorgente:
apt source accountsserviceapt source dbusL'immagine predefinita di ubuntu-20.04.2 può essere riprodotta: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso
Prima esaminiamo il codice problematico
/polkit-0.113/src/polkit/polkitsystembusname.c : 388 : polkit_system_bus_name_get_creds_sync
static void
on_retrieved_unix_uid_pid (GObject *src,
GAsyncResult *res,
gpointer user_data)
{
AsyncGetBusNameCredsData *data = user_data;
GVariant *v;
v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
data->caught_error ? NULL : data->error);
if (!v)
{
data->caught_error = TRUE; // imposta il flag di errore dopo un fallimento per qualche motivo
}
else
{
··· ···// elaborazione dei dati in caso di successo
}
··· ···
}
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid,
guint32 *out_pid,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
AsyncGetBusNameCredsData data = { 0, }; // data viene inizializzato a 0
··· ···
g_dbus_connection_call (connection,
"org.freedesktop.DBus", /* name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"GetConnectionUnixUser", /* method */
g_variant_new ("(s)", system_bus_name->name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
on_retrieved_unix_uid_pid, // callback
&data);
g_dbus_connection_call (connection,
"org.freedesktop.DBus", /* name */
"/org/freedesktop/DBus", /* object path */
"org.freedesktop.DBus", /* interface name */
"GetConnectionUnixProcessID", /* method */
g_variant_new ("(s)", system_bus_name->name),
G_VARIANT_TYPE ("(u)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
cancellable,
on_retrieved_unix_uid_pid, // callback
&data);
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); // aspetta che il bus termini l'esecuzione, uid e pid siano stati elaborati o che si sia verificato un errore
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; // indipendentemente da tutto, TRUE?
out:
if (tmp_context)
{
g_main_context_pop_thread_default (tmp_context);
g_main_context_unref (tmp_context);
}
if (connection != NULL)
g_object_unref (connection);
return ret;
}
Nella funzione polkit_system_bus_name_get_creds_sync, essa autentica l'identità per certe richieste (la logica specifica verrà dettagliata in seguito). Chiama prima GetConnectionUnixUser e poi GetConnectionUnixProcessID tramite il bus org.freedesktop.DBus. Questi due metodi sono forniti dal bus org.freedesktop.DBus e servono per ottenere il PID e l'UID del processo richiedente. I risultati vengono passati alla funzione callback on_retrieved_unix_uid_pid per l'elaborazione. Quindi la funzione si blocca in attesa che il bus termini l'esecuzione.
Nella funzione on_retrieved_unix_uid_pid si può vedere che, in base al risultato restituito dal bus, imposta se si è verificato un errore (error?) e imposta il flag di errore; se l'esecuzione ha successo, restituisce correttamente l'UID utente o il PID del processo. Ma nella funzione polkit_system_bus_name_get_creds_sync, dopo aver atteso il risultato dal bus (il criterio per considerare completata l'elaborazione è che sia UID che PID siano stati elaborati o che si sia verificato un errore). In altre parole, logicamente ci sono tre possibili risultati dal bus: ottenere UID utente 0 (utente privilegiato); ottenere UID utente diverso da 0 (utente normale); errore. Tuttavia, nell'elaborazione successiva, non viene gestito l'errore: il out_uid da restituire al livello superiore viene impostato direttamente al data.uid restituito dal bus, e ret viene impostato a TRUE:
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); // aspetta che il bus termini l'esecuzione, uid e pid siano stati elaborati o che si sia verificato un errore
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; // indipendentemente da tutto, TRUE?
Ma viene trascurato un caso: se il bus DBUS restituisce un errore, la callback imposta il flag di errore e ritorna senza elaborare i dati data, che però sono inizializzati a 0. Ciò fa sì che successivamente out_uid venga impostato a 0, cioè utente privilegiato.
Allora, dove viene utilizzata la funzione difettosa polkit_system_bus_name_get_creds_sync?
Il processo in cui si trova il punto di vulnerabilità è il processo polkitd:
polkit è un set di strumenti a livello applicativo che, mediante la definizione e la verifica di regole di autorizzazione, realizza la comunicazione tra processi con priorità diverse: le decisioni di controllo sono concentrate in un quadro unificato, determinando se un processo a bassa priorità ha diritto di accesso a un processo ad alta priorità.
In breve, è un processo root in esecuzione in background che, tramite comunicazione interprocesso, decide l'autorizzazione quando un processo a bassi privilegi tenta di accedere alle funzionalità fornite da un processo ad alti privilegi. Poiché questo processo si riavvia se si tenta di collegarlo con gdb, possiamo analizzarlo solo a livello di codice sorgente per trovare il percorso di innesco della funzione vulnerabile:
polkitd utilizza ampiamente il framework di comunicazione interprocesso gio DBUS. È possibile consultare il manuale per conoscere in anticipo alcune funzioni importanti.
Ora analizziamo il percorso di innesco della vulnerabilità in polkitd:
Prima di tutto, il main:
polkit-0.113\src\polkitbackend\polkitd.c : 155 : main
int
main (int argc,
char **argv)
{
··· ···
··· ···
loop = g_main_loop_new (NULL, FALSE);
sigint_id = g_unix_signal_add (SIGINT,
on_sigint,
NULL);
name_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
"org.freedesktop.PolicyKit1", // registra un nome di bus
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
(opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
on_bus_acquired, // callback quando si accede al bus
on_name_acquired,
on_name_lost,
NULL,
NULL);