
Linux PolKit의 로컬 권한 상승 취약점인 CVE-2021-3560에 대한 심층 분석. 근본 원인 분석, 익스플로잇 메커니즘, 그리고 루트 액세스로 이어지는 경쟁 조건(race condition)의 시연을 포함합니다.
[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을 호출한다