
Exploit en C para CVE-2021-3560, una evasión de autenticación en polkit que permite a usuarios sin privilegios crear una cuenta privilegiada mediante DBus, con un análisis técnico detallado.
un exploit confiable basado en C para CVE-2021-3560.
Ayer me topé con este artículo de blog de Kevin Backhouse (descubridor de esta vulnerabilidad), probé los comandos bash proporcionados en el artículo y para mi sorpresa funcionaron en mi máquina Kali Linux!
CVE-2021-3560 es una omisión de autenticación en polkit, que permite a un usuario no privilegiado llamar a métodos privilegiados usando DBus. El PoC explota este bug para llamar a 2 métodos privilegiados proporcionados por accountsservice (CreateUser y SetPassword), lo que nos permite crear un usuario privilegiado y luego establecerle una contraseña.
polkit verifica si el llamante está autorizado para llamar a dicho método. Lo hace comprobando primero el id de usuario del llamante; si es cero, se asume que el llamante es root y la acción se permite sin solicitar autenticación; de lo contrario, solicita la contraseña del usuario.
La función polkit_system_bus_name_get_creds_sync() invoca a 2 métodos para obtener el UID y PID del llamante GetConnectionUnixUser y GetConnectionUnixProcessID. El resultado de estas llamadas se escribe en la estructura data de tipo AsyncGetBusNameCredsData (esta estructura se inicializa a 0) por la función de callback on_retrieved_unix_uid_pid(), y polkit_system_bus_name_get_creds_sync() se bloquea mientras espera a que la función de callback establezca un error o el UID y 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, }; // 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);
la función de callback on_retrieved_unix_uid_pid() se invoca después de cada llamada al método para recuperar la respuesta (UID y PID) o establecer un error. Esta función llama a g_dbus_connection_call_finish() para recuperar la respuesta; si ocurre un error, entonces establece data.caught_error a TRUE y retorna (data.uid y data.pid siguen siendo 0). De lo contrario, asigna el valor recuperado (UID o PID) a data.uid o data.pid (dependiendo del valor recuperado) y luego retorna.
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;
}
}
}
Los métodos GetConnectionUnixUser y GetConnectionUnixProcessID devolverán el UID y PID si se encuentran (el proceso llamante sigue conectado al bus), o un error si ocurrió un error (por ejemplo: el proceso llamante fue eliminado).
Una vez que data.uid y data.pid están establecidos o que data.caught_error está establecido, la función polkit_system_bus_name_get_creds_sync() continuará y aquí es donde existe la vulnerabilidad: polkit_system_bus_name_get_creds_sync() no devuelve un error si data.caught_error está establecido; en su lugar, asigna cualquier valor que esté en data.uid a out_uid y devuelve TRUE (incluso si data.caught_error está establecido). out_pid es un puntero a una variable guint32 pasada a polkit_system_bus_name_get_creds_sync() cuando es llamada por polkit_system_bus_name_get_user_sync():
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;
explotación: