
Углублённый анализ CVE-2021-3560 — локальной уязвимости повышения привилегий в Linux PolKit. Включает анализ первопричины, механику эксплойта и демонстрацию состояния гонки, приводящего к получению root-доступа.
[toc]
Идентификатор уязвимости: CVE-2021-3560
Оценка уязвимости:
Уязвимый продукт: linux PolKit (polkitd)
Затронутые версии: внедрена в исходном коде 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
Условия эксплуатации: локальный доступ к linux; наличие dbus + polkitd
Результат эксплуатации: локальное повышение привилегий
Получение исходного кода:
apt source accountsserviceapt source dbusСтандартного образа ubuntu-20.04.2 достаточно для воспроизведения: http://old-releases.ubuntu.com/releases/20.04.2/ubuntu-20.04.2-desktop-amd64.iso
Сначала посмотрим на проблемный код
/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;
}
В функции polkit_system_bus_name_get_creds_sync выполняется аутентификация некоторых запросов (конкретный логический сценарий будет подробно описан ниже). Сначала через шину org.freedesktop.DBus вызываются методы GetConnectionUnixUser и GetConnectionUnixProcessID; эти методы предоставляются шиной org.freedesktop.DBus и служат для получения PID и UID пользователя запрашивающего процесса, после чего данные передаются в callback-функцию on_retrieved_unix_uid_pid для обработки. Затем выполняется блокирующее ожидание завершения выполнения процесса на стороне шины.
В функции on_retrieved_unix_uid_pid видно, что в зависимости от результата выполнения на стороне шины устанавливается признак ошибки (error?), после чего устанавливается бит ошибки; при успешном выполнении возвращаются UID пользователя или PID процесса. Однако в функции polkit_system_bus_name_get_creds_sync после блокирующего ожидания результата от шины (условием завершения обработки является обработка и UID, и PID, либо возникновение ошибки). То есть, с логической точки зрения, со стороны шины возможны три результата: получен UID пользователя 0 — привилегированный пользователь; получен UID пользователя, отличный от 0, — обычный пользователь; ошибка. Но в дальнейшей обработке ошибка не обрабатывается: out_uid, возвращаемый на верхний уровень, напрямую устанавливается в data.uid, полученный от шины, а затем ret просто устанавливается в TRUE:
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?
Однако был упущен случай, когда на стороне шины DBUS возникает ошибка выполнения: callback-функция устанавливает бит ошибки и возвращается, так и не обработав данные data, а data инициализируется нулём. В результате далее out_uid устанавливается в 0, то есть в привилегированного пользователя.
Где же используется эта проблемная функция polkit_system_bus_name_get_creds_sync?
Процессом, в котором находится уязвимость, является polkitd:
polkit — это набор инструментов прикладного уровня, который, определяя и проверяя правила прав доступа, обеспечивает взаимодействие между процессами с разными приоритетами: управление решениями централизовано в единой среде и определяет, имеет ли процесс с низким приоритетом право доступа к процессу с высоким приоритетом.
Проще говоря, это фоновый процесс root, который посредством межпроцессного взаимодействия выполняет проверку прав, когда другие процессы с низкими привилегиями обращаются к функциям, предоставляемым процессами с высокими привилегиями. Поскольку при подключении gdb этот процесс перезапускается, анализ можно проводить только на уровне исходного кода, чтобы найти путь срабатывания уязвимой функции:
В polkitd для разработки широко используется фреймворк межпроцессного взаимодействия gio DBUS; можно заранее ознакомиться с руководством, обратив внимание на некоторые ключевые функции.
Далее проанализируем путь срабатывания уязвимости в polkitd:
Сначала 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);
name_owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
"org.freedesktop.PolicyKit1", //注册了一个总线名称
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
(opt_replace ? G_BUS_NAME_OWNER_FLAGS_REPLACE : 0),
on_bus_acquired, //访问该总线时的回调函数
on_name_acquired,
on_name_lost,
NULL,
NULL);
g_print ("Entering main event loop\n");
g_main_loop_run (loop); //循环启动
··· ···
··· ···
}
Сначала регистрируется шина org.freedesktop.PolicyKit1, затем есть три callback-функции; основное внимание стоит уделить on_bus_acquired, которая вызывается при обращении к шине.
В функции on_bus_acquired напрямую вызывается функция регистрации polkit_backend_authority_register