
C-Exploit für CVE-2021-3560, eine Authentifizierungsumgehung in polkit, die es unprivilegierten Benutzern ermöglicht, über DBus ein privilegiertes Konto zu erstellen, mit einer detaillierten technischen Dokumentation.
ein zuverlässiger C-basierter Exploit für CVE-2021-3560.
Gestern bin ich auf diesen Blogbeitrag von Kevin Backhouse gestoßen (der diese Schwachstelle entdeckt hat), ich habe die im Blogbeitrag bereitgestellten Bash-Befehle ausprobiert und zu meiner Überraschung funktionierten sie auf meiner Kali Linux-Box!
CVE-2021-3560 ist ein Authentifizierungsumgehung auf polkit, die es einem unprivilegierten Benutzer ermöglicht, privilegierte Methoden über DBus aufzurufen. Der PoC nutzt diesen Fehler aus, um zwei privilegierte Methoden von accountsservice aufzurufen (CreateUser und SetPassword), was es uns ermöglicht, einen privilegierten Benutzer zu erstellen und dann ein Passwort zu setzen.
polkit prüft, ob der Aufrufer berechtigt ist, eine solche Methode aufzurufen. Dazu wird zunächst die Benutzer-ID des Aufrufers überprüft. Ist diese Null, wird der Aufrufer als root angenommen und die Aktion ohne Authentifizierungsanforderung erlaubt. Andernfalls wird nach dem Passwort des Benutzers gefragt.
polkit_system_bus_name_get_creds_sync() ruft zwei Methoden auf, um die UID und PID des Aufrufers zu erhalten: GetConnectionUnixUser und GetConnectionUnixProcessID. Das Ergebnis dieser Aufrufe wird in die data-Struktur vom Typ AsyncGetBusNameCredsData geschrieben (diese Struktur wird auf 0 initialisiert) durch die Callback-Funktion on_retrieved_unix_uid_pid(), und polkit_system_bus_name_get_creds_sync() blockiert, während sie darauf wartet, dass die Callback-Funktion einen Fehler oder die UID und PID setzt.
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, }; // intialize to 0
GDBusConnection *connection = NULL;
GMainContext *tmp_context = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, cancellable, error);
if (connection == NULL)
goto out;
data.error = error;
tmp_context = g_main_context_new ();
g_main_context_push_thread_default (tmp_context);
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 funtion
&data); // data is passed to the callback function along with the reply from the method
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 funtion
&data); // data is passed to the callback function along with the reply from the method
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // block while on_retrieved_unix_uid_pid() is not called yet
g_main_context_iteration (tmp_context, TRUE);
Die Callback-Funktion on_retrieved_unix_uid_pid() wird nach jedem Methodenaufruf aufgerufen, um die Antwort (UID und PID) abzurufen oder einen Fehler zu setzen. Diese Funktion ruft g_dbus_connection_call_finish() auf, um die Antwort abzurufen, wenn ein Fehler aufgetreten ist. Dann setzt sie data.caught_error auf TRUE und kehrt zurück (data.uid und data.pid sind weiterhin auf 0 gesetzt). Andernfalls weist sie den abgerufenen Wert (UID oder PID) data.uid oder data.pid zu (abhängig davon, welcher Wert abgerufen wurde) und kehrt zurück.
static void
on_retrieved_unix_uid_pid (GObject *src, // connection
GAsyncResult *res, // Async result object
gpointer user_data) // data paramter passed from previous function
{
AsyncGetBusNameCredsData *data = user_data;
GVariant *v;
v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
data->caught_error ? NULL : data->error); // finish and get the reply
if (!v) // error ??
{
data->caught_error = TRUE;
}
else
{
guint32 value;
g_variant_get (v, "(u)", &value); // unpack the reply, get UINT32 (u)
g_variant_unref (v);
if (!data->retrieved_uid) // GetConnectionUnixUser method
{
data->retrieved_uid = TRUE;
data->uid = value;
}
else
{
g_assert (!data->retrieved_pid); // GetConnectionUnixProcessID method
data->retrieved_pid = TRUE;
data->pid = value;
}
}
}
Die Methoden GetConnectionUnixUser und GetConnectionUnixProcessID geben die UID und PID zurück, falls gefunden (der Aufruferprozess ist noch mit dem Bus verbunden), oder einen Fehler, falls ein Fehler aufgetreten ist (z.B. der Aufruferprozess wurde beendet).
Sobald data.uid und data.pid gesetzt sind oder data.caught_error gesetzt ist, fährt die Funktion polkit_system_bus_name_get_creds_sync() fort. Hier liegt die Schwachstelle: polkit_system_bus_name_get_creds_sync() gibt keinen Fehler zurück, wenn data.caught_error gesetzt ist, sondern setzt stattdessen den Wert von data.uid in out_uid und gibt TRUE zurück (selbst wenn data.caught_error gesetzt ist). out_pid ist ein Zeiger auf eine guint32-Variable, die an polkit_system_bus_name_get_creds_sync() übergeben wird, wenn sie von polkit_system_bus_name_get_user_sync() aufgerufen wird:
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid, // pointer
guint32 *out_pid, // NULL
GCancellable *cancellable,
GError **error)
{
[snip]
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // wait for the callback function to handle reply
g_main_context_iteration (tmp_context, TRUE);
if (out_uid) // TRUE
*out_uid = data.uid; // set it even if there is an error [!]
if (out_pid) // FALSE
*out_pid = data.pid; // set it even if there is an error [!]
ret = TRUE; // return TRUE even if there is an error [!]
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;
Ausnutzung: