首页 > 技术文章 > Kylin 复现 CVE-2021-3560

luoleqi 2021-08-30 18:37 原文

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 ,并不能确保进入 check_authorization_sync() 函数时 uid 还是我们 fake 的 uid(0),所以需要循环不断尝试直至绕过判断)。
复现演示:

内容参考

github_CVE-2021-3560

推荐阅读