
Exploit C pour CVE-2021-3560, un contournement de l'authentification dans polkit permettant aux utilisateurs non privilégiés de créer un compte privilégié via DBus, avec une analyse technique détaillée.
un exploit fiable en C pour CVE-2021-3560.
Hier, je suis tombé sur ce billet de blog de Kevin Backhouse (qui a découvert cette vulnérabilité), j'ai essayé les commandes bash fournies dans le billet et, à ma grande surprise, cela a fonctionné sur ma machine Kali Linux !
CVE-2021-3560 est un contournement d'authentification sur polkit, qui permet à un utilisateur non privilégié d'appeler des méthodes privilégiées via DBus. Le PoC exploite ce bogue pour appeler 2 méthodes privilégiées fournies par accountsservice (CreateUser et SetPassword), ce qui nous permet de créer un utilisateur privilégié puis de lui définir un mot de passe.
polkit vérifie si l'appelant est autorisé à appeler une telle méthode ; il le fait en vérifiant d'abord l'identifiant utilisateur (UID) de l'appelant ; s'il est nul, l'appelant est considéré comme root et l'action est autorisée sans demande d'authentification ; sinon, il demande le mot de passe de l'utilisateur.
polkit_system_bus_name_get_creds_sync() invoque 2 méthodes pour obtenir l'UID et le PID de l'appelant GetConnectionUnixUser et GetConnectionUnixProcessID. Le résultat de ces appels est écrit dans la structure data de type AsyncGetBusNameCredsData (cette structure est initialisée à 0) par la fonction de rappel on_retrieved_unix_uid_pid(), et polkit_system_bus_name_get_creds_sync() bloque en attendant que la fonction de rappel définisse une erreur ou l'UID et le PID.
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, }; // initialisé à 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, // fonction de rappel
&data); // data est passée à la fonction de rappel avec la réponse de la méthode
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, // fonction de rappel
&data); // data est passée à la fonction de rappel avec la réponse de la méthode
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // bloque tant que on_retrieved_unix_uid_pid() n'a pas été appelée
g_main_context_iteration (tmp_context, TRUE);
La fonction de rappel on_retrieved_unix_uid_pid() est invoquée après chaque appel de méthode pour récupérer la réponse (UID et PID) ou définir une erreur. Cette fonction appelle g_dbus_connection_call_finish() pour récupérer la réponse ; si une erreur se produit, alors elle définit data.caught_error à TRUE et retourne (data.uid et data.pid restent à 0). Sinon, elle assigne la valeur récupérée (UID ou PID) à data.uid ou data.pid (selon la valeur récupérée) puis retourne.
static void
on_retrieved_unix_uid_pid (GObject *src, // connection
GAsyncResult *res, // objet résultat asynchrone
gpointer user_data) // paramètre data passé de la fonction précédente
{
AsyncGetBusNameCredsData *data = user_data;
GVariant *v;
v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
data->caught_error ? NULL : data->error); // termine et récupère la réponse
if (!v) // erreur ?
{
data->caught_error = TRUE;
}
else
{
guint32 value;
g_variant_get (v, "(u)", &value); // dépaquète la réponse, obtient UINT32 (u)
g_variant_unref (v);
if (!data->retrieved_uid) // méthode GetConnectionUnixUser
{
data->retrieved_uid = TRUE;
data->uid = value;
}
else
{
g_assert (!data->retrieved_pid); // méthode GetConnectionUnixProcessID
data->retrieved_pid = TRUE;
data->pid = value;
}
}
}
Les méthodes GetConnectionUnixUser et GetConnectionUnixProcessID retourneront l'UID et le PID s'ils sont trouvés (le processus appelant est encore connecté au bus), ou une erreur si une erreur se produit (par exemple : processus appelant tué).
Une fois que data.uid et data.pid sont définis ou que data.caught_error est défini, la fonction polkit_system_bus_name_get_creds_sync() continue son exécution et c'est là que se trouve la vulnérabilité. polkit_system_bus_name_get_creds_sync() ne retourne pas d'erreur si data.caught_error est défini ; au lieu de cela, elle affecte la valeur présente dans data.uid à out_uid et retourne TRUE (même si data.caught_error est défini). out_pid est un pointeur vers une variable guint32 passée à polkit_system_bus_name_get_creds_sync() lorsqu'elle est appelée par polkit_system_bus_name_get_user_sync() :
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid, // pointeur
guint32 *out_pid, // NULL
GCancellable *cancellable,
GError **error)
{
[snip]
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // attend que la fonction de rappel traite la réponse
g_main_context_iteration (tmp_context, TRUE);
if (out_uid) // TRUE
*out_uid = data.uid; // définit même s'il y a une erreur [!]
if (out_pid) // FALSE
*out_pid = data.pid; // définit même s'il y a une erreur [!]
ret = TRUE; // retourne TRUE même s'il y a une erreur [!]
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;
Exploitation :