Using CVE-2021-40531 for RCE with Sketch

Sketch is a popular UI/UX design app for macOS. This post covers a vulnerability in Sketch that I discovered back in July — CVE-2021-40531. In its simplest form, it is a macOS quarantine bypass, but in context it can be used for remote code execution.

Exploit Demo

The GIF above shows Calculator.app being opened by exploiting this vulnerability. The source code for the proof-of-concept shown above is available here.

Library feeds

Sketch users can create collections of reusable design assets and share them with other users; these collections are called “libraries”. To enable the distribution and maintenance of libraries over the internet, Sketch utilizes “library feeds”, which are just minimal RSS feeds that inform Sketch about a library. Shown below is the feed for Apple’s official iOS 14 UI library, with some unnecessary information trimmed for brevity.

<?xml version="1.0" encoding="utf-8"?>
<rss>
  <channel>
    <title>Apple iOS UI</title>
    <description>Apple iOS 14 Sketch library</description>
    <language>en</language>
    <item>
      <title>Apple iOS UI</title>
      <sparkle:minimumSystemVersion>70</sparkle:minimumSystemVersion>
      <sparkle:maximumSystemVersion>71</sparkle:maximumSystemVersion>
      <pubDate>Thu, 28 Jan 2021 9:00:00 +0000</pubDate>
      <enclosure url="https://...cdn.apple.com/.../library.dmg" ... />
    </item>
  </channel>
</rss>

When installing or updating a library via a library feed, Sketch will automatically download whatever is at the URL specified by the “enclosure” tag. In the example shown above, the library’s content is distributed as a DMG file.

Unrestricted file transfer

It turns out there are no restrictions on the type of URL that may be specified in the enclosure tag — it doesn’t have to use HTTPS, or use any web protocol at all. Given this lack of restrictions, it is possible to create a library feed with any URL imaginable, pointing towards any type of content; Sketch will happily download it.

Quarantine bypass

Users of macOS are familiar with pop-up dialogs asking them to confirm they would like to open a file downloaded from the internet. These prompts appear because web browsers (among many other applications) mark files downloaded from the internet with the “com.apple.quarantine” extended attribute; this signals to macOS that the file should be treated with caution.

The key detail here is that this behavior is not automatic; applications must explicitly mark downloaded files for quarantine. Prior to Sketch 75, files downloaded as part of library feeds were not marked for quarantine.

Remote code execution

To recap so far, Sketch will download any type of file (from any URL) specified in a library feed, and neglect to mark the downloaded file for quarantine. This behavior isn’t ideal, but also isn’t super interesting on its own.

However, I’ve left out one important detail — after Sketch downloads a file from a library feed, that file will be automatically opened, with no user interaction at all.

Now things are more interesting. All of these behaviors combined provide a great foundation for remote code execution — the only remaining task is finding a file type that can be used to execute code when it is opened.

Terminal profiles

Surely there is no shortage of third-party applications suitable for this task, but I wanted to find an application that ships with macOS that could be used. The default terminal emulator on macOS (Terminal.app) supports saving a “terminal profile” to a file, which looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CommandString</key>
    <string>open -a Calculator; killall Terminal</string>
    <key>ProfileCurrentVersion</key>
    <real>1.0</real>
    <key>RunCommandAsShell</key>
    <false/>
    <key>name</key>
    <string>CVE-2021-40531</string>
    <key>shellExitAction</key>
    <integer>0</integer>
    <key>type</key>
    <string>Window Settings</string>
    <key>warnOnShellCloseAction</key>
    <integer>0</integer>
  </dict>
</plist>

When a terminal profile is opened, it launches Terminal.app, creates a new window using that profile, and runs the startup command specified in the “CommandString” property. This makes terminal profiles a great choice for achieving remote code execution in the context of this vulnerability.

It is important to note that if Sketch were properly quarantining downloaded files, the terminal profile would not be able to open and execute code without confirmation. Even with proper quarantining, some users might just click “Run” anyway, but that’s a separate problem out of scope for this blog post. :)

URL handler

To make installing libraries (or delivering this exploit) even easier, Sketch registers a URL handler that accepts links in the following form:

sketch://add-library?url=<LIBRARY_FEED_URL>

This makes it possible to install a Sketch library feed by simply clicking a link. In fact, Apple uses this strategy to provide access to their official Sketch libraries. While using the URL handler is not necessary for exploiting this vulnerability, it makes it even easier; potentially securing remote code execution on the victim’s machine with just one click.

Proof of concept

A small proof-of-concept which opens Calculator.app by exploiting this vulnerability (shown at the top of the page) is available on my GitHub here.

Closing notes

Sketch 75 and beyond now appropriately quarantine files downloaded from library feeds. If you aren’t using Sketch 75 or newer, you should avoid using any untrusted library feeds and update as soon as possible.

Finally, I’d like to thank Sketch for their swift cooperation in fixing this vulnerability and for providing a €1,250 reward for reporting this vulnerability.