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;
}
}
}
整个流程大致如下:
- polkit_system_bus_name_get_creds_sync() 函数调用方法 GetConnectionUnixUser 和 GetConnectionUnixProcessID 获取发起会话的用户的 UID 和会话进程的 PID,并调用回调函数on_retrieved_unix_uid_pid() 。
- 回调函数 on_retrieved_unix_uid_pid() 的作用将获取的 UID 和 PID 写入变量 data->uid 和 data->pid 中。如果 UID 或 PID 获取失败,函数会将 data->caught_error 设置为 TRUE 并返回 polkit_system_bus_name_get_creds_sync() 函数。
- 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),所以需要循环不断尝试直至绕过判断)。
复现演示: