
تحليل متعمق لـ CVE-2021-3560، وهي ثغرة تصعيد امتيازات محلية في Linux PolKit. يتضمن تحليل السبب الجذري، وآليات الاستغلال، وعرضًا توضيحيًا لحالة السباق (race condition) التي تؤدي إلى الوصول بصلاحيات الجذر (root access).
[toc]
رقم الثغرة: CVE-2021-3560
تقييم الثغرة:
المنتج المتأثر: linux PolKit (polkitd)
نطاق التأثير: تم إدخاله في الكود المصدري 0.113؛ https://www.venustech.com.cn/new_type/aqtg/20210611/22788.html
شروط الاستغلال: محلي على لينكس؛ وجود dbus + polkitd
تأثير الاستغلال: تصعيد امتيازات محلي
الحصول على الكود المصدري:
apt source accountsserviceapt source dbusيمكن إعادة إنتاج الثغرة باستخدام الصورة الافتراضية لـ 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; // تعيين بت الخطأ بعد الفشل لسبب ما
}
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، تقوم هذه الدالة بالمصادقة على بعض الطلبات (سيتم شرح المنطق المحدد لاحقًا). يتم استدعاء طريقتي GetConnectionUnixUser و GetConnectionUnixProcessID تباعًا عبر ناقل org.freedesktop.DBus. هاتان الطريقتان مقدمة من ناقل org.freedesktop.DBus، وتقومان بالحصول على PID العملية الطالبة ومعرف المستخدم (UID)، ثم يتم تمريرهما إلى دالة الاسترجاع on_retrieved_unix_uid_pid لمعالجة البيانات المرتجعة. بعد ذلك، يتم حظر التنفيذ حتى تنتهي العملية على جانب الناقل.
يمكن ملاحظة من دالة on_retrieved_unix_uid_pid أنه يتم تعيين ما إذا كان هناك استثناء (error?) بناءً على نتيجة التنفيذ من جانب الناقل، ثم يتم تعيين بت الاستثناء. إذا تم التنفيذ بنجاح، يتم إرجاع معرف المستخدم أو 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 عبارة عن مجموعة أدوات على مستوى التطبيق، والتي تحقق الاتصال بين العمليات ذات الأولويات المختلفة من خلال تعريف ومراجعة قواعد الإذن: يتركز قرار التحكم في إطار موحد، ليحدد ما إذا كانت العملية منخفضة الأولوية لها الحق في الوصول إلى عملية عالية الأولوية.
باختصار، هذه عملية جذرية تعمل في الخلفية، وتقوم بتقييم الإذن للعمليات الأخرى منخفضة الصلاحية عند الوصول إلى الوظائف التي توفرها العمليات عالية الصلاحية من خلال الاتصال بين العمليات. نظرًا لأن هذه العملية ستعيد التشغيل إذا تم إرفاق 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