探索 DBus 跨进程消息传递中的安全风险

一、前言

D-Bus (Desktop Bus)是一个用于在 Linux 和 Unix 系统上进行进程间通信的消息总线系统,它提供了一种机制,使得软件组件可以互相交流、传递消息和调用服务。

尽管 D-bus 最初是为桌面环境设计的,但它的通信机制和功能使其在非桌面环境中同样适用,以下是一些非桌面环境中使用 D-Bus 的例子:

  1. 服务间通信:D-Bus 可以用作服务之间进行通信的机制,无论是在服务器环境、嵌入式系统还是其他非桌面应用中。它可以帮助不同的服务或守护进程相互交流和协调工作。
  2. 系统管理:D-Bus 在系统管理领域也有广泛的应用。例如,系统服务可以使用 D-Bus 在后台进行通信,以便进行配置、监控和控制。这对于系统管理工具、系统监控应用和自动化任务非常有用。
  3. 嵌入式系统:D-Bus 在嵌入式系统中也可以发挥作用。它可以用于不同的组件之间进行通信,如硬件驱动程序、系统服务和用户应用程序。通过使用 D-Bus,这些组件可以共享信息、传递事件和协调操作。
  4. IoT(物联网)设备:D-Bus 在物联网设备中的应用也在增加。它可以用于不同设备之间的通信,例如智能家居设备、传感器、控制器等。通过使用 D-Bus,这些设备可以相互通信、共享数据和提供服务。

然而,就像任何其他的通信协议一样,D-Bus通信也存在一些安全风险。本文将介绍 D-Bus 的通信机制,并分析其中的安全问题。

二、D-Bus 通信

2.1 D-Bus 通信背景知识

  • D-Bus 消息总线:D-Bus 使用消息总线作为通信的中心枢纽,允许不同进程之间消息传递。
  • 总线名称和对象路径:每个 D-Bus 消息都与一个特定的总线名称和对象路径相关联,以确定消息是由哪个进程发送和接收。
  • 接口和方法调用:D-Bus使用接口和方法调用的概念,进程可以调用其它进程公开的方法来进行通信。

D-Bus 通信比较常见,很多系统设置相关的操作都会触发 d-bus 通信。比如:修改用户头像操作,可以通过命令行在发送的 D-Bus 消息实现。

dbus-send --system --print-reply --dest=org.freedesktop.Accounts /org/freedesktop/Accounts/User1000 org.freedesktop.Accounts.User.SetIconFile string:/home/fish/Pictures/1.jpg

2.2 D-Bus 消息介绍

D-Bus 消息由header和body组成,hedaer包含消息的基本信息,包括发送进程的链接名,方法,消息类型等。

消息类型有以下四种:

  • CALL方法调用:发起进程发出的消息;
  • REPLY 方法返回:方法调用返回的结果;
  • ERROR 消息:方法调用返回一个异常;
  • SIGNAL 消息:通知,广播消息。

消息结构如下图所示:

图片来源:blackhat 参考链接[3]

2.3 D-Bus Hello 消息

在 D-Bus 通信中,第一个数据包被称为 “Hello” 消息。它是在客户端应用程序连接到 D-Bus 守护进程时发送的。

“Hello” 消息包含了客户端应用程序的一些基本信息,例如要使用的 D-Bus 版本、客户端的唯一名称等。

下图右边所示,表明了发起者的身份信息,左边是对应的回复包。

  • unique bus name 用于标识在总线上的身份。
  • well-known name 是由处理器声明的,用于防止非特权进程模拟系统服务并使用它来拦截重要的消息。
图片来源:blackhat 参考链接[3]

2.4 D-Bus 生态系统架构

dbus-daemon 作为守护进程,负责将 dbus 消息分发到正确的接收者中,支持D-Bus通信的进程有一些重要的系统服务,如:systemd, accountesservice, polkitd 等。

图片来源:blackhat 参考链接[3]

三、DBus 通信漏洞案例

3.1 案例一:鉴权功能处理不当

在鉴权时,尽管考虑到了获取发起者进程失败的情况且设置了错误信息,但是函数还是返回了true,后续函数也没有去判断错误信息,而是根据函数返回结果去做进一步操作。

CVE-2021-3560

CVE-2021-3560是一个存在于 polkit 上的一个授权绕过漏洞,非特权用户可以绕过授权检查,以 root 权限执行未经授权的操作。比如创建特权账户,创建账户的dbus命令如下:

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:"dried" string:"dried name" int32:1

polkit 是一个用于 Linux 操作系统的权限管理框架,通过提供一个授权和认证机制来管理用户或进程对系统资源的访问权限。

它允许系统管理员定义哪些操作需要特权,并为非特权用户提供临时的特权访问。例如,用户需要进行系统设置或安装软件时,polkit 可以提示用户输入管理员密码以授权操作。

下面是 polkit的配置文件,从中可以看出,修改用户自己的数据是允许的,账户管理相关操作(比如创建账户)则需要管理员权限。

所以通过 dbus 命令创建账户过程,需要通过 polkit 来鉴权,流程及步骤如下所示:

图片来源:blackhat 参考链接[3]

1.当我们将创建账号的命令,通过 dbus-send 将消息发送给账号服务进程。

2.dbus-daemon 守护进程会先接收到 dbus-send 的消息,并给消息添加唯一总线名称(unique bus name),假设是“:1.3591”,然后发送给账号服务进程,添加的唯一总线名称确保了消息无法被伪造。

3.账号服务进程收到消息后,通过 dbus-daemon 守护进程向 polkit 发送一条检查授权的消息,询问 “:1.3591” 的连接是否具有权限。

4.polkit 再向 dbus-daemon 守护进程询问该连接的uid,假如返回的 uid 为 0,polkit 会立即返回 ok 进行授权。否则会向 authentication agent 进程发送消息弹出对话框让用户输入密码。

5.authentication agent 通过 polkit-agent-helper-1 的程序检测用户输入的密码,密码正确后设置 uid。

6.polkit 在收到响应后才会信任用户是具有 root 权限。

7.最后,polokit 会发回一个 ok 的回复给账户服务进程,账号服务继续创建新账户。

漏洞出现在第四步,polkit 向 dbus-daemon 守护进程获取请求 uid的时候,当 uid 不存在时候,没有正确处理这种情况。

在 polkit 中,通过函数 polkit_system_bus_name_get_creds_sync 获取与指定系统总线名称相关联的dbus消息发起进程的凭证信息,包括UID信息。

在该函数中通过 g_dbus_connection_call 函数向 dba-deamon 进程询问消息发起进程的UID等凭证信息,polkit_system_bus_name_get_creds_sync函数虽然设置了错误参数data,但是函数返回值还是返回 TRURE,导致了后续判断是否创建账户判断不够严谨。该函数中代码片段如下:

从函数调用堆栈中可以看到:

in polkit_system_bus_name_get_creds_sync of polkitsystembusname.c:388

in polkit_system_bus_name_get_user_sync of polkitsystembusname.c:511

in polkit_backend_session_monitor_get_user_for_subject of polkitbackendsessionmonitor-systemd.c:303

in check_authorization_sync of polkitbackendinteractiveauthority.c:1121

in check_authorization_sync of polkitbackendinteractiveauthority.c:1227

in polkit_backend_interactive_authority_check_authorization of polkitbackendinteractiveauthority.c:981

in polkit_backend_authority_check_authorization of polkitbackendauthority.c:227

in server_handle_check_authorization of polkitbackendauthority.c:790

in server_handle_method_call of polkitbackendauthority.c:1272

在函数 check_authorization_sync 中,代码中的 polkit_backend_session_monitor_get_user_for_subject 函数虽然有 error 参数,但是后续没有判断error的值,只是通过polkit_backend_session_monitor_get_user_for_subject 函数返回值来判断是否获取到了发起进程的UID。

前面提到函数polkit_system_bus_name_get_creds_sync在没有获取到消息发起进程UID信息时只是设置了error 但是还是返回TRUE,这导致会在该函数中直接进入第二个 if 执行 polkit_authorization_result_new 函数创建账户。

  /* every subject has a user; this is supplied by the client, so we rely
   * on the caller to validate its acceptability. */
  user_of_subject = polkit_backend_session_monitor_get_user_for_subject (priv->session_monitor,
                                                                         subject, NULL,
                                                                         error);
  if (user_of_subject == NULL)
      goto out;

  /* 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;
    }

最后可以通过主动结束 dbus-send 创建账户的命令来触发这个获取不到发起进程UID信息的场景,由于不确定命令发出多久会刚好走查询是否有权限的步骤,可以修改结束时间进行多尝试。

dbus-send --system --dest=org.freedesktop.Accounts --type=method_call --print-reply /org/freedesktop/Accounts org.freedesktop.Accounts.CreateUser string:dried string:"dried name" int32:1 & sleep 0.007s; kill $!

3.2 案例二:特权功能判断不全

gdm3通过向账号服务进程获取用户列表时,对账号服务进程拒绝服务的情况考虑不全,导致后续又可以重新创建一个新的root账号。

CVE-2020-16125

CVE-2020-16125漏洞是由于 gdm3 在 Ubuntu 上的机制导致的提权。

当 gdm3 通过 dbus 通信向 accountservice 获取当前用户列表时,而 accountservice 未响应,此时gdm3 会启动 gnome-initial-setup 程序创建一个新账户,这导致非特权用户能够创建一个新账户,而且该账户具有root相同权限。

是否具有root权限从 gnome-initial-setup 的配置文件可以确定,gnome-initial-setup 配置文件如下图所示:

从中可以看出,gnome-inital-setup 用户支持的方法包括:挂载磁盘、设置时间、控制网络和创建用户等。

其中的 org.freedesktop.accounts.* 方法,通过 org.freedesktop.Accounts 的配置文件,可以看出只有 root 用户才允许调用。所以通过 gnome-initial-setup 程序创建的新账户也是具有root权限的。

漏洞点位于,当 gdm3 使用 D-Bus 方法 g_dbus_proxy_new_syncorg.freedesktop.Accounts 守护进程获取现有用户列表过程中,由于 priv->have_existing_user_accounts 默认是false,当通过函数 g_dbus_proxy_new_sync 无法与 org.freedesktop.Accounts 建立连接时,将直接 goto 到函数出口,此时priv->have_existing_user_accounts 依然保持 false。 look_for_existing_users_sync 函数如下所示:

根据函数调用关系,获取用户列表函数 look_for_existing_users_sync 是从 gdm_display_prepare 调用的,代码如下所示:

当获取用户列表函数中的 priv->have_existing_user_accounts 为 false,表示现在没有用户账户,因此 wants_initial_setup 函数会返回 true,从而调用 gnome-initial-setup,执行创建新用户的操作。

四、总结

文章介绍了D-Bus 通信机制并分析了相关的漏洞案列,漏洞的成因可能是开发者对认证和授权机制结果考虑不全面,导致可以通过利用这些特定条件绕过检查达到权限提升的目的。在分析 D-Bus 通信过程中,建议关注鉴权流程和特权操作相关功能,审计相关功能的代码逻辑。

五、参考资料

[1] Privilege escalation with polkit: How to get root on Linux with a seven-year-old bug

[2] GHSL-2023-139: Use After Free (UAF) in accountsservice – CVE-2023-3297

[3] Message in a Broken Bottle:Exploring the Linux IPC Attack Surface