
CVE-2021-3560 के लिए C एक्सप्लॉइट, polkit में एक प्रमाणीकरण बाईपास जो अविशेषाधिकार प्राप्त उपयोगकर्ताओं को DBus के माध्यम से एक विशेषाधिकार प्राप्त खाता बनाने में सक्षम बनाता है, विस्तृत तकनीकी विवरण के साथ।
CVE-2021-3560 के लिए एक विश्वसनीय C-आधारित एक्सप्लॉइट।
कल मैं इस ब्लॉग पोस्ट पर ठोकर खाई, जो केविन बैकहाउस (जिन्होंने इस भेद्यता की खोज की) द्वारा लिखा गया था। मैंने ब्लॉगपोस्ट में दिए गए bash कमांड आज़माए और आश्चर्यजनक रूप से यह मेरे Kali Linux बॉक्स पर काम कर गया!
CVE-2021-3560 polkit पर एक प्रमाणीकरण बायपास है, जो एक गैर-विशेषाधिकार प्राप्त उपयोगकर्ता को DBus का उपयोग करके विशेषाधिकार प्राप्त विधियों को कॉल करने की अनुमति देता है। PoC इस बग का फायदा उठाकर accountsservice द्वारा प्रदान की गई 2 विशेषाधिकार प्राप्त विधियों (CreateUser और SetPassword) को कॉल करता है, जिससे हम एक विशेषाधिकार प्राप्त उपयोगकर्ता बना सकते हैं और फिर उसके लिए पासवर्ड सेट कर सकते हैं।
polkit जाँचता है कि क्या कॉल करने वाला ऐसी विधि को कॉल करने के लिए अधिकृत है। यह पहले कॉल करने वाले की user id की जाँच करके ऐसा करता है। यदि यह शून्य है, तो कॉल करने वाले को root मान लिया जाता है और कार्रवाई बिना प्रमाणीकरण मांगे अनुमत होती है, अन्यथा यह उपयोगकर्ता का पासवर्ड पूछता है।
polkit_system_bus_name_get_creds_sync() फ़ंक्शन कॉल करने वाले का UID और PID प्राप्त करने के लिए GetConnectionUnixUser और GetConnectionUnixProcessID नामक 2 विधियों को आमंत्रित करता है। इन कॉलों का परिणाम AsyncGetBusNameCredsData प्रकार के data स्ट्रक्चर में लिखा जाता है (यह स्ट्रक्चर 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 लौटाएँगी (कॉल करने वाली प्रक्रिया अभी भी बस से जुड़ी हुई है), या यदि कोई त्रुटि हुई है तो एक त्रुटि लौटाएँगी (जैसे: कॉल करने वाली प्रक्रिया मार दी गई)।
जब 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 एक guint32 चर के लिए पॉइंटर है जिसे polkit_system_bus_name_get_user_sync() द्वारा कॉल किए जाने पर polkit_system_bus_name_get_creds_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;
शोषण: