Hi, it’s been a long time since my last blog post.
In the past few months, I spent lots of time preparing for the talk of Black Hat USA 2017 and DEF CON 25. Being a Black Hat and DEFCON speaker is part of my life goal ever. This is also my first English talk in such formal conferences. It’s really a memorable experience :P
Thanks Review Boards for the acceptance.
This post is a simple case study in my talk. The techniques here are old, but I’ll show you just how powerful those old tricks can be! If you are interested in, you can check slides here:
The slides covered even more powerful new approaches on SSRF and other techniques not included in this article.
In this article, I will show you a beautiful exploit chain that chained 4 vulnerabilities into a Remote Code Execution(RCE) on GitHub Enterprise. It also be rewarded for the Best Report in GitHub 3rd Bug Bounty Anniversary Promotion!
Foreword
In my last blog post, I mentioned that the new target - GitHub Enterprise, also demonstrated how to de-obfuscate Ruby code and find SQL Injection on it. After that, I see several bounty hunters start to pay attentions on GitHub Enterprise and find lots of amazing bugs, like:
- The road to your codebase is paved with forged assertions by ilektrojohn
- GitHub Enterprise Remote Code Execution by iblue
Seeing those writeups, I got a little frustrated and blame myself why I didn’t notice that :(
Therefore, I have made up my mind to find a critical vulnerability that no one have found.
Of course, in my own way!
Vulnerabilities
Before I examine the architecture of GitHub Enterprise. My intuition tells me, there are so many internal services inside GitHub Enterprise. If I can play with them, I believe I have confidences to find something interesting.
So, I am focusing on finding Server Side Request Forgery(SSRF) vulnerability more.
First Bug - Harmless SSRF
While playing GitHub Enterprise, I notice that there is an interesting feature called WebHook
. It can define a custom HTTP callback when specific GIT command occurs.
You can create a HTTP callback from the URL:
1 | https://<host>/<user>/<repo>/settings/hooks/new |
And trigger it by committing files. Thus, GitHub Enterprise will notify you with a HTTP request. The payload and the request look like bellow:
Payload URL:
1 | http://orange.tw/foo.php |
Callback Request:
1 | POST /foo.php |
GitHub Enterprise uses Ruby Gem faraday
to fetch external resources and prevents users from requesting internal services by Gem faraday-restrict-ip-addresses
.
The Gem seems to be just a blacklist and can be easily bypassed by the Rare IP Address Formats defined in RFC 3986. In Linux, the 0
represented localhost
PoC:
1 | http://0/ |
OK, we got a SSRF now. However, we still can’t do anything. Why?
There are several limitations in this SSRF, such as:
- Only POST method
- Only allowed HTTP and HTTPS scheme
- No 302 redirection
- No CR-LF Injection in
faraday
- Couldn’t control the POST data and HTTP headers
The only thing we can control is Path part.
But, It’s still worth to mentioned that this SSRF can lead to Denied of Service(DoS).
There is an Elasticsearch service bound on port 9200. In the shutdown
command, Elasticsearch doesn’t care about whatever the POST data is. Therefore, you can play its REST-ful API for fun :P
Denied of Service PoC:
1 | http://0:9200/_shutdown/ |
Second Bug - SSRF in Internal Graphite
We have a SSRF now, with lots of limitations. What can I do? My next idea is - Is there any Intranet services we can leverage?
It’s a big work. There are several HTTP services inside, and each service based on different language implementations like C / C++, Go, Python and Ruby…
With a couple of days digging. I find there is a service called Graphite
on port 8000. Graphite
is a highly scalable real-time graphing system and GitHub uses this system to show some statistics to users.
Graphite
is written in Python and also a open-source project, you can download the source code here!
From reading the source, I quickly find another SSRF here. The second SSRF is simple.
In file webapps/graphite/composer/views.py
1 | def send_email(request): |
You can see Graphite
receive the user input url
and just fetch it! So, we can use the first SSRF to trigger the second SSRF and combine them into a SSRF execution chain.
The SSRF execution chain payload:
1 | http://0:8000/composer/send_email? |
The request of second SSRF
1 | nc -vvlp 12345 |
OK, we successfully change the POST-based SSRF into a GET-based SSRF. But still can’t do anything.
Let’s go to next stage!
Third Bug - CR-LF Injection in Python
As you can see, Graphite
uses Python httplib.HTTPConnection
to fetch the resources. With some trials and errors, I notice that there is a CR-LF Injection in httplib.HTTPConnection
. Therefore, we have the ability to embed malicious payloads in HTTP protocol.
CR-LF Injection PoC
1 | http://0:8000/composer/send_email? |
1 | $ nc -vvlp 12345 |
This is one small step, but it become a giant leap for whole the exploit chain. Now, I can smuggle other protocols in this SSRF Execution Chain. For example, If we want to play with Redis, we can try following payload:
1 | http://0:8000/composer/send_email? |
P.s. The SLAVEOF is a very nice command that you can make out-bound traffics. This is a useful trick when you are facing some Blind-SSRF!
That’s look great! However, there are also some limitations in protocol smuggling
- Protocols with handshakes like SSH, MySQL and SSL will fail
- The payload we used in second SSRF only allowed bytes from 0x00 to 0x8F due to the
Python2
By the way, there is more than one way to smuggle protocols in the HTTP scheme. In my slides, I also show that how to use the features in Linux Glibc to smuggle protocols over SSL SNI, and a case study in bypassing Python CVE-2016-5699!
Check it, if you are interested :)
Fourth Bug - Unsafe Deserialization
For now, we have the ability to smuggle other protocols in a HTTP protocol, but the next problem is, what protocol do I choose to smuggle?
I spend lots of time to find out what vulnerabilities can be triggered if I can control the Redis or Memcached.
While reviewing the source. I am curious about why GitHub can store Ruby Objects in Memcached. After some digging, I find GitHub Enterprise uses Ruby Gem memcached
to handle caches, and the cache was wrapped by Marshal.
It’s a good news to me. Everyone know that Marshal is dangerous.
(If you don’t know, I recommend you read the slides Marshalling Pickles by @frohoff and @gebl from AppSec California 2015)
So, our our goal is clear.
We use our SSRF execution chain to store malicious Ruby Objects in Memcached. The next time GitHub fetches the cache, Ruby Gem memcached
will de-serialize the data automatically. And the result is… BOOM! Remote Code Execution! XD
Unsafe Marshal in Rails Console
1 | GitHub.cache.class.superclass |
OK, let’s summarize our steps!
- First SSRF - Bypass the existing protection in
Webhook
- Second SSRF - SSRF in
Graphite
service - Chained first SSRF and second SSRF into a SSRF execution chain
- CR-LF Injection in the SSRF execution chain
- Smuggled as Memcached protocol and insert a malicious Marshal Object
- Triggered RCE
Exploit in a Nutshell
The final exploit you can find on Gist and video on Youtube
1 | #!/usr/bin/python |
The Fix
GitHub had made a number of improvements to prevent related issues again!
- Enhanced the Gem
faraday-restrict-ip-addresses
- Applied a custom Django middleware to ensure attackers can’t reach path outside
http://127.0.0.1:8000/render/
- Enhanced
iptables
rules that block access with patternUser-Agent: GitHub-Hookshot
1 | $ cat /etc/ufw/before.rules |
Timeline
- 2017/01/23 23:22 Report the vulnerability to GitHub via HackerOne, report number 200542 assigned
- 2017/01/23 23:37 GitHub changed the status to Triaged.
- 2017/01/24 04:43 GitHub responses that the issue validated and working on a fix.
- 2017/01/31 14:01 GitHub Enterprise 2.8.7 released.
- 2017/02/01 01:02 GitHub response that this issue have been fixed!
- 2017/02/01 01:02 GitHub rewarded $7,500 USD bounty!
- 2017/03/15 02:38 GitHub rewarded $5,000 USD for the best report bonus.