写在前面的话

本文由安全研究人员Gergely发表于他自己的博客,并在文中详细介绍了macOS中的一个本地提权漏洞(CVE-2022-26704)。该漏洞已上报给了苹果安全奖励计划ABS,并在上报后的322天拿到了漏洞奖金,目前苹果已将该漏洞成功修复。

关于漏洞

这个漏洞名为batsignal,是macOS中的一个本地提权漏洞,该漏洞允许研究人员在Spotlight(聚焦搜索功能)中将非特权用户(包括访客用户在内)提升为root权限。

Spotlight介绍

Spotlight是macOS系统中的一个搜索服务,它可以对系统中的磁盘内容进行索引,而它本身对于研究人员来说也是非常有意思的一个研究目标。

Spotlight服务由多个守护进程组成,其中我们最关心的两个如下:

mds:以root权限运行,拥有全磁盘访问权限(FDA);
mds_stores:以root权限运行;

这两个组件所做的事情略有不同,沙盒也不一样,但都是以root权限运行的,这个才是最重要的。

为了完成搜索任务,Spotlight必须能够访问系统上的文件,这可能是这些守护进程以root身份运行的原因。我们现在暂时不去分析该服务是如何实现文件内容解析的,因为我们要先分析一下我发现的一个奇怪情况,即“卷的索引文件存储在卷上”。

值得注意的是,这是一个典型的速度与安全性的权衡:一方面,随卷携带索引可以保持可用性并保持搜索速度。另一方面,攻击者可以使用此功能尝试在计算机上执行远程代码,或在本地升级权限。

我们之所以提到这一点,是因为Spotlight确实能够在本地存储这些索引文件:它会对网络卷这样做,但不会对本地磁盘这样做。很显然,macOS的开发人员也意识到了这一点,当然也有可能是为了不让卷索引占用太多的本地磁盘空间。

漏洞分析

Spotlight会在卷中一个受SIP保护的目录(名为/.Spotlight-V100)中执行文件操作。我们同时还发现,在挂载的文件系统上单独运行root守护进程并没什么,但系统会提示:“只有当你能保证卷是可信的时,这样做才是安全的”。

据我们所知,在普通的Linux系统上,普通用户是无法装载卷的。除此之外,它们不能触发root守护进程并在新安装的文件系统上进行操作。但macOS不一样,在macOS上,磁盘挂载行为无处不在,这也是任何用户安装系统后首先要做的事情之一。没有提示,没有密码,没有问题。正好,也没有安全保障。

任意的macOS都可以实现以下操作:

1、挂载自己的磁盘镜像;
2、卸载、更新和移除挂载点;
3、设置union和noowners之类的挂载标记;

这样一来,攻击者便有机可乘了:

1、他们可以准备一个恶意镜像;
2、他们可以对守护进程执行恶意操作,然后修改挂载标记;
3、他们可以直接使用noowners作为挂载标记,并向任何只有root用户可以写入的文件中写入任意内容;

这样一来,我们就可以轻松解除SIP的保护,因为就文件系统而言,SIP只是一个正则表达式引擎而已。那么,我们该如何解除SIP的保护呢?

用户装载卷上的通用SIP绕过

为了保护“/.Spotlight-V100”目录,SIP将尝试按名称来与它匹配,它很可能使用的是mount-relative-regex指令。我们之所以说是“很可能使用”,是因为它允许Spotlight守护进程访问它。这在mds和mds_stores的沙盒策略中是可用的,但它阻止其他人访问的机制对我们来说是不可见的。因此,我们没有把握确定这是同一机制,但这肯定是一个安全的选择。

现在,我们需要尝试修改卷名称,在装载卷时这是不允许的,但我们可以通过卸载它来绕过SIP。编辑磁盘映像就像编辑其他任何东西一样:现在它不再是文件系统,它只是一堆字节数据的集合。

我们有在线和离线两种方法来实现我们的目标,离线意味着可以提前准备好磁盘镜像,而在线意味着需要在磁盘挂载和目标应用正在运行的时候去修改卷内容。

由于在线方法非常的麻烦,我们这里使用离线方法的来做介绍。使用离线(卸载)磁盘修改方法,我们可以搞定任何受保护的文件/目录:

1、我们可以删除、修改和替换任何受保护的文件/目录;
2、我们可以跟卷root硬连接以便后续访问;
3、我们可以修改权限;
4、....

在我们修改镜像之前,我们需要选择我们的文件系统,这里有很多选择,但HFS+的效果是最好的。接下来,我们还需要让文件系统去做一些它们“本不该做”的事情,实现这个目标有两种方法:

方法一:使用真正的文件系统驱动器

我们可以使用Linux的文件系统驱动器,它也能识别HFS+,而且限制相比macOS的HFS+驱动器要更少。我们还需要使用强制参数才能将镜像挂载为rw。或者,我们可以使用任何其他可以可靠地写入我们选择的文件系统的东西。此时,FUSE会是一个不错误的选择,但我们并没有测试过。

方法二:直接编辑源代码

一个更野蛮但有效的解决方案是手动编辑二进制文件,但需要注意的是,这种方法对于任何复杂的(通常是较新的)文件系统来说都是非常困难的,只有像FAT和HFS+这样简单的旧版文件系统才适用。

实际上,我们只需要修改文件系统上的一个字符串,就能够实现我们的目标了。对于HFS+,我们只需要做下列事情:

buf.replace(b’\x31\x00\x30\x00\x30\x00’, b'\x39\x00\x30\x00\x30\x00’)

上述的Python代码会将100修改为900,并导致.Spotlight-V100 变为 .Spotlight-V900。

上述代码不是固定的,只要能破坏原始正则表达式就可以了。

下面给出的是我们绕过SIP保护的步骤:

1、创建并挂载一个HFS+磁盘;
2、使用mdutil -i on /MY/VOLUME命令开启Spotlight,此时/.Spotlight-V100目录便会在磁盘卷中被创建;
3、卸载磁盘;
4、编辑磁盘二进制源码,破坏原始正则表达式,将.Spotlight-V100 变改为 .Spotlight-V90;
5、挂载磁盘;
6、在.Spotlight-V90中做任何你想做的事情;
7、卸载磁盘;
8、恢复对二进制源码的修改;
9、重新挂载磁盘,这一次Spotlight将会运行并执行恶意内容/代码;

漏洞利用场景

撇开架构错误不谈,Spotlight中的一个具体缺陷是它写入文件内容的方式。由于Spotlight认为自己一直会受到SIP的保护,所以在处理文件时就没有那么的小心了。

在Spotlight中,我们可以通过在卷上写入“可缓存”文件来触发不安全的文件写入。当这种情况发生时,文件的内容将被编入索引,提取的文本将被写入缓存目录中的新文件。

此时将会生成一个新的文件:

./mnt/.Spotlight-V100/Store-V2/[UUID]/Cache/0000/0000/0000/[X].txt

其中[UUID]是一个UUID,[X]则是原始文件的索引节点编号。

这里支持很多文件类型,但我选择使用一个名为payload.pdf的PDF来做测试,其内容如下:

ALL ALL=(ALL) NOPASSWD:ALL

首先,[X].tmp会被写入,并调用rename()将其重命名为[X].txt,写入操作将由open()或write()执行,随后open()便调用符号连接。

这很明显是一个安全漏洞!

漏洞利用步骤如下:

1、创建一个磁盘;
2、在./mnt上挂载磁盘;
3、将payload.pdf拷贝到磁盘卷;
4、开启Spotlight;
5、等待Payload.pdf被索引,然后会创建缓存目录和文件;
6、卸载磁盘;
7、编辑磁盘镜像内容;
8、挂载磁盘;
9、将Cache缓存目录移动到磁盘卷根目录,使用一个符号连接替换它;
10、卸载磁盘;
11、恢复磁盘镜像内容的修改;
12、挂载磁盘;
13、创建指向/etc/sudoers的硬连接/Library/Caches/com.apple.Spotlight/whatever;
14、在./mnt/Cache/0000/0000/0000/[X].tmp创建指向/Library/Caches/com.apple.Spotlight/whatever的符号连接;
15、从Cache中删除[X].txt;
16、重新索引;
17、此时,/etc/sudoers将被我们的Payload重写;

总结

这是一个价值17000美元的安全漏洞,并且已经通过苹果的漏洞奖励计划上报给了苹果公司。简而言之,如果攻击者能够控制文件系统,那么一切安全保护或安全访问机制都将“不复存在”。