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,这是一个在非沙盒用户态服务中的完全不起眼的空指针解引用漏洞,同一代码库中存在许多类似问题。它本身不可利用,但我们需要它来崩溃服务并重启它。敬请关注。
回顾
为了利用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)可以在没有用户交互的情况下获得,只要用户在管理员组中。之后,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源代码显示了此检查的实现:
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上获得权限。
当然,这个错误只有在我们可以短时间内创建近10万个进程时才现实可利用(Pwn2Own每次尝试有时间限制5分钟)。我们最初的想法是使用空指针解引用或其他简单错误反复崩溃某些系统服务,并让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/
特别是,两个进程都可以从/private/var/folders/
因此,在该目录中伪造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(非沙盒)中的空指针引用,导致其重新生成
最终漏洞利用的序列可以粗略总结如下:
完整的沙盒逃逸漏洞利用代码可以在我们的GitHub上找到。