X Site eScape (Part I): Exploitation of and Old CoreFoundation Sandbox Bug
What is your impression of XSS? Stealing credentials from websites? Struggling for CSP and SameSite cookies?
Here’s an odd case for it. The input vector has nothing to do with the HTTP protocol, and the motivation is to escape the sandbox instead of exfiltrating sensitive tokens. It’s a story about how I turned a sandbox escape primitive to a XSS in a privileged WebView and archive further native code execution.
Last year I blogged about a TOCTOU bug that doesn’t require race. It seemed to be long standing since OS X Yosemite or even earlier, definitely before I’ve ever had my very first Mac.
One-liner Safari Sandbox Escape Exploit
This time I am writing about a dead simple and reliable sandbox exploit which only have one line of code. Yeah I am…
The one-liner PoC writes a launch entry that can be activated upon the next login, which is enough for both sandbox escape and persistence. But is there a way to trigger the code execution immediately? Surely yes. Messing up with an arbitrary property list file is a powerful primitive. Here I picked Dashboard as my target.
Dashboard was an application for Apple Inc.’s macOS operating systems, used as a secondary desktop for hosting mini-applications known as widgets.
Dashboard uses WebView (distinguished from WKWebView) to render the page, so there is no JIT nor sandbox. A non-jit bug is required to archive code execution outside. There came a weird idea in my head: any possibility that I can do XSS in Dashboard?
At first, I was looking at the WebClip of Safari. There used to be a context menu in Safari that can add the current page to Dashboard. I thought that there might be some way to trigger the action through Safari IPC. After analyzing its workflow, I just gave it up because it requires user interaction to take place in the UIProcess.
Now, plan B is to abuse writing PLIST to install a user-defined widget. A widget is a piece of HTML application that has a .wdgt extension. Each bundle consists of at least the following contents:
- Info.plist: the manifest
- An HTML file
- An icon in png format
- Other assets like stylesheets and external scripts
macOS used to have preinstalled widgets in
~/Library/Widgets is for 3rd-party widgets. The historical widget market is still on Apple’s official site now!
Apple - Downloads - Dashboard
Apple is providing links to these applications as a courtesy, and makes no representations regarding the applications…
For further information about widgets, there is a book named Beginning Mac OS X Tiger Dashboard Widget Development.
Beginning Mac OS X Tiger Dashboard Widget Development
Aimed at the estimated 300,000 Mac OS X developers who are interested in customizing and creating widgets for their…
There is a preferences domain that holds the active layout of all widgets. You see the key
path is absolute, which means we can add widgets without touching the recommended locations.
To trigger an XSS in Dashboard, drop the bundle somewhere writable and register it using the TOCTOU bug in cfprefsd. As mentioned before we have to release a well-organized bundle. The manifest file
Info.plist and here are some of its essential entries:
CFBundleIdentifier: the name and identifier
MainHTML: name of the main user interface
AllowNetworkAccess: permission to make cross-domain AJAX
AllowSystem: permission to execute a shell command
AllowJava: permission to use Java
AllowFullAccess: permission to read local files
Holy crap! Did you see the phase ‘shell command’? It implies that once we had a custom widget, there is no need for re-exploiting the renderer twice.
This is the implementation of the WebView’s delegate:
➜ ~ nm /System/Library/PrivateFrameworks/DashboardClient.framework/DashboardClient | grep webScriptNameForSelector
0000000000007c5b t +[DBCAsyncUNIXScriptJSObject webScriptNameForSelector:]
000000000000540b t +[DBCCalculatorJSObject webScriptNameForSelector:]
0000000000005b93 t +[DBCJSObject webScriptNameForSelector:]
0000000000005fac t +[DBCMenuJSObject webScriptNameForSelector:]
00000000000060e4 t +[DBCScriptingJSObject webScriptNameForSelector:]
0000000000006bbf t +[DBCUNIXScriptJSObject webScriptNameForSelector:]
DBCScriptingJSObject exposes an API to the window object, which calls
+[DBCUNIXScriptJSObject UNIXScriptWithCommand:callback:currentDirectory:widget:] to execute shell command internally. This is it.
There are still two things left. What if Dashboard is disabled? And the widget needs to be activated to trigger command execution.
Now it’s time for miracles. It just happened to be a MIG service named
com.apple.dock.server that controls the preferences of Dock and Dashboard, and it even allows access from renderer sandbox. It’s even got some neat private APIs so you don’t have to build Mach message on your own.
➜ ~ nm /System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices | grep CoreDock | grep \ T\
0000000000019e51 T _CoreDockAddFileToDock
0000000000018dad T _CoreDockBounceAppTile
0000000000018df2 T _CoreDockCompositeProcessImage
0000000000011e62 T _CoreDockCopyPreferences
000000000001a410 T _CoreDockCopyWorkspacesAppBindings
It doesn’t matter whether Dashboard is on or not. We can always use
CoreDockSendNotification accepts a string to toggle corresponding desktop actions:
- Show desktop:
- Show Workspaces:
- Show Dashboard:
- TouchPad Preferences:
Now it goes like this.
- Renderer takeover
- Safari renderer sandbox has a writable temporary directory, where we can release a widget bundle here. Dashboard does not care about the quarantine flag
- Manipulate the preferences domain
com.apple.dashboardto install our widget
CoreDockSetPreferencesto enable Dashboard forcibly
CoreDockSendNotificationto activate Dashboard desktop
- Widget activated and game over
As you see, this exploit is just full of coincidence.
The source code can be found here (and you can see how dumb it is):