Safari沙箱逃逸:利用授权漏洞与语音合成服务实现Root权限提升

本文详细分析了如何利用CVE-2017-2535授权框架逻辑漏洞绕过Safari沙箱限制,结合语音合成服务(CVE-2017-2534)的配置缺陷实现代码执行,最终通过进程PID重用技术获取系统挂载权限并完成权限提升。

Pwn2Own: Safari沙箱第二部分——绕过限制获取Root权限

2017年7月6日

• 作者:niklasb, saelo

本系列文章的前一部分讨论了macOS 10.12.4上的本地权限提升漏洞。要从Safari沙箱中利用该漏洞,还需要两个基本能力:我们需要一个具有system.volume.internal.mount权限的授权令牌,以及能够在任意目录中创建符号链接的能力。这就引出了CVE-2017-2535/ZDI-17-356,这是Apple安全框架中的一个逻辑问题,允许我们绕过授权沙箱,以及CVE-2017-2534,这是语音合成服务的一个奇特配置,允许我们轻松在其上下文中执行任意代码。

由此产生的利用链不依赖于内存损坏问题,并能够将权限提升到root。实际上,这只有95%的真实性。我们还使用了CVE-2017-6977,这是一个在非沙盒用户态服务中的完全不起眼的NULL指针解引用,在同一代码库中有许多这样的漏洞。它本身不可利用,但我们需要它来崩溃服务并重新启动它。请继续关注。

回顾

为了利用CVE-2017-2533(diskarbitrationd中的TOCTOU问题),我们需要以下能力,其中一些我们已经具备:

  • 访问diskarbitrationd的IPC端点 ✓
  • 对任意目录的写访问 ✓
  • 获取挂载授权令牌的能力 ✗
  • 创建符号链接的能力 ✗

授权令牌和权限

macOS中的授权令牌是使用AuthorizationCreate API创建的。它由服务com.apple.authd提供,该服务管理活动令牌列表并捕获创建它们的用户和进程。令牌可以通过AuthorizationMakeExternalForm/AuthorizationCreateFromExternalForm API在进程之间序列化和反序列化来复制和共享。外部形式基本上只是一个随机的12字节句柄,与authd服务内的令牌相关联。有趣的是,在令牌被导出并由不同进程再次导入后,最初创建令牌的进程可以退出而不会使令牌失效。authd只要存在来自连接进程的引用,就会保持令牌活动。

令牌可以与许多权限相关联——这些是简单字符串,定义在文件/System/Library/Security/authorization.plist中,该文件还指定了关于谁被允许获取它们的规则(例如“is-admin”,指定任何管理员用户都被允许获取此权限)。向令牌添加权限的API调用是AuthorizationCopyRights。显然,具有给定权限的令牌可以作为调用者被authd允许获取该权限的证据。这是一些macOS服务和工具(如authopen实用程序)处理授权的方式。

以下shell片段简要展示了授权框架的工作原理。它运行一个小的swift程序来获取令牌并将其导出到文件中。在这种情况下,authd将打开一个对话框要求用户权限(“swift想要进行更改…”),用户必须允许。其他权限(特别是system.volume.internal.mount)可以在没有用户交互的情况下获取,只要用户在admin组中。之后,authopen读取并内部化令牌,检查令牌是否包含所需权限(在这种情况下是sys.openfile.readonly./tmp/cantread.txt),然后继续打开和读取文件。请注意,authorize.swift进程需要至少保持活动状态,直到authopen再次内部化令牌,从而增加其在authd内的引用计数。

ls -l cantread.txt -r——– 1 root wheel 7 Jul 5 23:56 cantread.txt

swift authorize.swift # 将打开一个授权对话框,必须批准 External form written to ./token Press enter to quit ^Z [1] + 3310 suspended swift authorize.swift

/usr/libexec/authopen -extauth cantread.txt < token THE_FILE_CONTENT

在错误进程上执行权限检查

除了指定可以使用令牌获取哪些权限的规则外,authd服务还对沙盒化令牌施加了额外限制:创建令牌的进程或想要向其添加权限的进程都不能被沙盒化,或者如果它们被沙盒化,沙盒规则必须包含对所请求权限的显式“authorization-right-obtain”规则,可能看起来像这样:

(allow authorization-right-obtain (right-name “system.volume.internal.mount”))

一个旧版本的authd源代码可用,并显示了此检查的实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
static bool _verify_sandbox(engine_t engine, const char * right)
{
    pid_t pid = process_get_pid(engine->proc);
    if (sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) {
        LOGE("Sandbox denied authorizing right '%s' by client '%s' [%d]", right, process_get_code_url(engine->proc), pid);
        return false;
    }

    pid = auth_token_get_pid(engine->auth);
    if (auth_token_get_sandboxed(engine->auth) && sandbox_check(pid, "authorization-right-obtain", SANDBOX_FILTER_RIGHT_NAME, right)) {
        LOGE("Sandbox denied authorizing right '%s' for authorization created by '%s' [%d]", right, auth_token_get_code_url(engine->auth), pid);
        return false;
    }

    return true;
}

我们的场景如下:我们可以在Safari渲染器进程(WebContent)内创建一个令牌,并将其传递给diskarbitrationd。当它尝试获取system.volume.internal.mount权限时,第一个检查(关于令牌用户,在这种情况下是diskarbitrationd)将通过,但第二个关于其创建者的检查将失败。

请注意,在两个sandbox_check中,相应的进程由其PID标识。然而,正如我们之前所见,创建者进程可以退出而不会使令牌失效。此外,macOS上的PID限制在0到99999之间,并且可能会被重用。因此,沙盒检查可能会在错误的进程上执行!这就是为什么我们想要崩溃一个非沙盒服务:如果我们能让它占用与创建我们令牌的沙盒进程相同的PID,两个检查都将通过,并且可以添加权限。

巧合的是,这个错误与CVE-2017-7004非常相似,该漏洞在Pwn2Own后几天由Google Project Zero的Ian Beer报告,并且可以以相同的方式利用以在iOS上获取权限。

当然,这个错误只有在我们可以短时间内创建近100k个进程时才现实可利用(在Pwn2Own每次尝试有5分钟的时间限制)。我们最初的想法是使用NULL指针解引用或其他简单错误重复崩溃某些系统服务,并让launchd服务重新启动它们。然而,它似乎实现了某种形式的速率限制,因为在连续两次崩溃后,需要10秒才能重新启动服务。第二个选项是使用exec()、fork()或vfork()系统调用。它们并不理想,因为它们通常不被应用程序沙盒允许,但也有例外。

speechsynthesisd中的代码执行——错误还是特性?

实际上只有两个服务:

  • 可以从Safari沙盒访问;
  • 有沙盒配置文件,因此可能经过很少审查,但
  • 仍然支持分叉和创建符号链接,因此足够强大以实现我们的利用。

即,这两个是众所周知不安全的com.apple.fontd以及com.apple.speechsynthesisd,该服务实现了Apple的语音合成API。

幸运的是,对于后者,代码执行不是错误……而是一个特性!SpeechSynthesisRegisterModuleURL字面上接受用户控制的文件路径并将其视为CFBundle,以便从中加载动态库并将其用作语音识别插件。没有签名验证,因此可以在库加载期间通过初始化器运行任意代码。这本身可能被认为不那么糟糕,因为服务是沙盒化的,但在macOS 10.12.4之前,它还包含以下沙盒规则:

(allow file-read* file-write* (regex #"^(/private)?/var/folders/.+/com.apple.speech.speechsynthesisd.*"))

请记住,Safari渲染器对目录/private/var/folders//C/com.apple.WebKit.WebContent+com.apple.Safari具有完全读写访问权限。

特别是,两个进程都可以从/private/var/folders//C/com.apple.WebKit.WebContent+com.apple.Safari/com.apple.speech.speechsynthesisd读取和写入。

因此,在该目录中伪造CFBundle并将其从渲染器加载到speechsynthesisd中是没有问题的。这里某处有一个错误,因为speechsynthesisd的沙盒比渲染器的沙盒宽松。Apple决定错误在正则表达式中,在macOS 10.12.5更新中更改如下:

(allow file-read* file-write* (regex #"^(/private)?/var/folders/[^/]+/[^/]+/[^/]+/com.apple.speech.speechsynthesisd.*"))

整合各部分

此时,我们拥有完整沙盒逃逸链的所有要素:

  • CVE-2017-2533:给定创建符号链接和获取system.volume.internal.mount权限的能力的本地权限提升
  • CVE-2017-2535:给定分叉进程和按需生成非沙盒进程的能力,获取上述权限的能力
  • CVE-2017-2534:创建符号链接和使用vfork系统调用的能力
  • CVE-2017-6977:nsurlstoraged(非沙盒)中的NULL指针引用,导致其重新启动

最终利用的序列可以粗略总结如下:

完整的沙盒逃逸利用代码可以在我们的GitHub上找到。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计