Developers have increasingly become a more valuable target to compromise in recent years. The DevOps movement means they have more access to production, not to mention the plethora of source code and keys that you are likely to find.
This post is going to show you how a production configuration mistake on the atom.io domain that put users of the Atom editor at risk.
Note this issue is now fixed thanks to the GitHub Security team.
Background
Atom’s package manager apm uses npm to fulfill it’s dependencies and for native modules this means using node-gyp when code needs to be compiled.
As part of the compilation step for native modules, node-gyp downloads a tarball for headers from a specified dist-url and a list of SHASUMS for integrity verification.
These urls are configured with a flag, something like this
node-gyp rebuild --dist-url=https://atom.io/download/atom-shell
This is where the subtle bug gets introduced. When auditing the source someone might look at this, confirm that HTTPS is being used and move on. They might even go a step further and curl the url and verify that it is in fact using HTTPS and you would be correct that it is.
However….
The dist-url and SHASUM url’s that are derived by node-gyp are forwarded to an S3 bucket over HTTP by the atom.io web service.
For example: When hxxps://atom.io/download/atom-shell/v0.30.6/node-v0.30.6.tar.gz is requested a 302 is returned with a redirect location of hxxp://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist/v0.30.6/node-v0.30.6.tar.gz
The same goes for the SHASUM url
hxxps://atom.io/download/atom-shell/v0.30.6/SHASUMS256.txt redirects to hxxp://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist/v0.30.6/SHASUMS256.txt
So what?
That means if we can get between the developer and atom.io we can control the content returned for the dist tarball and the SHASUM file.
But before getting that far I had to figure out how to backdoor the dist tarball so that I could have code execution. Knowing nothing about how node-gyp works, and finding it to be a rats nest of gibberish this took a while. I’m sure there are other ways as well, such as causing a backdoor to be compiled into the final library. I went with what I felt was a simpler route, as attackers tend to do.
The Exploit
What I found was that node-gyp loads a file namedcommon.gypi. This file provides ways to specify defaults and configuration for gyp. The proof of concept below includes a default target with an action to execute the say command, however this could be any command the attacker wants to run.
'target_name': 'sdf',
'sources': ['src/asdf.h'],
'actions': [{
'action_name': 'hax',
'inputs': ['src/asdf.h'],
'outputs': ['<(PRODUCT_DIR)/asdf.out'],
'action': ['/usr/bin/say', 'ha ha ha ha ha ha powned'],
}],
Once I had reliable code execution during a node-gyp installation, I had to perform a MITM (machine in the middle) attack and swap out the tarball and the SHASUMS file.
For this task I used Bettercap, an extensible MITM framework with many already built plugins. Since what I had to do was a bit custom, I wrote a custom plugin to replace more than one file on the fly.
Mitigating Factors
First of all, not all modules trigger this condition because there are plenty of modules that are not native modules.
Secondly, node-gyp caches the dist content so if it’s already on disk it won’t be downloaded again.
Timeline Fun Facts
- 10.28.2016 — Vulnerability found
- 10.29.2016 — Confirmed exploitability
- 10.30.2016 — Notified atom.io via email
- 10.31.2016 — Notified AtomEditor via twitter
- 11.03.2016 — Submitted to GitHub bounty via hackerone.com
- 11.03.2016 — Received bug confirmation
- 11.05.2016 — Verified fix was in place
- 11.07.2016 — GithHub Security resolved the issue & issued a bounty
Big thanks to the GitHub Security team for their quick response and good communication with the reported issue.
Originally posted on Medium