Kylin 復現 CVE-2021-3560
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 ,所以需要使用迴圈不斷產生 fake 的 uid(0),直至我們的 fake 的 uid(0) 成功進入 check_authorization_sync() 繞過檢測)。
復現演示: