继续我的 macOS 迁移系列博文,我想稍微讨论一下 macOS 应用沙盒。
强烈建议先阅读 macOS 应用结构 这篇博文——我会假定读者了解应用(App)与进程(任务)之间的区别,对 launchd 及其与应用启动的关系有一定了解。
我第一次了解 macOS 沙盒时,天真地尝试创建一个恶意的 Word 宏。
这在 Windows 生态系统中(至今)仍然是一个非常常见的攻击向量,所以我想看看我是否能够直接启动进程并肆意破坏。
好吧,在 macOS 上事情并没有那么简单——例如,我确实能够运行进程,但似乎它们做不了太多事情。
投放文件时总是遇到令人费解的 Operation not permitted 错误——这到底是怎么回事?
我开始阅读一些关于 macOS 和 Word 的资料,然后看到了 Adam Chester(就职于 MDSec)写的这篇优秀博文。我强烈推荐阅读这篇博文,但我在这里把其中的发现总结一下:
macOS 曾经有一个可用的工具叫做 sandbox-exec,可以在沙盒中执行命令。虽然它已被弃用,但它能揭示很多东西。你可以从它的手册页中看到,它接收一个 profile(配置文件),因此我们可以推断沙盒规则是维护在配置文件中的。这些配置文件可以有各种各样的形式和形态——文件、预定义名称,甚至可以是字面字符串。
手册页还说明开发者应该使用应用沙盒(App Sandbox)功能。阅读更多相关资料后,我明白了沙盒规则是被嵌入到二进制文件中的,在我们的例子中,位于 /Application/Microsoft Word.app/Contents/MacOS/Microsoft Word 下(如果这对你来说很陌生,请查看我的 macOS 应用结构博文)。
虽然你可以轻松地手动提取它们,但最好还是使用一个工具:codesign:
```jbo@McJbo ~ % codesign -dv --entitlements - /Applications/Microsoft\ Word.app/Contents/MacOS/Microsoft\ Word
Executable=/Applications/Microsoft Word.app/Contents/MacOS/Microsoft Word
Identifier=com.microsoft.Word
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=351454 flags=0x10000(runtime) hashes=10972+7 location=embedded
Signature size=8980
Timestamp=Apr 10, 2023 at 8:09:50 AM
Info.plist entries=52
TeamIdentifier=UBF8T346G9
Runtime Version=13.1.0
Sealed Resources version=2 rules=13 files=28766
Internal requirements count=1 size=180
[Dict]
[Key] com.apple.application-identifier
[Value]
[String] UBF8T346G9.com.microsoft.Word
[Key] com.apple.developer.aps-environment
[Value]
[String] production
[Key] com.apple.developer.team-identifier
[Value]
[String] UBF8T346G9
[Key] com.apple.security.app-sandbox
[Value]
[Bool] true
...
[Key] com.apple.security.temporary-exception.files.absolute-path.read-only
[Value]
[Array]
[String] /Library/Preferences/com.microsoft.office.licensingV2.plist
[String] /Library/Application Support/Microsoft/
...
[Key] com.apple.security.temporary-exception.sbpl
[Value]
[Array]
[String] (allow file-read* file-write* (require-all (vnode-type REGULAR-FILE) (regex #"(^|/)~\$[^/]+$")) )
[String] (deny file-write* (subpath (string-append (param "_HOME") "/Library/Application Scripts")) (subpath (string-append (param "_HOME") "/Library/LaunchAgents")) )
[Key] com.apple.security.temporary-exception.shared-preference.read-only
[Value]
[Array]
[String] com.ThomsonResearchSoft.EndNote
...
这里有很多内容需要解读,让我们记下一些高层要点:
-dv 标志,分别代表 display 和 verbose。然后,--entitlements 会展示与应用或二进制文件关联的 entitlements(授权)(是的,codesign 对两者都适用)。我们会在另一篇博文中深入探讨 entitlements,但就目前而言,我们可以说它们反映了应用的能力,其中一项表明该应用是沙盒化的(com.apple.security.app-sandbox 的布尔值为 True)。plists 的人(再次,在[我的 macOS 应用结构博文]中)可能会怀疑这个键值字典是某个属性列表(property list)的表示形式,他们猜对了。com.apple.security.temporary-exception.files.absolute-path.read-only 提到了应用被允许读取的绝对路径数组。com.apple.security.temporary-exception.sbpl 下的正则表达式也在这里——它用于创建那些 Word 非常钟爱的臭名昭著的 ~$whatever.docx 临时文件。注意这些沙盒规则有多么强大!
在我之前提到的 MDSec 2018 年的博文中,com.apple.security.temporary-exception.sbpl 下的 deny file-write* 部分并不存在,这允许宏创建具有任意内容的文件,例如 /Library/LaunchAgents/~$evil.plist。为什么这能逃离沙盒?
LaunchAgents 和 LaunchDaemons 是 macOS 中众所周知的(合法)持久化机制。我之前提到过它们,但你可以把它们想象成服务(如果你来自 Windows 世界的话)——LaunchDaemons 在操作系统启动时开始运行(因此存在于用户会话之外),而 LaunchAgents 在用户登录时启动。
有趣的是,两者都是用简单的 plist 文件描述的。以下是我的 OneDrive 更新程序的一个例子:
jbo@McJbo ~ % plutil -p /Library/LaunchAgents/com.microsoft.OneDriveStandaloneUpdater.plist
{
"Label" => "com.microsoft.OneDriveStandaloneUpdater"
"Program" => "/Applications/OneDrive.app/Contents/StandaloneUpdater.app/Contents/MacOS/OneDriveStandaloneUpdater"
"ProgramArguments" => [
]
"RunAtLoad" => 1
"StartInterval" => 86400
}
这些 LaunchAgents 和 LaunchDaemons 由 launchd(还记得那个进程吗?)启动,因此它可以逃离沙盒,因为 launchd 并不知道该 plist 是否来自沙盒化进程(即使它知道——它又如何知道该应用哪些沙盒规则呢?)。
这种利用 launchd 逃离 macOS 沙盒的概念被广泛使用,事实上,我自己过去就曾使用过。
省去你几次点击——思路如下:
launchd 负责启动 macOS 应用。这些应用可以通过双击启动,也可以通过其他方式启动——例如,点击 zip 文件会使用 Archive Utility(归档实用工具),因为它与 zip 文件相关联。launchd 启动应用的另一种方式是使用 open 命令。open 命令功能丰富——你可以使用它的一些有趣特性,例如选择应用、选择要打开的文件名,甚至可以提供完整的命令行参数。Python 应用(在新的原版 macOS 设备上已不存在)来启动 Python,并带有一个 stdin 参数,该参数实际上将标准输入重定向到我投放的一个文件(由于 Word 的限制,那个文件是 ~$evil.py)。launchd 运行了一个未沙盒化的 Python 应用实例,它开始从包含任意 Python 命令的 ~$evil.py 中读取内容,从而实质上逃离了沙盒。在其他披露中也有类似的想法(一个很好的例子在这里),但思路都是一样的。我相当确定在明面上还有更多!
有一个值得一提的优秀博文来自 Wojciech Regula——这次聚焦于 Terminal(终端)应用和环境变量操纵。你应该读一读!
MDSec 发现的那个问题特定于 Office——并通过更严格的规则进行了修复。
那些滥用 LaunchServices(这是通过 launchd 启动应用的框架名称)的问题则更为通用——因此 Apple 不得不修复它们。
我注意到的一件事是,Word 投放的文件现在会带有 com.apple.quarantine 扩展属性,是的,就是我在 Gatekeeper 入门 博文中提到的那个。
事实证明,这个隔离属性是针对某些攻击的一种加固措施——例如,Terminal(终端)应用拒绝启动带有该属性创建的 shell 脚本。顺便说一下,这就是我不得不为 Python 使用 --stdin 选项的原因。
正如 Gergely Kalman 所指出的——Apple 已经对 open 二进制文件添加了进一步的检查,以加固对这类漏洞利用的防御。如果调用进程处于沙盒中,--stdin、--args 和其他命令行标志似乎会被忽略。然而,open 只是通过 IPC 调用 LaunchServices(位于 launchd 中),而且恰好有相应的 API,例如 LSOpenURLsWithRole。
我还没有调查 LaunchServices 本身是否也得到了加固——如果没有,那么我相信类似的沙盒逃离可以轻易实现。
我们简要讨论了 macOS 的另一项技术——沙盒。我们看到了它有多么强大和可配置,以及它是如何被攻破的。
我们还将一些东西联系了起来——应用如何与沙盒规则协同工作,launchd 启动应用如何不仅仅破坏进程树,以及 plist 文件如何被用于行善或作恶——这次是持久化(LaunchAgents 和 LaunchDaemons)。
幸运的是,我们甚至将 Gatekeeper 入门 博文中的 com.apple.quarantine 扩展属性联系了起来,并解释了它如何被用作抵御沙盒逃离的额外加固。还不错!
在接下来的几篇博文中,我们将探索 macOS 中更多的安全机制,并可能讨论攻破它们的策略。
敬请期待!
Jonathan Bar Or (https://jonathanbaror.com)