
Eingehende Analyse von CVE-2021-3560, einer lokalen Schwachstelle zur Privilegieneskalation in Linux PolKit. Umfasst Root-Cause-Analyse, Exploit-Mechanismen und eine Demonstration der Race Condition, die zu Root-Zugriff führt.
[toc]
Schwachstellen-ID: CVE-2021-3560
Schwachstellenbewertung:
Betroffenes Produkt: Linux PolKit (polkitd)
Betroffene Systeme: Eingeführt in Quellcode 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
Voraussetzungen für die Ausnutzung: lokaler Linux-Zugriff; dbus + polkitd vorhanden
Auswirkung der Ausnutzung: lokale Privilegieneskalation
Quellcode-Beschaffung:
apt source accountsserviceapt source dbusDas Standard-Ubuntu-Image ubuntu-20.04.2 ist ausreichend, um die Schwachstelle zu reproduzieren: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso
Zuerst betrachten wir den problematischen Code
/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; //某些原因失败之后设置error位
}
else
{
··· ···//执行成功的数据处理
}
··· ···
}
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 被初始化为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, //回调函数
&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, //回调函数
&data);
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); //等待总线那边执行完毕,uid、pid都处理完毕或异常
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; //无论如何都是 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;
}
In der Funktion polkit_system_bus_name_get_creds_sync wird die Authentifizierung bestimmter Anfragen durchgeführt (die konkreten Szenarien und die Logik werden später im Detail erläutert). Über den org.freedesktop.DBus-Bus werden nacheinander die Methoden GetConnectionUnixUser und GetConnectionUnixProcessID aufgerufen. Diese beiden Methoden werden vom org.freedesktop.DBus-Bus bereitgestellt und dienen dazu, die PID und die Benutzer-UID des anfragenden Prozesses zu ermitteln. Anschließend werden sie an die Callback-Funktion on_retrieved_unix_uid_pid übergeben, die die zurückgegebenen Daten verarbeitet. Danach wird blockierend gewartet, bis der Prozess auf der Bus-Seite abgeschlossen ist.
In der Funktion on_retrieved_unix_uid_pid ist zu erkennen, dass anhand des von der Bus-Seite zurückgegebenen Ausführungsergebnisses festgelegt wird, ob ein Fehler aufgetreten ist (error?), und anschließend das Fehlerflag gesetzt wird. Bei erfolgreicher Ausführung werden ordnungsgemäß die Benutzer-UID oder die Prozess-PID zurückgegeben. In der Funktion polkit_system_bus_name_get_creds_sync hingegen blockiert der Prozess, bis die Bus-Seite ein Ergebnis zurückgibt (als Kriterium für die abgeschlossene Verarbeitung gilt: sowohl UID als auch PID wurden verarbeitet oder es ist ein Fehler aufgetreten). Logisch betrachtet kann die Bus-Seite also drei Ergebnisse liefern: eine Benutzer-UID von 0 (privilegierter Benutzer), eine Benutzer-UID ungleich 0 (normaler Benutzer) oder einen Fehler. In der anschließenden Verarbeitung wird der Fehlerfall jedoch nicht behandelt: Das an die obere Ebene zurückzugebende out_uid wird direkt auf das von der Bus-Seite zurückgegebene data.uid gesetzt und ret wird direkt auf TRUE gesetzt:
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))
g_main_context_iteration (tmp_context, TRUE); //等待总线那边执行完毕,uid、pid都处理完毕或异常
if (out_uid)
*out_uid = data.uid;
if (out_pid)
*out_pid = data.pid;
ret = TRUE; //无论如何都是 TRUE?
Es wird jedoch ein Fall übersehen: Wenn auf der DBUS-Bus-Seite ein Fehler auftritt, setzt die Callback-Funktion lediglich das Fehlerflag und kehrt zurück, ohne die Daten in data zu verarbeiten. Da data mit 0 initialisiert wird, wird out_uid in der Folge direkt auf 0 gesetzt – also auf den privilegierten Benutzer.
Wo wird diese fehlerhafte Funktion polkit_system_bus_name_get_creds_sync nun verwendet?
Der von der Schwachstelle betroffene Prozess ist der polkitd-Prozess:
polkit ist ein Toolkit auf Anwendungsebene, das durch die Definition und Überprüfung von Berechtigungsregeln die Kommunikation zwischen Prozessen unterschiedlicher Priorität ermöglicht: Die Kontrollentscheidungen sind in einem einheitlichen Rahmen zentralisiert und legen fest, ob ein Prozess mit niedriger Priorität auf einen Prozess mit hoher Priorität zugreifen darf.
Kurz gesagt handelt es sich um einen im Hintergrund laufenden Root-Prozess, der über Interprozesskommunikation eine Berechtigungsentscheidung trifft, wenn andere Prozesse mit niedrigen Rechten auf Funktionen zugreifen möchten, die von Prozessen mit hohen Rechten bereitgestellt werden. Da dieser Prozess neu startet, sobald gdb daran angehängt wird, können wir nur auf Quellcode-Ebene analysieren, um den Auslösungspfad der verwundbaren Funktion zu finden:
polkitd wurde in großem Umfang mit dem GIO DBUS-Framework für Interprozesskommunikation entwickelt. Es lohnt sich, vorab einen Blick in das Handbuch zu werfen und sich auf einige wichtige Funktionen zu konzentrieren.
Als Nächstes analysieren wir den Auslösungspfad der Schwachstelle in polkitd:
Zunächst 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);