引言

在对来自 open-vm-tools 仓库 的 setuid-root 二进制文件 vmware-user-suid-wrapper 进行例行审查时,我发现了本报告中所述的漏洞。审查的版本是 open-vm-tools 版本 12.2.0。然而,open-vm-tools 仓库中 setuid-root 二进制文件的源代码自 10.3.0 版本(发布于 2018 年)以来并未改变,因此当前大多数 open-vm-tools 的安装可能都受到此发现的影响。

vmware-user-suid-wrapper 的行为

乍一看,vmware-user-suid-wrapper 似乎很小且无害

  • 如果它认为自己在 Wayland 上运行,它会以 root 身份打开 /dev/uinput。后者是通过检查环境变量 XDG_SESSION_TYPE 的值来确定的,检查它是否设置为“wayland”。
  • 如果 /var/run/vmblock-fuse/dev 存在,它会以 root 身份打开它。
  • 它会永久性地撤销所有权限给真实(非特权)用户和组 ID,并执行 /usr/bin/vmtoolsd,将之前打开的任何文件描述符继承给它。
  • 新的 vmtoolsd 进程将检查环境变量,例如检查当前主机是否在 vmware 虚拟机环境中运行以及是否可用图形会话。如果其中一项不满足,进程将迅速终止。成功后,守护进程将继续运行,提供其服务,并保持已打开的特权文件描述符。

所以一切似乎都正常,程序打开最多两个特权文件,撤销权限,并将打开的文件传递给 vmtoolsd 以在调用用户的上下文中进行使用。

漏洞

这里(有些令人惊讶)的问题在于撤销权限到真实 uid / gid 与后续 execve() 以执行非 setuid 程序 vmtoolsd 的组合。在 execve() 期间,进程的“dumpable”属性将被重置为 1 的值。

prctl(5) 手册页中,我们可以了解到关于进程的 dumpable 属性的以下信息

Normally, the "dumpable" attribute is set to 1. However, it is reset to
the current value contained in the file /proc/sys/fs/suid_dumpable (which by
default has the value 0), in the following circumstances:

[...]

- The process executes (execve(2)) a set-user-ID or set-group-ID program,
  resulting in a change of either the effective user ID or the effective
  group ID.

[...]

Processes that are not dumpable can not be attached via ptrace(2)
PTRACE_ATTACH; see ptrace(2) for further details.

在大多数 Linux 发行版中,全局 suid_dumpable 设置要么是 0(setuid 程序根本不能转储核心),要么是 2(setuid 程序可以转储核心,但只能在安全的文件系统位置)。因此,当 vmware-user-suid-wrapper 运行时,其 dumpable 属性在 openSUSE Tumbleweed 上被设置为 2,我一直在使用它来研究这个问题。然而,在 execve() 之后,情况会发生变化,正如 execve(2) 手册页中所记录的那样

The following Linux-specific process attributes are also not preserved
during an execve():

- The process's "dumpable" attribute is set to the value 1, unless a
  set-user-ID program, a set-group-ID program, or a program with
  capabilities is being executed, [...].

因此,当 vmtoolsd 以撤销权限的方式执行时,进程的“dumpable”属性将被重置为 1。

问题在于,最初调用 vmware-user-suid-wrapper 的非特权用户现在被允许 ptrace() vmtoolsd 进程,以及许多在 setuid-root 进程之前不允许的其他操作。

从非特权用户的角度来看,vmtoolsd 拥有的有趣资源是 /dev/uinput 和/或 /var/run/vmblock-fuse/dev 的打开文件描述符。借助 ptrace(),恶意代码可以被注入到 vmtoolsd 进程中,以访问特权文件描述符。一个更简单的方法是使用现代 Linux 的 pidfd API pidfd_open()pidfd_getfd() 来获取特权文件描述符的副本。在 pidfd_getfd(2) 手册页中,我们可以找到

Permission to duplicate another process's file descriptor is governed by a
ptrace access mode PTRACE_MODE_ATTACH_REALCREDS check (see ptrace(2)).

在这种情况下,这又回到了进程的“dumpable”属性,该属性现在设置为 1,因此操作被允许。

利用该问题

通过设置用户控制的环境变量 XDG_SESSION_TYPE=wayland,可以强制 vmware-user-suid-wrapper 打开 /dev/uinput,即使它没有在 Wayland 上运行。这意味着该设备文件的文件描述符将始终是有效的攻击者目标,而与系统上的实际情况无关。

关于该问题的可利用性,有两种不同的场景需要考虑。较简单的情况是当存在 vmtoolsd 的有效环境时,即存在图形桌面会话并且成功通过运行在 VMware 虚拟机中的检查(函数调用 VMCheck_IsVirtualWorld())。在这种情况下,vmtoolsd 将永久运行,并且没有竞态条件需要赢得。利用该问题很简单,正如 PoC 程序 vmware-get-fd.c 所演示的那样。

更困难的情况是当攻击者没有运行图形环境,或者甚至没有运行在 VMware 虚拟机环境中时。在最坏的情况下,vmtoolsd 将很快终止,因为 VMCheck_IsVirtualWorld() 检查失败。因此,实际操作易受攻击进程的时间窗口很短。PoC 程序的变体 vmware-race-fd.c 会持续启动 vmware-user-suid-wrapper,并尝试从短暂存在的 vmtoolsd 进程中抢夺特权文件描述符。在我的测试中,这通常很快就成功了(甚至在第一次尝试时),可能是因为 vmtoolsd 的资源尚未被内核缓存。后来的尝试通常需要更长时间才能成功,但在 10 到 20 秒后仍然成功。

总而言之,setuid-root 程序 vmware-user-suid-wrapper 的存在足以利用 /dev/uinput 的问题。攻击者不需要任何特殊权限(即使是 nobody 用户也可以利用它),并且操作系统甚至不需要作为 VMware 虚拟机运行。这在 open-vm-tools 作为通用 Linux 发行版/镜像默认分发,或者在允许非特权用户从受信任源安装附加软件而无需 root 身份验证的环境中(这是例如 PackageKit 项目支持的模型)可能相关。

漏洞影响

/dev/uinput

获得对 /dev/uinput 设备的文件描述符的访问权限,允许攻击者创建任意用户空间输入设备并将其注册到内核。这包括向内核发送合成的键盘或鼠标事件的可能性。示例程序 uinput-inject.c 演示了如何利用此功能向图形或文本登录控制台的本地用户会话注入任意键盘输入。因此,此攻击向量接近于任意代码执行,但有一个限制,即需要存在本地交互式用户。

此漏洞的方面可用于在获得低特权访问后提升权限,例如通过远程安全漏洞。在共享访问的多用户机器上,它可用于准备一次攻击,其中后台进程等待受害者用户登录到机器,然后将其恶意输入注入其会话。

由于 /dev/uinput 不是 VMware 特有的,因此此攻击向量基本上在非 VMware 环境中也可用。

以下是使用附带程序进行利用的示例运行,前提是已安装 vmware-user-suid-wrapper 并且可用编译器

user$ gcc -O2 vmware-race-fd.c -ovmware-race-fd
user$ gcc -O2 uinput-inject.c -ouinput-inject

user$ ./vmware-race-fd
vmware-user: could not open /proc/fs/vmblock/dev
vmware-user: could not open /proc/fs/vmblock/dev
[...]
/usr/bin/vmtoolsd running at 12226
Found fd 3 for /dev/uinput in /usr/bin/vmtoolsd
Executing sub shell which will inherit the snatched file descriptor 4 (check /proc/self/fd)

user$ ls -l /proc/self/fd/4
l-wx------ 1 user group 64 Jul 25 13:43 /proc/self/fd/4 -> /dev/uinput

user$ ./uinput-inject 4
Sleeping 3 seconds for input subsystem to settle
completed one iteration
completed one iteration

这将不断地将“you have been hacked”这一行写入当前系统显示屏上选择的任何会话。

/var/run/vmblock-fuse/dev

据我理解,该文件由 vmware-vmblock-fuse 守护进程创建,并代表一个控制文件。FUSE 文件系统用于实现对 VMware 主机和 VMware 客户机之间共享文件夹的访问。根据 文档,该文件允许添加、删除或列出共享文件夹中的块。

因此,对该文件描述符的访问打破了客户机系统中不同用户关于共享文件夹访问的界限。共享文件夹内容的完整性可能会受到损害。也可能将信息从共享文件夹泄露到非特权用户的上下文中。

根据实际环境,如果例如恶意代码被写入共享文件夹,并且这些代码可能在 VMware 主机系统上执行,则可能导致代码执行。

vmware-fuse 文档提到了允许非特权用户访问此控制文件的展望,但在目前的形式下,这个想法对我来说似乎不安全。

我没有更深入地研究这方面的实际利用。

建议的修复

要解决这个问题,必须防止在执行 vmtoolsdvmware-user-suid-wrapper 进程的“dumpable”属性被重置。实现这一目标的一种方法是将权限撤销逻辑移入 vmtoolsd 中。只要进程以 setuid-root 上下文运行,“dumpable”属性就不会被重置。然后 vmtoolsd 可以撤销权限,并使用 O_CLOEXEC 标志标记特权文件描述符,以防止它们被意外继承给进一步的子进程,这可能会再次导致相同的问题。

更新:这是上游提供的补丁所采用的途径。

作为一种紧急措施和/或加固措施,可以限制对 vmware-user-suid-wrapper 的访问,只允许特权组(例如 vmware-users)的成员访问。这将减少攻击面,并防止例如被攻破的 nobody 用户账户利用这一点。

在加固方面,vmware-user-suid-wrapper 还可以添加一些代码来清理从非特权上下文中传递的环境变量,这是 setuid-root 二进制文件中安全问题的常见来源。至少 PATH 变量应该重置为安全值,以避免在查找 execve() 的可执行文件时出现任何未来意外。

时间线

2023-07-25 我已将发现报告给 security@vmware.com,并提议协调披露。
2023-08-23 VMware 安全部门要求在 11 月初发布,这超出了我们最长 90 天的披露政策。我们勉强同意了这一例外。
2023-10-20 VMware 在未告知我的情况下,将问题和错误修复与 distros 邮件列表共享。同时,我已收到关于 2023-10-26 提前发布的通知。我要求在发布前审查补丁草案的请求未得到满足。
2023-10-27 通用发布日期已到。

参考文献