一、概述
大家也许比较少听说过macOS上面的MACL,实际上这是一个macOS上面的隐藏功能,用于支撑Apple的“User-Intent”(用户意图)概念。这个概念是macOS在隐私控制领域的一种转型,避免像之前一样无休止地打扰用户。我想现在我们都知道,这些不断弹出的警报对于攻击者而言是多么的麻烦。
对攻击者而言,能够在终端上运行是必不可少的,但遗憾的是,随着macOS的不断升级,实现这一目标也越来越困难。即使我们已经攻陷了终端,并成功提升到root身份,此时也有很多存储在文件中的数据是不可用的。并且,如果出现了任何一步失误,都可能导致前功尽弃:
在之前,我尝试过将dylib加载到具有权限(entitlement)的特定Apple应用程序中,从而实现Apple隐私控制(也称为TCC)的绕过。尽管有了之前的经验,但要实现难度较高的工作时,我总是希望能储备1-2种比较深入的技术方法。
因此,在本文中,我们将研究macOS的用户意图(User-Intent)系统工作原理,分析其暴露的攻击面,分析一个已经发现的漏洞(CVE-2020-9968),同时站在攻击者的视角,去研究如何滥用User-Intent功能来绕过TCC。
二、User-Intent分析
经过最近与@rbmaslen的沟通交流,我们花了一些时间来观察在TCC绕过时出现的奇怪行为。建议大家也可以亲自尝试这种情况。
使用Catalina版本系统,打开例如Visual Studio Code之类的应用程序,选择“文件”-“打开”,这时会显示出通常的对话框,然后就可以选择桌面上的任何文件。但是,在单击“打开”后,我们会注意到两件事。首先,并不会看到“Visual Studio Code请求访问Desktop文件夹”的提示信息。即使我们在隐私设置中,阻止了Visual Studio Code对Desktop文件夹的访问,我们也仍然保留对文件的访问权限。
经过查证,我们发现在2019年WWDC会议上进行了说明。演讲者简要介绍了用户意图(User-Intent)和用户同意(User-Consent),我们可以明显看到,Apple正在尝试解决困扰Windows用户的UAC提示不断弹出的问题。
Apple在Catalina中的解决方案是创建一个方法,允许用户批准对文件或目录的访问,而不再每次都是通过对话框来明确要选择的选项。目前,有几种方式可以明确用户的意图:
1、用户通过“打开”或“保存”对话框选择的文件;
2、用户将文件从Finder拖拽到另一个应用程序窗口中;
3、用户在Finder中双击文件。
这种逻辑非常正确,如果用户在对话框中已经选择了一个文件,那么就不需要再次提示用户去同意了。
基于此,我花费了几天时间,去详细了解所有的组件,最终发现这是Apple为加强用户隐私保护打造的一个非常棒的系统。
接下来,聚焦用户层面,我制作了一个小型应用程序,在应用中可以通过“打开”对话框选择文件。完整的源代码可以在这里找到,我们重点关注这两种选择文件的方式:
// Uses the open() syscall to open a file handle - (IBAction)clickedOpenManual:(id)sender { NSString *val = [_openTextBox stringValue]; int fd = open([val cString], O_RDONLY); if (fd > 0) { [[self->_logTextBox documentView] insertText:@"Attempting to open file: Success\\n"]; } else { [[self->_logTextBox documentView] insertText:@"Attempting to open file: Failure\\n"]; } } // Uses the Open dialog to choose a target file - (IBAction)clickedOpen:(id)sender { NSOpenPanel* panel = [NSOpenPanel openPanel]; [panel setCanChooseDirectories:YES]; [panel beginWithCompletionHandler:^(NSInteger result){ if (result == NSModalResponseOK) { NSURL* theDoc = [[panel URLs] objectAtIndex:0]; [[self->_logTextBox documentView] insertText:[NSString stringWithFormat:@"Selected file: %@\\n", [theDoc path]]]; } }]; }
这个示例为我们提供了两种处理文件的方法,使用路径调用open()系统调用,或者由用户在“打开”对话框中选择文件。
如果我们选择open系统调用,并尝试打开桌面上的一个文件,就会看到熟悉的对话框。
在这里,如果选择“不允许”选项,会发现打开文件的尝试失败。
接下来,我们启动“打开”对话框,并选择目标文件。令人惊讶的是,尽管我们已经明确拒绝了访问,但此时的访问是成功进行的。
此外,如果重新启动应用程序,再提供相同文件的路径,这次没有再通过对话框进行选择,我们看到open方法可以成功打开文件。
这实际上是用户意图,macOS看到我们已经使用对话框选择了一个文件,这个文件又反过来赋予应用程序访问其内容的权限,此时就不会弹出任何警告。
现在,事情变得有趣了,我们看一下应用到使用命令选择的文件上的所有属性:
xattr -l ~/Documents/supersecretz.txt
我们看到的是这样的:
在这个功能背后,实际上是com.apple.macl属性在发挥作用。
三、com.apple.macl分析
在MACL属性中,通常包含一个头部值02 00,后面接着一个与允许访问该文件的应用程序相对应的UUID。对于每个系统、用户和应用程序来说,UUID都是唯一的,这意味着我们不能伪造这个值。我们不太确定这种实现方式是否符合隐私方面的要求,毕竟它提供了一个工具,可以查看用户使用应用程序访问了哪些文件。但无论如何,我们目前已经了解,这个MACL属性被添加之后,对应的应用程序和用户便可以重新访问文件,而无需再调整隐私设置。
为了说明这一点,我们将添加到上述测试文件中的MACL项直接应用到另一个文件,例如~/Desktop/secret.txt:
xattr -wx com.apple.macl 020091877181CB4E4D7F8004D7BFF6B58C58000000000000000000000000 ~/Desktop/secret.txt
正如我们所料,现在就可以直接从应用程序中打开该文件,不需要再进行隐私设置。
现在,我们尝试使用以下命令,从文件中删除MACL:
xattr -d com.apple.macl ~/Desktop/secret.txt
出乎意料的是,我们在这里看到MACL立即重新出现。这意味着,按照设计,一旦将MACL添加到文件中,就很难将其删除。这样的尝试也会导致在控制台中显示以下消息:
四、MACL在打开对话框中的应用
那么,为什么打开对话框会如此特别,以及如何将MACL添加到原本不应该访问的文件中呢?显然,我们自己的应用程序不能随意添加MACL,因为这可能会引起macOS隐私控制中存在一个巨大的漏洞,因此一定会有其他位置来控制这一点。
我们首先从实际负责“打开”对话框的AppKit.framework库开始。我们发现,该框架不仅仅包含AppKit库,还包括许多支持XPC服务的组件。
这是Apple框架常用的模式,这一模式可以允许将权限(entitlement)分配给服务,然后可以使用XPC从加载到我们的进程中的库请求服务。一个比较明显的XPC服务是com.apple.appkit.xpc.openAndSavePanelService.xpc。当我们查看其权限时,可以看到以下内容:
在这里,我们看到了很多私有的权限,包括非常值得关注的kTCCServiceSystemPolicyAllFiles,该权限授予对受到隐私保护的位置中所有文件的访问权,而无需提示用户。这对我们来说很有帮助,因为我们希望能够在添加MACL之前就实际访问到文件,不管TCC做了什么样的设置。但进行完这一步,还不能直接就将MACL应用到我们指定的文件上。
尽管这个服务能在不提示用户的情况下公开要访问目标文件所需的权限,但MACL属性的处理实际上是在内核完成的,具体是在Sandbox.kext内核扩展中,这意味着我们还需要访问内核,了解在里面发生了什么。
五、内核沙箱扩展
在查看从AppKit导入的符号时,我们发现了对以sandbox_extension_...开头的一系列函数的引用。这些函数通过调用“Sandbox”模块并调用所谓的“extension”来包装对sandbox_ms系统调用的调用。在我们的示例中,我们对从libSystem.dylib公开的方法(以sandbox_extension_issue_file...和sandbox_extension_consume开头)非常感兴趣。
我们先调用其中一种方法——sandbox_extension_issue_file_to_self。该调用使用了3个参数:
char* sandbox_extension_issue_file_to_self(const char *sandboxEnt, const char *filePath, int flags);
调用该函数,并提供我们可以访问的文件的路径后,发现返回的字符串如下所示:
f6b75b461bada9c3b2e73400359bc8d5844b4a3d4442700fd352fe45bcfd650b;00;00030000;00001b03;003d0fe5;000000000000001a;com.apple.app-sandbox.read;01;01000005;00000000000d1bbf;1d;/users/xpn/documents
这些内容非常奇怪,要进一步了解它们,还需要加载反汇编程序,并跳转到Sandbox.kext。
我们感兴趣的函数是_syscall_extension_issue,它带有两个参数:
_syscall_extension_issue(proc_t *proc, struct extension_issue_request *req);
在经过一些逆向后,发现第二个参数似乎是一个类似于以下内容的结构:
struct extension_issue_request { const char *sandbox_string; // 0x00 int cmd; // 0x08 const char *filePath; // 0x10 int flags; // 0x18 char *returnedToken; // 0x20 int pid; // 0x28 int res; // 0x30 }
我们用一些时间来对函数进行逆向,就可以找到Token中值得关注的部分:
f6b75b461bada9c3b2e73400359bc8d5844b4a3d4442700fd352fe45bcfd650b - HMAC-SHA256 of the token 00 - Sandbox extension cmd 00030000 - Flags 00001b03 - PID 003d0fe5 - PID Version 000000000000001a - Size of sandbox string com.apple.app-sandbox.read - Sandbox string 01 - Does file exist 01000005 - Filesystem ID 00000000000d1bbf - iNode ID 1d - Sandbox Storage Class /users/xpn/documents - Target file path
在继续之前,我们首先要讨论一下HMAC-SHA256哈希。用于HMAC的密钥实际上是在加载sandbox.kext模块时生成的,这意味着Token在重新启动后就会失效。
密钥设置为随机存储在_secret变量中的40个字节,因此暴力破解的方法是行不通的。
那么,我们如何处理这个返回的Token?要理解如何处理,我们需要查看另一个方法——_syscall_extension_consume,它带有两个参数:
_syscall_extension_consume(proc_t *proc, struct extension_consume_request *req);
再次通过反汇编这个函数,我们发现传递的请求可能类似于:
struct extension_consume_request { const char *token; int length; int *returnValue; };
因此,当我们的Token被解析时,会进行哪些检查?首先,是对HMAC-SHA256哈希的验证。
这里要说明一下,如果大家也像我一样,想要对HMAC哈希值进行时间攻击,Apple已经考虑过这种攻击方式。
假设HMAC-SHA256哈希值匹配,则接下来的调用过程中还会对PID和PID版本进行验证,以确保完全匹配。
如果验证通过,接下来根据Token包含的Sandbox扩展cmd值,输入到4条路径之一。为了实现我们的目标,选取了0x00,这会将我们带到_macl_record函数中,该函数使用以下参数:
_macl_record(proc_t *proc, const char *filename, bool fileExists, int fsID, int iNode, int res);
该函数对传递的参数执行大量检查。首先,确认传入的文件系统ID上是否存在索引节点。
如果这个文件存在,则添加MACL。
有趣的是,如果根据索引节点发现文件不存在,就会按照文件名进行查找,如果找到了同名文件,则会应用MACL。
至此,我们就掌握了应用程序到XPC驱动的对话框,再到MACL的用户意图(User-Intent)框架。接下来,就剩下寻找漏洞的工作了。
六、滥用User-Intent绕过TCC
既然我们已经了解用户意图(User-Intent)的工作原理,那么是否有方法可以滥用原理以实现绕过TCC?在掌握了内部原理后,我花费了几个小时的时间,寻找到了一个潜在的滥用漏洞。
要将MACL分配给无权访问的文件夹,最简单的一种方法是通过chroot容器。这意味着,我们需要具有root或sudo特权才能实现这一点,因为我们需要获得进行chroot调用的权限。根据以下方法,创建一个非常简单的容器:
# Add libs and progs required by our POC mkdir -p /tmp/jail/usr/lib/; cp -r /usr/lib/* /tmp/jail/usr/lib/ mkdir /tmp/jail/bin; cp /bin/bash /tmp/jail/bin # Add our POC cp /tmp/poc /tmp/jail/
创建容器后:
# Execute our chroot to grab a token sudo chroot /tmp/jail /bin/sh -c "mkdir -p /Users/xpn/Documents; /main issue /Users/xpn/Documents"
在执行后,我们能够得到:
现在,我们就有了/Users/xpn/Documents的chroot路径的Token。现在,如果我们尝试使用这个Token:
遗憾的是,现在还行不通,这是因为此时inode值仍然存在,并且根据我们之前的分析,如果inode存在,则它会优先于文件名。所以,我们调整命令,在生成Token后删除该目录:
sudo chroot /tmp/jail /bin/sh -c "mkdir -p /Users/xpn/Desktop; /main issue /Users/xpn/Desktop; rmdir /Users/xpn/Desktop"
这次如果我们使用Token,将可以访问Documents文件夹,从而完全绕过TCC:
当然,这适用于受到TCC保护的任何文件夹。
注意:我还不能100%确定其原因,但是rmdir需要从chroot内部执行。如果尝试执行rm /tmp/jail/Users/xpn/Desktop之类的操作,则应用Token后也无法访问受保护的文件夹。我非常想知道其本质原因,希望后续能对其进行进一步的研究。
通过上述分析过程,我们清晰地了解了为什么TCC有时候看起来会允许明显应该被阻止的内容。而本文涉及的漏洞,实际上需要对macOS内部原理有一定掌握后才能发现。
该漏洞已在macOS 10.15.6、iOS 14、watchOS 7和tvOS 14及以上版本实现修复。感谢Apple安全团队让漏洞披露过程变得如此轻松和快捷。
本文翻译自:https://blog.xpnsec.com/we-need-to-talk-about-macl/如若转载,请注明原文地址: