进程注入:用一个漏洞打破所有的macOS安全层

作者:Sec-Labs | 发布时间:

如果你用Xcode 13.2创建了一个新的macOS应用程序,你可能会注意到模板中的这个新方法。

- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
	return YES;
}

这是在Xcode模板中添加的,以解决我们报告的一个进程注入漏洞!

在2021年10月,苹果修复了CVE-2021-30873。这是一个影响(基本上)所有基于 macOS AppKit 的应用程序的进程注入漏洞。我们向苹果公司报告了这一漏洞,以及利用这一漏洞逃出沙盒、将权限提升到root并绕过SIP的文件系统限制的方法。在这篇文章中,我们将首先描述什么是进程注入,然后是这个漏洞的细节,最后是我们如何滥用它。

这项研究还发表在2022年美国黑帽大会和DEF CON 30上。

进程注入

进程注入是指一个进程在另一个进程中执行代码的能力。在Windows中,使用这种方法的一个原因是为了逃避反病毒扫描器的检测,例如通过一种被称为DLL劫持的技术。这允许恶意代码假装是一个不同的可执行文件的一部分。在macOS中,由于两个应用程序的权限不同,这种技术的影响会明显大于此。

在经典的Unix安全模型中,每个进程都作为一个特定的用户运行。每个文件都有一个所有者、组和标志,确定哪些用户被允许读取、写入或执行该文件。作为同一用户运行的两个进程具有相同的权限:假定它们之间没有安全边界。用户是安全边界,进程不是。如果两个进程以相同的用户身份运行,那么一个进程可以作为调试器附加到另一个进程上,允许它读取或写入另一个进程的内存和寄存器。root用户是一个例外,因为它可以访问所有文件和进程。因此,root总是可以访问计算机上的所有数据,无论是在磁盘上还是在RAM中。

这在本质上是与macOS相同的安全模式,直到引入SIP,也被称为 "无根"。这个名字并不意味着不再有root用户,但现在它本身的功能就不那么强大了。例如,某些文件不再能被根用户读取,除非该进程也有特定的权限。权限是为可执行文件生成代码签名时包含的元数据。检查一个进程是否拥有某种权限是macOS中许多安全措施的一个重要部分。Unix的所有权规则仍然存在,这是在它们之上的一个额外的权限检查层。某些敏感文件(如Mail.app数据库)和功能(如网络摄像头)不再可能只拥有root权限,而是需要额外的权限。换句话说,权限升级并不足以完全破坏Mac上的敏感数据。

例如,使用以下命令,我们可以看到Mail.app的权限。

$ codesign -dvvv --entitlements - /System/Applications/Mail.app

在输出中,我们看到以下权利。

...
	[Key] com.apple.rootless.storage.Mail
	[Value]
		[Bool] true
...

这就是授予Mail.app读取SIP保护的邮件数据库的权限,而其他恶意软件将无法读取它。

除了权利,还有由透明度、同意和控制(TCC)处理的权限。这是一种机制,应用程序可以通过这种机制请求访问,例如,网络摄像头、麦克风和(在最近的macOS版本中)文件,如文档和下载文件夹中的文件。这意味着,即使不使用Mac应用程序沙盒的应用程序也可能无法访问某些功能或文件。

当然,如果任何进程都可以作为调试器附在同一用户的另一个进程上,那么权利和TCC权限就没有用了。如果一个应用程序可以访问网络摄像头,但另一个没有,那么一个进程可以作为调试器附加到另一个进程,并注入一些代码来窃取网络摄像头的视频。为了解决这个问题,调试其他应用程序的能力已被严格限制。

将一个已经使用了几十年的安全模型改变为一个更严格的模型是很困难的,尤其是在像macOS这样复杂的东西中。附加调试器只是一个例子,还有许多类似的技术可以用来将代码注入到不同的进程。苹果公司已经压制了许多这样的技术,但其他许多技术可能仍未被发现。

除了苹果自己的代码外,这些漏洞也可能发生在第三方软件中。在一个特定的应用程序中发现进程注入漏洞是很常见的,这意味着该应用程序的权限(TCC权限和权利)会被所有其他进程抢占。获得这些修复是一个困难的过程,因为许多第三方开发者并不熟悉这种新的安全模式。报告这些漏洞往往需要充分解释这种新的模式! 特别是Electron应用程序因容易被注入而臭名昭著,因为有可能在不使代码签名无效的情况下替换其JavaScript文件。

比一个应用程序中的进程注入漏洞更危险的是影响到多个,甚至所有应用程序的进程注入技术。这将使人们能够访问大量不同的权限和TCC权限。一个影响所有应用程序的通用进程注入漏洞是一个非常强大的工具,我们将在这篇文章中展示。

保存状态的漏洞

在关闭Mac时,它会提示你是否应该在下次登录时重新打开当前打开的窗口。这是功能上称为 "保存状态 "或 "持久用户界面 "的一部分。

b018d7ad23085724

当重新打开窗口时,它甚至可以恢复某些应用程序中尚未保存的新文件。

它被用在更多的地方,而不仅仅是在关机时。例如,它还被用于一个叫做App Nap的功能。当应用程序已经有一段时间没有活动了(不是焦点应用程序,没有播放音频等),那么系统可以告诉它保存其状态并终止该进程。当用户切换回该应用程序时,它就会迅速启动并恢复其状态。在内部,这也使用了相同的保存状态功能。

当使用AppKit构建一个应用程序时,对保存状态的支持在很大程度上是自动的。在某些情况下,应用程序需要在保存的状态中包括它自己的对象,以确保完整的状态可以被恢复,例如在一个基于文档的应用程序中。

每次应用程序失去焦点时,它都会向文件写入。

~/Library/Saved Application State/<Bundle ID>.savedState/windows.plist
~/Library/Saved Application State/<Bundle ID>.savedState/data.data

windows.plist文件包含所有应用程序打开的窗口的列表。(还有其他一些看起来不像窗口的东西,比如菜单栏和Dock菜单)。

例如,TextEdit.app的windows.plist文件可以是这样的。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>MenuBar AvailableSpace</key>
		<real>1248</real>
		<key>NSDataKey</key>
		<data>
		Ay1IqBriwup4bKAanpWcEw==
		</data>
		<key>NSIsMainMenuBar</key>
		<true/>
		<key>NSWindowID</key>
		<integer>1</integer>
		<key>NSWindowNumber</key>
		<integer>5978</integer>
	</dict>
	<dict>
		<key>NSDataKey</key>
		<data>
		5lyzOSsKF24yEcwAKTBSVw==
		</data>
		<key>NSDragRegion</key>
		<data>
		AAAAgAIAAADAAQAABAAAAAMAAABHAgAAxgEAAAoAAAADAAAABwAAABUAAAAb
		AAAAKQAAAC8AAAA9AAAARwIAAMcBAAAMAAAAAwAAAAcAAAAVAAAAGwAAACkA
		AAAvAAAAPQAAAAkBAABLAQAARwIAANABAAAKAAAAFQAAABsAAAApAAAALwAA
		AD0AAAAJAQAASwEAAD4CAADWAQAABgAAAAwAAAAJAQAASwEAAD4CAADXAQAA
		BAAAAAwAAAA+AgAA2QEAAAIAAAD///9/
		</data>
		<key>NSTitle</key>
		<string>Untitled</string>
		<key>NSUIID</key>
		<string>_NS:34</string>
		<key>NSWindowCloseButtonFrame</key>
		<string>{{7, 454}, {14, 16}}</string>
		<key>NSWindowFrame</key>
		<string>177 501 586 476 0 0 1680 1025 </string>
		<key>NSWindowID</key>
		<integer>2</integer>
		<key>NSWindowLevel</key>
		<integer>0</integer>
		<key>NSWindowMiniaturizeButtonFrame</key>
		<string>{{27, 454}, {14, 16}}</string>
		<key>NSWindowNumber</key>
		<integer>5982</integer>
		<key>NSWindowWorkspaceID</key>
		<string></string>
		<key>NSWindowZoomButtonFrame</key>
		<string>{{47, 454}, {14, 16}}</string>
	</dict>
	<dict>
		<key>CFBundleVersion</key>
		<string>378</string>
		<key>NSDataKey</key>
		<data>
		P7BYxMryj6Gae9Q76wpqVw==
		</data>
		<key>NSDockMenu</key>
		<array>
			<dict>
				<key>command</key>
				<integer>1</integer>
				<key>mark</key>
				<integer>2</integer>
				<key>name</key>
				<string>Untitled</string>
				<key>system-icon</key>
				<integer>1735879022</integer>
				<key>tag</key>
				<integer>2</integer>
			</dict>
			<dict>
				<key>separator</key>
				<true/>
			</dict>
			<dict>
				<key>command</key>
				<integer>2</integer>
				<key>indent</key>
				<integer>0</integer>
				<key>name</key>
				<string>New Document</string>
				<key>tag</key>
				<integer>0</integer>
			</dict>
		</array>
		<key>NSExecutableInode</key>
		<integer>1152921500311961010</integer>
		<key>NSIsGlobal</key>
		<true/>
		<key>NSSystemAppearance</key>
		<data>
		YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9i
		amVjdHMSAAGGoF8QD05TS2V5ZWRBcmNoaXZlctEICVRyb290gAGkCwwRElUk
		bnVsbNINDg8QViRjbGFzc18QEE5TQXBwZWFyYW5jZU5hbWWAA4ACXxAUTlNB
		cHBlYXJhbmNlTmFtZUFxdWHSExQVFlokY2xhc3NuYW1lWCRjbGFzc2VzXE5T
		QXBwZWFyYW5jZaIVF1hOU09iamVjdAgRGiQpMjdJTFFTWF5jan1/gZidqLG+
		wQAAAAAAAAEBAAAAAAAAABgAAAAAAAAAAAAAAAAAAADK
		</data>
		<key>NSSystemVersion</key>
		<array>
			<integer>12</integer>
			<integer>2</integer>
			<integer>1</integer>
		</array>
		<key>NSWindowID</key>
		<integer>4294967295</integer>
		<key>NSWindowZOrder</key>
		<array>
			<integer>5982</integer>
		</array>
	</dict>
</array>
</plist>

data.data文件包含一个自定义的二进制格式。它由一个记录列表组成,每个记录包含一个AES-CBC加密的序列化对象。windows.plist文件包含密钥(NSDataKey)和它所对应的data.data中的记录的ID(NSWindowID)1。

比如说。

00000000  4e 53 43 52 31 30 30 30  00 00 00 01 00 00 01 b0  |NSCR1000........|
00000010  ec f2 26 b9 8b 06 c8 d0  41 5d 73 7a 0e cc 59 74  |..&.....A]sz..Yt|
00000020  89 ac 3d b3 b6 7a ab 1b  bb f7 84 0c 05 57 4d 70  |..=..z.......WMp|
00000030  cb 55 7f ee 71 f8 8b bb  d4 fd b0 c6 28 14 78 23  |.U..q.......(.x#|
00000040  ed 89 30 29 92 8c 80 bf  47 75 28 50 d7 1c 9a 8a  |..0)....Gu(P....|
00000050  94 b4 d1 c1 5d 9e 1a e0  46 62 f5 16 76 f5 6f df  |....]...Fb..v.o.|
00000060  43 a5 fa 7a dd d3 2f 25  43 04 ba e2 7c 59 f9 e8  |C..z../%C...|Y..|
00000070  a4 0e 11 5d 8e 86 16 f0  c5 1d ac fb 5c 71 fd 9d  |...]........\q..|
00000080  81 90 c8 e7 2d 53 75 43  6d eb b6 aa c7 15 8b 1a  |....-SuCm.......|
00000090  9c 58 8f 19 02 1a 73 99  ed 66 d1 91 8a 84 32 7f  |.X....s..f....2.|
000000a0  1f 5a 1e e8 ae b3 39 a8  cf 6b 96 ef d8 7b d1 46  |.Z....9..k...{.F|
000000b0  0c e2 97 d5 db d4 9d eb  d6 13 05 7d e0 4a 89 a4  |...........}.J..|
000000c0  d0 aa 40 16 81 fc b9 a5  f5 88 2b 70 cd 1a 48 94  |..@.......+p..H.|
000000d0  47 3d 4f 92 76 3a ee 34  79 05 3f 5d 68 57 7d b0  |G=O.v:.4y.?]hW}.|
000000e0  54 6f 80 4e 5b 3d 53 2a  6d 35 a3 c9 6c 96 5f a5  |To.N[=S*m5..l._.|
000000f0  06 ec 4c d3 51 b9 15 b8  29 f0 25 48 2b 6a 74 9f  |..L.Q...).%H+jt.|
00000100  1a 5b 5e f1 14 db aa 8d  13 9c ef d6 f5 53 f1 49  |.[^..........S.I|
00000110  4d 78 5a 89 79 f8 bd 68  3f 51 a2 a4 04 ee d1 45  |MxZ.y..h?Q.....E|
00000120  65 ba c4 40 ad db e3 62  55 59 9a 29 46 2e 6c 07  |e..@...bUY.)F.l.|
00000130  34 68 e9 00 89 15 37 1c  ff c8 a5 d8 7c 8d b2 f0  |4h....7.....|...|
00000140  4b c3 26 f9 91 f8 c4 2d  12 4a 09 ba 26 1d 00 13  |K.&....-.J..&...|
00000150  65 ac e7 66 80 c0 e2 55  ec 9a 8e 09 cb 39 26 d4  |e..f...U.....9&.|
00000160  c8 15 94 d8 2c 8b fa 79  5f 62 18 39 f0 a5 df 0b  |....,..y_b.9....|
00000170  3d a4 5c bc 30 d5 2b cc  08 88 c8 49 d6 ab c0 e1  |=.\.0.+....I....|
00000180  c1 e5 41 eb 3e 2b 17 80  c4 01 64 3d 79 be 82 aa  |..A.>+....d=y...|
00000190  3d 56 8d bb e5 7a ea 89  0f 4c dc 16 03 e9 2a d8  |=V...z...L....*.|
000001a0  c5 3e 25 ed c2 4b 65 da  8a d9 0d d9 23 92 fd 06  |.>%..Ke.....#...|
[...]

每当一个应用程序被启动时,AppKit将读取这些文件并恢复应用程序的窗口。这是自动发生的,应用程序不需要实现任何东西。读取这些文件的代码是相当谨慎的:如果应用程序崩溃了,那么也许状态也被破坏了。如果应用程序在恢复状态时崩溃了,那么下一次就会丢弃状态,做一个新的开始。

我们发现的漏洞是,存储在data.data文件中的加密序列化对象没有使用 "安全编码"。为了解释这意味着什么,我们先解释一下序列化的漏洞,特别是在macOS上。

串行化的对象

许多面向对象的编程语言都增加了对二进制序列化的支持,它将一个对象变成一个字节组,然后再返回。与XML和JSON相反,这些是自定义的、特定于语言的格式。在一些编程语言中,对类的序列化支持是自动的,在其他语言中类可以选择加入。

在许多这些语言中,这些特性导致了漏洞。许多实现中的问题是,首先创建一个对象,然后检查其类型。在创建或销毁这些对象时,可能会对其调用方法。通过以不寻常的方式组合对象,当恶意对象被反序列化时,有时有可能获得远程代码执行。因此,对于任何可能通过网络从不信任的一方收到的数据,使用这些序列化函数不是一个好主意。

对于Python pickle和Ruby Marshall.load来说,远程代码的执行是很直接的。在Java ObjectInputStream.readObject和C#中,如果使用了某些常用的库,RCE是可能的。ysoserial和ysoserial.net工具可以用来生成一个有效载荷,这取决于使用的库。在PHP中,RCE的可利用性是罕见的。

Objective-C序列化

在Objective-C中,类可以实现NSCoding协议来实现可序列化。NSCoder的子类,如NSKeyedArchiver和NSKeyedUnarchiver,可以被用来序列化和反序列化这些对象。

这在实践中是如何运作的,如下所示。一个实现NSCoding的类必须包括一个方法。

- (id)initWithCoder:(NSCoder *)coder;

在这个方法中,这个对象可以使用编码器来解码它的实例变量,使用的方法包括-decodeObjectForKey:、-decodeIntegerForKey:、-decodeDoubleForKey:等。当它使用-decodeObjectForKey:时,编码器将递归地对该对象调用-initWithCoder:,最终对整个对象图进行解码。

苹果公司也意识到了对不可信任的输入进行反序列化的风险,所以在10.8中,增加了NSSecureCoding协议。这个协议的文档指出。

一个协议,能够以一种稳健的方式进行编码和解码,以防止对象的替换攻击。

 

这意味着不是先创建一个对象,然后检查其类型,而是在解码一个对象时需要包括一组允许的类。

因此,代替不安全的结构。

id obj = [decoder decodeObjectForKey:@"myKey"];
if (![obj isKindOfClass:[MyClass class]]) { /* ...fail... */ }

必须使用以下方法。

id obj = [decoder decodeObjectOfClass:[MyClass class] forKey:@"myKey"];

这意味着当一个安全编码器被创建时,不再允许-decodeObjectForKey:,而必须使用-decodeObjectOfClass:forKey:。

这使得可利用的漏洞大大增加了难度,但它仍然可能发生。这里需要注意的是,指定类的子类是允许的。例如,如果指定了NSObject类,那么所有实现NSCoding的类仍然是允许的。如果只期待NSDictionary,而导入的框架包含一个很少使用的、易受攻击的NSDictionary的子类,那么这也会产生一个漏洞。

在苹果的所有操作系统中,这些序列化的对象被到处使用,经常用于进程间的数据交换。例如,NSXPCConnection严重依赖安全序列化来实现远程方法调用。在iMessage中,这些序列化对象甚至会通过网络与其他用户进行交换。在这种情况下,始终启用安全编码是非常重要的。

创建一个恶意的序列化对象

在保存状态的data.data文件中,对象是使用NSKeyedArchiver储存的,没有启用安全编码。这意味着我们可以包括任何实现NSCoding协议的类的对象。这可能的原因是,应用程序可以用他们自己的对象来扩展保存状态,由于保存状态的功能比NSSecureCoding要老,苹果不能直接将其升级为安全编码,因为这可能会破坏第三方应用程序。

为了利用这一点,我们需要一种方法来构建一个对象链,使我们能够执行任意代码。然而,似乎没有类似于Objective-C的ysoserial的项目存在,我们也找不到其他在macOS中滥用不安全的反序列化的例子。在远程iPhone开发的第一部分。通过iMessage和CVE-2019-8641窥探内存,Google Project Zero的Samuel Groß描述了通过滥用NSSharedKeyDictionary(NSDictionary的一个不常见的子类)中的漏洞对安全编码者的攻击。由于这个漏洞现在已经被修复,我们无法使用这个。

通过反编译AppKit中大量的-initWithCoder:方法,我们最终找到了2个对象的组合,可以用来在另一个反序列化的对象上调用任意的Objective-C方法。

我们从NSRuleEditor开始。这个类的-initWithCoder:方法创建了一个与同一存档的对象的绑定,其关键路径也是从存档中获得的。

绑定是Cocoa中的一种反应式编程技术。它使得直接将模型绑定到视图成为可能,而不需要控制器的模板代码。每当模型中的一个值发生变化,或者用户在视图中做了改变,这些变化就会自动传播。

绑定是通过调用该方法创建的。

- (void)bind:(NSBindingName)binding 
    toObject:(id)observable 
 withKeyPath:(NSString *)keyPath 
     options:(NSDictionary<NSBindingOption, id> *)options;

这就把接收器的属性绑定到了observable的keyPath上。keypath是一个字符串,例如,可以用来访问对象的嵌套属性。但是创建绑定的更常见的方法是通过在Xcode中创建它们作为XIB文件的一部分。

例如,假设模型是一个Person类,它有一个属性@property (readwrite, copy) NSString *name;。那么你可以将一个文本字段的 "值 "绑定到Person的 "name "键盘路径上,以创建一个显示(并可以编辑)人名的字段。

在XIB编辑器中,这将被创建为如下。

f33c2d62e4090100

不同的keypath的含义选项实际上是相当复杂的。例如,当用 "foo "这个关键路径进行绑定时,它首先会检查getFoo、foo、isFoo和_foo这些方法是否存在。这通常是用来访问对象的一个属性,但这并不是必须的。当一个绑定被创建时,该方法将在创建绑定时被立即调用,以提供一个初始值。如果该方法实际返回无效,这并不重要。这意味着,通过在反序列化过程中创建一个绑定,我们可以用它来调用其他反序列化对象上的零参数方法

ID NSRuleEditor::initWithCoder:(ID param_1,SEL param_2,ID unarchiver)
{
	...

	id arrayOwner = [unarchiver decodeObjectForKey:@"NSRuleEditorBoundArrayOwner"];

	...

	if (arrayOwner) {
	  keyPath = [unarchiver decodeObjectForKey:@"NSRuleEditorBoundArrayKeyPath"];
	  [self bind:@"rows" toObject:arrayOwner withKeyPath:keyPath options:nil];
	}

	...
}

在这种情况下,我们用它来调用下一个对象的-draw。

我们使用的下一个对象是一个NSCustomImageRep对象。它从存档中获得一个字符串形式的选择器(方法名称)和一个对象。当-draw方法被调用时,它在对象上调用选择器的方法。它把自己作为第一个参数。

ID NSCustomImageRep::initWithCoder:(ID param_1,SEL param_2,ID unarchiver)
{
	...
	id drawObject = [unarchiver decodeObjectForKey:@"NSDrawObject"];
	self.drawObject = drawObject;
	id drawMethod = [unarchiver decodeObjectForKey:@"NSDrawMethod"];
	SEL selector = NSSelectorFromString(drawMethod);
	self.drawMethod = selector;
	...
}

...

void ___24-[NSCustomImageRep_draw]_block_invoke(long param_1)
{
  ...
  [self.drawObject performSelector:self.drawMethod withObject:self];
  ...
}

通过反序列化这两个类,我们现在可以调用零参数方法和多参数方法,尽管第一个参数将是一个NSCustomImageRep对象,其余的参数将是任何恰好还在这些寄存器中的东西。尽管如此,是一个非常强大的原语。我们将在以后的博文中介绍我们所使用的链的其余部分。

漏洞

沙盒逃逸

首先,我们利用这个漏洞逃脱了Mac应用程序的沙盒。为了解释这一点,有必要多介绍一些关于保存状态的背景。

在一个沙盒应用程序中,许多原本存储在~/Library中的文件被存储在一个单独的容器中。因此,它的状态不是保存在:

~/Library/Saved Application State/<Bundle ID>.savedState/

沙盒中的应用程序将其状态保存到。

~/Library/Containers/<Bundle ID>/Data/Library/Saved Application State/<Bundle ID>.savedState/

显然,当系统关闭时,一个应用程序仍在运行(当显示提示询问用户是否在下次重新打开窗口时),第一个位置被 talagent 符号链接到第二个位置。我们不清楚原因,这可能与应用程序升级到新版本有关,因为新版本是沙盒的。

其次,大多数应用程序不能访问所有文件。沙盒中的应用程序当然是非常受限制的,但随着 TCC 的加入,即使是访问下载、文档等文件夹也需要用户批准。如果应用程序会打开一个打开或保存面板,如果用户只能看到该应用程序可以访问的文件,那就相当不方便了。为了解决这个问题,在打开这样一个面板时,会启动一个不同的进程:com.apple.appkit.xpc.openAndSavePanelService。尽管窗口本身是应用程序的一部分,但其内容是由openAndSavePanelService绘制的。这是一个XPC服务,可以完全访问所有文件。当用户在面板中选择一个文件时,应用程序获得对该文件的临时访问权。这样一来,即使在没有权限列出这些文件的应用程序中,用户仍然可以浏览他们的整个磁盘。

b8f01f43ad090251

由于它是一个服务类型为Application的XPC服务,它为每个应用程序单独启动。

我们注意到的是,这个XPC服务读取它的保存状态,但使用的是启动它的应用程序的捆绑ID! 由于这个面板可能是多个应用程序的保存状态的一部分,它需要为每个应用程序分离其状态,这确实有些道理。

结果是,它从容器外的位置读取其保存的状态,但使用应用程序的捆绑ID。

~/Library/Saved Application State/<Bundle ID>.savedState/

但正如我们所提到的,如果用户关闭电脑时,应用程序曾经打开过,那么这将是一个指向容器路径的符号链接。

因此,我们可以通过以下方式逃脱沙箱。

  • 如果符号链接还不存在,等待用户在应用程序打开时关机。
  • 在应用程序自己的容器内写入恶意的data.data和windows.plist文件。
  • 打开一个NSOpenPanel或NSSavePanel。

com.apple.appkit.xpc.openAndSavePanelService进程现在会反序列化恶意对象,让我们在一个非沙盒进程中执行代码。

这比其他问题更早被修复,在macOS 11.3中被列为CVE-2021-30659。苹果通过不再从com.apple.appkit.xpc.openAndSavePanelService中的相同位置加载状态来解决这个问题。

 

特权升级

通过将我们的代码注入一个具有特定权限的应用程序,我们可以将我们的权限提升到root。为此,我们可以应用A2nkF在《Unauthd - Logic bugs FTW》中解释的技术。

一些应用程序有一个com.apple.private.AuthorizationServices的权限,其中包含system.install.apple-software的值。这意味着这个应用程序被允许安装有苹果公司生成的签名的软件包,而无需用户授权。例如,"Install Command Line Developer Tools.app "和 "Bootcamp Assistant.app "有这个权利。A2nkF还发现了一个由苹果签名的软件包,其中包含一个漏洞:macOSPublicBetaAccessUtility.pkg。当这个软件包被安装到一个特定的磁盘时,它将从该磁盘运行(以root身份)一个安装后脚本。该脚本假定它被安装到含有macOS的磁盘上,但这并没有被检查。因此,通过在同一位置创建一个恶意脚本,就有可能通过安装这个软件包以root身份执行代码。

利用步骤如下。

  • 创建一个RAM磁盘,并复制一个恶意脚本到将由macOSPublicBetaAccessUtility.pkg执行的路径。
  • 通过为该应用程序创建windows.plist和data.data文件,将我们的代码注入含有system.install.apple-software权利的应用程序,然后启动它。
  • 使用注入的代码将macOSPublicBetaAccessUtility.pkg包安装到RAM磁盘。
  • 等待安装后脚本的运行。

在A2nkF的文章中,安装后脚本的运行没有SIP的文件系统限制。它从安装过程中继承了这一点,因为软件包的安装可能需要写到SIP保护的位置,所以需要它。这一点已被苹果公司修复:安装后和安装前的脚本不再有SIP豁免。然而,该软件包及其特权升级仍然可以使用,因为苹果仍然使用相同的脆弱的安装包。


绕过SIP文件系统

现在我们已经逃离了沙盒,并将我们的权限提升到了root,我们确实也想绕过SIP。为了做到这一点,我们在所有可用的应用程序中寻找一个有合适权限的应用程序。最终,我们在macOS Big Sur Beta安装盘镜像上找到了一些东西。"macOS Update Assistant.app "有com.apple.rootless.install.heritable的权利。这意味着这个进程可以写到所有受SIP保护的位置(而且它是可继承的,这很方便,因为我们可以直接生成一个shell)。虽然它应该只在测试版安装时使用,但我们可以把它复制到正常的macOS环境并在那里运行。

这方面的利用很简单。

  • 为 "macOS Update Assistant.app "创建恶意的windows.plist和data.data文件。
  • 启动 "macOS Update Assistant.app"。

当免于SIP的文件系统限制时,我们可以从受保护的位置读取所有文件,如用户的Mail.app邮箱。我们还可以修改TCC数据库,这意味着我们可以授予自己访问网络摄像头、麦克风等的权限。我们还可以将我们的恶意软件持续存在于受SIP保护的位置,使苹果以外的人很难删除它。最后,我们可以改变批准的内核扩展数据库。这意味着,我们可以默默地加载一个新的内核扩展,而不需要用户批准。当与一个易受攻击的内核扩展(或一个允许签署内核扩展的编码证书)相结合时,我们将能够获得内核代码执行,这也将允许禁用所有其他限制。

演示

我们录制了以下视频来演示不同的步骤。它首先显示应用程序 "Sandbox "已被沙盒化,然后它逃出沙盒并启动了 "Privesc"。这就把权限提升到了root,并启动了 "SIP Bypass"。最后,它打开了一个不受SIP文件系统限制的反向外壳,通过在/var/db/SystemPolicyConfiguration(存放已批准的内核模块数据库的位置)写一个文件来证明。

 

 

修复方法

苹果在11.3中首先修复了沙盒逃逸,不再读取com.apple.appkit.xpc.openAndSavePanelService中应用程序的保存状态(CVE-2021-30659)。

修复该漏洞的其余部分则更为复杂。第三方应用程序可能会在保存状态中存储自己的对象,这些对象可能不支持安全编码。这让我们回到了介绍中的方法:-applicationSupportsSecureRestorableState:。应用程序现在可以通过从该方法返回TRUE来选择是否需要对其保存状态进行安全编码。除非应用程序选择加入,否则它将继续允许非安全编码,这意味着进程注入可能仍然存在。

这确实突出了这些安全措施的当前设计的一个问题:降级攻击。一个应用程序的代码签名(以及由此产生的权利)将在很长一段时间内保持有效,如果应用程序被降级,应用程序的TCC权限仍将有效。一个没有沙盒的应用程序可以默默地下载一个旧的、有漏洞的应用程序版本,并利用它。对于SIP旁路,这将不起作用,因为 "macOS Update Assistant.app "不能在macOS Monterey上运行,因为某些私有框架不再包含必要的符号。但这是一个巧合的修复,在许多其他情况下,旧的应用程序可能仍然运行良好。因此,只要对旧的macOS应用程序有向后的兼容性,这个漏洞就会一直存在!

尽管如此,如果你写一个Objective-C应用程序,请确保你添加-applicationSupportsSecureRestorableState:以返回TRUE,并对所有用于保存状态的类进行安全编码。

结论

在当前macOS的安全架构中,进程注入是一种强大的技术。一个通用的进程注入漏洞可以被用来逃脱沙盒,将权限提升到root,并绕过SIP的文件系统限制。我们已经展示了我们如何在加载一个应用程序的保存状态时使用不安全的反序列化来注入任何Cocoa进程。这被苹果公司作为CVE-2021-30873解决。

目前还不清楚这里的AES加密是为了增加什么安全性,因为密钥就存储在它旁边。没有MAC,所以没有对密码文本的完整性检查。︎

原文链接

https://sector7.computest.nl/post/2022-08-process-injection-breaking-all-macos-security-layers-with-a-single-vulnerability/

标签:漏洞分享