[toc]
漏洞编号: CVE-2021-3560
漏洞评分:
漏洞产品: linux PolKit (polkitd)
影响范围: 源代码 0.113 引入;https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
利用条件: linux 本地;dbus + polkitd 存在
利用效果: 本地提权
源码获取:
apt source accountsserviceapt source dbusubuntu 默认镜像 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
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,然后传入回调函数 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:
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总线那边执行异常,导致回调函数设置了异常位就反悔了,根本没处理data 这个数据,而data 初始化是0,这就造成后续直接将out_uid设置成了0,也就是特权用户。
那么这个出问题的 polkit_system_bus_name_get_creds_sync 函数会在哪用到呢。
漏洞点所在进程是 polkitd 进程:
polkit 是一个应用程序级别的工具集,通过定义和审核权限规则,实现不同优先级进程间的通讯:控制决策集中在统一的框架之中,决定低优先级进程是否有权访问高优先级进程。
简而言之就是,这是一个后台运行的root 进程,通过进程间通信的方式来对其他低权限进程访问高权限进程提供的功能时做一个权限判断。由于这个进程gdb 挂上就会重启,所以我们只能通过源码的层面进行分析,找到漏洞函数的触发路径:
polkitd 里面大量使用了gio DBUS 进程间通信框架来开发,可以提前了解下手册,关注一些重点函数什么的。
接下来分析polkitd 的漏洞触发路径,
首先是main:
polkit-0.113\src\polkitbackend\polkitd.c : 155 : main
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); //循环启动
··· ···
··· ···
}
先注册了个叫org.freedesktop.PolicyKit1 的总线,然后有三个回调函数,主要关注on_bus_acquired ,会在总线被访问的时候调用。
在on_bus_acquired 函数中直接调用了一个注册类型的函数 polkit_backend_authority_register
在 polkit_backend_authority_register 函数中,注册了一个接口对象org.freedesktop.PolicyKit1.Authority,并且传入一个回调函数表 server_vtable。漏洞触发点就在这个回调函数表中的一个:
policykit-1_0.113.orig\polkit-0.113\src\polkitbackend\polkitbackendauthority.c : 1333 : server_vtable
static const GDBusInterfaceVTable server_vtable =
{
server_handle_method_call, //回调函数
server_handle_get_property,
NULL, /* server_handle_set_property */
};
static void
server_handle_method_call (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
Server *server = user_data;
PolkitSubject *caller;
caller = polkit_system_bus_name_new (g_dbus_method_invocation_get_sender (invocation));
if (g_strcmp0 (method_name, "EnumerateActions") == 0)
server_handle_enumerate_actions (server, parameters, caller, invocation);
else if (g_strcmp0 (method_name, "CheckAuthorization") == 0)
server_handle_check_authorization (server, parameters, caller, invocation); //漏洞函数路径在这里
··· ···
··· ···
}
当其他进程通过总线调用 CheckAuthorization 方法的时候就会调用到 server_handle_check_authorization 函数。
在server_handle_check_authorization 函数中,调用 polkit_backend_authority_check_authorization
在 polkit_backend_authority_check_authorization 函数中,通过回调掉用了 klass->check_authorization ,后者祖册的函数是 polkit_backend_interactive_authority_check_authorization :
polkit-0.113\src\polkitbackend\polkitbackendauthority.c : 195 : polkit_backend_authority_check_authorization
void
polkit_backend_authority_check_authorization (PolkitBackendAuthority *authority,