
C эксплойт для CVE-2021-3560, обход аутентификации в polkit, позволяющий непривилегированным пользователям создать привилегированную учетную запись через DBus, с подробным техническим описанием.
Надёжный эксплойт на C для CVE-2021-3560.
Вчера я наткнулся на эту публикацию в блоге Кевина Бэкхауса (обнаружившего эту уязвимость), я попробовал bash-команды, приведённые в статье, и, к моему удивлению, они сработали на моей машине с Kali Linux!
CVE-2021-3560 — это обход аутентификации в polkit, который позволяет непривилегированному пользователю вызывать привилегированные методы через DBus. PoC использует эту ошибку для вызова двух привилегированных методов, предоставляемых accountsservice (CreateUser и SetPassword), что позволяет создать привилегированного пользователя, а затем установить ему пароль.
polkit проверяет, авторизован ли вызывающий для вызова такого метода. Сначала он проверяет UID вызывающего: если он равен нулю, то вызывающий считается root, и действие разрешается без запроса аутентификации; в противном случае запрашивается пароль пользователя.
Функция polkit_system_bus_name_get_creds_sync() вызывает два метода для получения UID и PID вызывающего: GetConnectionUnixUser и GetConnectionUnixProcessID. Результат этих вызовов записывается в структуру data типа AsyncGetBusNameCredsData (эта структура инициализируется нулями) функцией обратного вызова on_retrieved_unix_uid_pid(), а polkit_system_bus_name_get_creds_sync() блокируется в ожидании, пока функция обратного вызова не установит ошибку или не вернёт UID и 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, }; // инициализируем нулями
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", /* имя */
"/org/freedesktop/DBus", /* путь объекта */
"org.freedesktop.DBus", /* имя интерфейса */
"GetConnectionUnixUser", /* метод */
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); // data передаётся в функцию обратного вызова вместе с ответом от метода
g_dbus_connection_call (connection,
"org.freedesktop.DBus", /* имя */
"/org/freedesktop/DBus", /* путь объекта */
"org.freedesktop.DBus", /* имя интерфейса */
"GetConnectionUnixProcessID", /* метод */
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); // data передаётся в функцию обратного вызова вместе с ответом от метода
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // ждём, пока не будет вызвана on_retrieved_unix_uid_pid()
g_main_context_iteration (tmp_context, TRUE);
Функция обратного вызова on_retrieved_unix_uid_pid() вызывается после каждого вызова метода для получения ответа (UID и PID) или установки ошибки. Эта функция вызывает g_dbus_connection_call_finish(), чтобы получить ответ; если произошла ошибка, она устанавливает data.caught_error в TRUE и возвращается (data.uid и data.pid остаются равными 0). В противном случае она присваивает полученное значение (UID или PID) data.uid или data.pid (в зависимости от того, какое значение получено) и возвращается.
static void
on_retrieved_unix_uid_pid (GObject *src, // connection
GAsyncResult *res, // объект асинхронного результата
gpointer user_data) // параметр 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;
}
else
{
guint32 value;
g_variant_get (v, "(u)", &value); // распаковываем ответ, получаем UINT32 (u)
g_variant_unref (v);
if (!data->retrieved_uid) // метод GetConnectionUnixUser
{
data->retrieved_uid = TRUE;
data->uid = value;
}
else
{
g_assert (!data->retrieved_pid); // метод GetConnectionUnixProcessID
data->retrieved_pid = TRUE;
data->pid = value;
}
}
}
Методы GetConnectionUnixUser и GetConnectionUnixProcessID вернут UID и PID, если вызывающий процесс всё ещё подключён к шине, или ошибку, если произошла ошибка (например, вызывающий процесс был завершён).
Как только data.uid и data.pid установлены или data.caught_error установлен, функция polkit_system_bus_name_get_creds_sync() продолжает выполнение, и здесь находится уязвимость: polkit_system_bus_name_get_creds_sync() не возвращает ошибку, если установлен data.caught_error; вместо этого она присваивает out_uid значение data.uid и возвращает TRUE (даже если data.caught_error установлен). out_pid — это указатель на переменную guint32, переданную в polkit_system_bus_name_get_creds_sync() при вызове из polkit_system_bus_name_get_user_sync():
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid, // указатель
guint32 *out_pid, // NULL
GCancellable *cancellable,
GError **error)
{
[snip]
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // ждём обработки ответа функцией обратного вызова
g_main_context_iteration (tmp_context, TRUE);
if (out_uid) // TRUE
*out_uid = data.uid; // устанавливаем, даже если ошибка [!]
if (out_pid) // FALSE
*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;
Эксплуатация: