12.03 繞過GitHub的OAuth授權驗證機制

*本文中涉及到的相關漏洞已報送廠商並得到修復,本文僅限技術研究與討論,嚴禁用於非法用途,否則產生的一切後果自行承擔。

繞過GitHub的OAuth授權驗證機制

這幾年來,信息安全研究一直是我的業餘愛好,雖然有很多人專職做漏洞眾測以獲得獎勵,但對我個人來說,我只對一些感興趣的項目投入不多的時間去深入研究。今年,我想看看自己是否是全職漏洞賞金獵人的料,所以就從6月份開始每天抽出幾個小時的時間去測試GitHub的安全漏洞。

我對GitHub的主要測試方法為,下載試用版的GitHub Enterprise,然後用 我寫的腳本 把它反混淆(deobfuscate),然後觀察GitHub的 Rails 代碼查看是否有一些奇怪的行為或漏洞。從安全開發的角度來說,GitHub的的代碼架構做得非常好,雖然我能偶而發現一兩個由應用邏輯處理導致的小bug,但最終都不會導致大的安全問題,而且整個代碼的運行權限較低,根本無從下手。看來GitHub做的滴水不漏,天衣無縫。但儘管如此,我還是想方設法絞盡腦汁地發現了GitHub的一些有趣漏洞,其中就包括它的一個OAuth授權驗證繞過漏洞。

GitHub的OAuth授權驗證機制

在6月份的時候,我開始測試GitHub的OAuth授權驗證機制代碼,簡單來說,這裡的GitHub OAuth授權驗證流程如下:

1、某第三方應用 (這裡暫且叫“Foo App”) 想要訪問GitHub用戶的數據,它會向GitHub用戶發送包含大量查詢信息的鏈接: https://github.com/login/oauth/authorize

2、之後,GitHub用戶端會顯示以下授權頁面:

繞過GitHub的OAuth授權驗證機制

3、如果GitHub用戶選擇允許第三方應用訪問,他需要點擊“Authorize” 按鈕,接著,就會跳轉匹配到Foo App的查詢字符串,這些字符串代碼後續將會訪問到GitHub用戶的相關數據;(當然,GitHub用戶也可以選擇拒絕Foo App的訪問)

在檢查該流程時,我首重查看了“Authorize”按鈕的具體實現行為,之後我發現該“Authorize”按鈕其中是一個獨立的HTML格式,它會發送一個包含CSRF token在內的隱藏表單字段的POST請求。當該POST請求被髮送後,此時其CSRF token是被驗證過的,也就是代表GitHub用戶想要授權給第三方APP訪問權限。這種猜測基本是合理的。

有意思的是,“Authorize”按鈕對應的終端URL鏈接也是/login/oauth/authorize,它和授權驗證頁面是一樣的URL,GitHub會根據HTTP請求方法的響應來確定如何執行下一步操作(GET請求會返回授權頁面的信息,而POST請求會得到相應的授權)。

這種行為切換實際上發生在Github的內部代碼中,路由router會把GET 和 POST 請求轉發到同一個控制器controller上,如下:

# In the router
match "/login/oauth/authorize", # For every request with this path...
:to => "[the controller]", # ...send it to the controller...
:via => [:get, :post] # ... as long as it's a GET or a POST request.
# In the controller
if request.get?
# serve authorization page HTML
else

# grant permissions to app
end

所以,最後路由router會接受GET 或 POST 請求,而控制器controller會檢查哪種請求被髮送了,從而執行後續動作。乍一看,這不算是什麼安全問題,但是,深入探究發現,路由router機制存在隱患。

Rails 路由能夠識別 URL 地址,並把它們分派給控制器動作或 Rack 應用進行處理。它還能生成路徑和 URL 地址,從而避免在視圖中硬編碼字符串。

HTTP HEAD請求時Rails路由在說謊

HEAD方法跟GET方法相同,只不過服務器響應時不會返回消息體。一個HEAD請求的響應中,HTTP頭中包含的元信息應該和一個GET請求的響應消息相同。這種方法可以用來獲取請求中隱含的元信息,而不用傳輸消息實體本身。也經常用來測試超鏈接的有效性、可用性和最近的修改。

自HTTP協議被創建以來,HTTP的HEAD方法就一直存在了,但是人們對它的使用較少。當服務器收到HEAD請求時,只會向客戶端發送迴響應頭,而不發送響應體,這有一些特殊用途。例如,在決定是否要開始下載文件之前,客戶端可以發送HEAD請求來檢查大文件的大小(通過內容長度響應頭來確定)。

顯然,編寫網絡應用程序的人通常不想花時間來實現HEAD請求的行為。可以理解的是,獲得一個有效的產品比符合超文本傳輸協議規範的特定部分更為重要。但總的來說,如果HEAD請求能夠得到正確處理,這是件好事,前提是應用程序開發人員不必手動處理它們。所以Rails以及其它的一些網絡框架採用了一個聰明的技巧: 它試圖將HEAD請求路由到與GET請求相同的地方 ,然後運行控制器代碼,以此省略掉消息響應體。

這看上去很好,但卻是一個漏洞百出的抽象概念,如果此時控制器發出request.get?的請求,對於這樣的請求,因為現在控制器是HEAD請求,而不是GET請求,所以將會返回false。

濫用HEAD請求

如果我們向 https://github.com/login/oauth/authorize? 發送一個授權驗證的HEAD請求,將會發生什麼情況?前面我們說過,Rails路由會把它當成GET請求來處理,所以它會被髮送到控制器中。但當HEAD請求到達控制器後,控制器會意識到這不是一個GET請求,所以控制器會檢查它是否是一個經過授權驗證的POST請求,之後, GitHub會找到請求中指定OAuth授權流程的APP,並給予相應的訪問授權。

這裡的利用點是,GitHub的CSRF防護機制要求所有授權驗證POST請求必須包含一個 CSRF token,但是HEAD請求由於不會造成過多影響,所以通常不需要CSRF token。但在此,我們可以無需告知目標用戶的方法,通過跨站方式向用戶發送一個給予任意OAuth權限的HEAD請求,以此實現我們的授權繞過目的。

最終效果是,如果目標Github用戶訪問了由攻擊者構造的頁面,攻擊者可以執行對目標Github用戶隱私數據的讀取或更改,可以 點擊此PoC頁面 進行體會(由於漏洞已經被修復,最終執行結果不再有效)

我向Github上報了該漏洞後,它們在三小時內就積極進行了修復,最終我也收穫了Github官方$25000的獎勵!是我做Github漏洞測試以來的最大一筆獎金。

漏洞上報及處理進程

2019-06-19 通過HackerOne向GitHub上報漏洞
2019-06-19 GitHub安全團隊確認漏洞
2019-06-20 漏洞修復,GitHub確認補丁已成功釋放
2019-06-26 GitHub推出修復版本的 Enterprise 2.17.3, 2.16.12、2.15.17 和 2.14.24
2019-06-26 GitHub獎勵我$25000


分享到:


相關文章: