1. 程式人生 > 其它 >Kylin 復現 CVE-2021-3560

Kylin 復現 CVE-2021-3560

CVE-2021-3560

CVE-2021-3560 通過利用 polkit 中存在的漏洞可以達到使得非特權本地使用者獲得系統root許可權的目的。
復現環境:ubuntukylin-20.04-pro

原理簡述

polkit

polkit 是一個應用程式框架,通過定義和稽核許可權規則,實現不同有限級程序間的通訊。每當使用者中的某個程序嘗試在系統環境中執行操作時,系統就會查詢 polkit 。使用者發起的會話由授權和身份驗證代理構成,授權以系統訊息總線上的一個服務是形式來實現,而身份驗證代理用於對啟動會話的當前使用者進行身份認證。

原始碼分析

首先放上原始碼:

static gboolean
polkit_system_bus_name_get_creds_sync (...)
{
  ...
  g_dbus_connection_call (...
			  "GetConnectionUnixUser",      /* method */
                          ...
			  on_retrieved_unix_uid_pid, // callback funtion
			  &data); // data is passed to the callback function along with the reply from the method

  g_dbus_connection_call (...
			  "GetConnectionUnixProcessID", /* method */
                          ...
			  on_retrieved_unix_uid_pid, // callback funtion
			  &data); // data is passed to the callback function along with the reply from the method

  while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // block while on_retrieved_unix_uid_pid() is not called yet
    g_main_context_iteration (tmp_context, TRUE);

  ...  

  if (out_uid) // TRUE
    *out_uid = data.uid; // set it even if there is an error [!]
  if (out_pid) // FALSE
    *out_pid = data.pid; // set it even if there is an error [!]
  ret = TRUE; // return TRUE even if there is an error [!]
 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;
}

on_retrieved_unix_uid_pid (...)
{
  ...
  v = g_dbus_connection_call_finish ((GDBusConnection*)src, res,
				     data->caught_error ? NULL : data->error); // finish and get the reply
  if (!v) // error ??
    {
      data->caught_error = TRUE;
    }
  else
    {
      ...
      if (!data->retrieved_uid) // GetConnectionUnixUser method
	{
	  data->retrieved_uid = TRUE;
	  data->uid = value;
	}
      else
	{
	  g_assert (!data->retrieved_pid); // GetConnectionUnixProcessID method
	  data->retrieved_pid = TRUE;
	  data->pid = value;
	}
    }
}

整個流程大致如下:

  1. polkit_system_bus_name_get_creds_sync() 函式呼叫方法 GetConnectionUnixUser 和 GetConnectionUnixProcessID 獲取發起會話的使用者的 UID 和會話程序的 PID,並呼叫回撥函式on_retrieved_unix_uid_pid() 。
  2. 回撥函式 on_retrieved_unix_uid_pid() 的作用將獲取的 UID 和 PID 寫入變數 data->uid 和 data->pid 中。如果 UID 或 PID 獲取失敗,函式會將 data->caught_error 設定為 TRUE 並返回 polkit_system_bus_name_get_creds_sync() 函式。
  3. polkit_system_bus_name_get_creds_sync() 函式中運用 while 迴圈等待回撥函式結束,當用 UID 和 PID 被找到或者發生錯誤(data->caught_error = TRUE),函式將繼續執行將 *out_uid 和 *out_pid 設定成對應的值並返回 TRUE。

到這裡漏洞的成因就很明顯了,當獲取 UID 和 PID 失敗的時候,程式只是將 data->caught_error 設定為 TRUE 而沒有產生報錯,會繼續執行下去將 *out_uid 設定成 0 。(資料被初始化為 0,而有沒有獲取到 pid,所以最後返回是 *out_uid 的值為 0)。

check_authorization_sync (...)
{
  ...
  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;

  /* special case: uid 0, root, is _always_ authorized for anything */
  if (identity_is_root_user (user_of_subject)) // true
    {
      result = polkit_authorization_result_new (TRUE, FALSE, NULL); // authorize the caller
      goto out;
    }
  ...
)

接下來會進入 check_authorization_sync() 函式,check_authorization_sync() 函式通過 identity_is_root_user 判斷 UID 是否為 root (root 的 UID 為 0),而之前已經將 UID 設定為 0,所以將會返回 TRUE 並對這次會話行為進行授權。

利用思路

我們可以利用這個漏洞來建立一個 root 許可權的使用者從而實現提權。首先開啟一個會話使得系統呼叫 polkit 來進行身份認證和授權,發出資訊後馬上退出會話,這樣在會話程序 PID 就不存在了,polkit_system_bus_name_get_creds_sync() 函式中的 GetConnectionUnixUser 和 GetConnectionUnixProcessID 方法將返回錯誤,然後在 on_retrieved_unix_uid_pid() 回撥函式中data.caught_error 被設定為 TRUE,最後導致 UID 被設定為 0 繞過 identity_is_root_user 檢測從而授權這次會話行為,這樣我們就成功建立了一個 root 許可權使用者(polkit 呼叫過程中會多次請求 UID ,所以需要使用迴圈不斷產生 fake 的 uid(0),直至我們的 fake 的 uid(0) 成功進入 check_authorization_sync() 繞過檢測)。
復現演示:

內容參考

github_CVE-2021-3560