一個新的令人興奮的漏洞(對不起,我們很容易為這些事情感到興奮??)已經在Ruby中發布了。CVE-2018-8778是一個緩沖區整數溢出,由String#unpack觸發。向發現這個漏洞的Eyal Itkin致敬!在本文中,我們將深入探討這個漏洞,展示如何利用漏洞以及如何防護漏洞。
我們所做的幾乎所有有意義的計算都是基于存儲在內存中的數據結構(對象)而完成的。
每個對象都有一個定義的大小和內存中的字段布局。因此,查看內存的人可以看到我們的對象被填充為為給定長度的二進制數據(零和一)。這就是我們所說的緩沖區(內存區域)。作為開發人員,在操作我們的對象時,我們應該在給定的緩沖區內工作,并且不應該在我們的對象被申請之前/之后進行讀/寫。
Ruby在所謂的堆上分配數據。堆是一個內存空間(另一個是棧)。幾乎每個Ruby對象都會在那里保存。
因此,緩沖區under-read表示攻擊者訪問目標緩沖區之前內存位置的漏洞。這通常發生在指針或其索引遞減到緩沖區之前的位置時。
這是一個嚴重的漏洞,但嚴重程度實際上取決于應用程序處理的數據。它可能會導致敏感信息的暴露或可能導致崩潰。您可能會泄漏數據,如 tokens,數據庫憑據,會話cookie甚至是轉賬信用卡號碼。
這里是該漏洞的公告。為了更好地理解它,我們首先在此處深入研究修復提交的Ruby源代碼(是的,可以看到它是SVN修訂版,因為Ruby歷史早于Git!)。
該漏洞位于String#unpack方法內部。此方法str根據提供的字符串格式進行解碼,返回提取的每個值的數組(您可以在RubyDoc上閱讀更多關于它的信息)。格式字符串由一系列單字符指令(數字,“*”,“_”或“!”)組成,并可由@指定數據的位置。這就是問題所在。
從測試中,我們可以看到只需要使用特定的格式來觸發它。這種格式字符串實際上就像一個小程序。字符串“@42C10”解碼為:跳過42個字節,然后解碼10個8位整數。
這里的問題是偏移量驗證不足。如果用@傳遞一個很大的值,那么unpack會跳過負數個字節數。這就會解碼緩沖區外的數據并返回。所以攻擊者可以使用它來讀取堆上的敏感數據。
未驗證偏移量是一個經典的錯誤,稱為整數溢出。當使用帶符號整數時,試圖解碼一個巨大的無符號整數值,解碼值將是一個負數。這給我們一種方法去返回一個負的偏移。與此相關,第一個阿麗亞娜5型火箭墜毀就是因為這個…相關鏈接。
String#unpack實際上是在C語言的Ruby核心源代碼中定義的。正如我們在修復提交中所看到的,表示為字符串(C語言中的 char *)的偏移量必須轉換為整數值。為此,Ruby使用一個調用宏STRTOUL,然后調用ruby_strtoul(在ruby.h中定義)。該名稱似乎告訴了我們這將輸出一個無符號的長整數。
unsigned long ruby_strtoul(const char *str, char **endptr, int base);
直到這里,沒有問題,字符串“18446744073709551416”被正確解碼為長整數18446744073709551416。然而,這個值被存儲在len中時被聲明為一個有符號long整數。這樣做將無符號數轉換為有符號數。所以導致18446744073709551416變成-200。
以下一起討論一些問題:
如何利用這個漏洞
在實時應用程序中利用漏洞的第一步是Poc驗證。我們首先嘗試從irb交互式shell 讀取內存。我們將使用漂亮的hexdump gem來顯示以便提供給讀者更容易閱讀的東西,為了簡單我們使用單線程。
那么我們在這里做什么?我們有一個關于leak(leak)大小的參數,我們計算這個龐大的數字,然后將其解碼為一個負整數。然后我們創建一個小緩沖區并使用這兩個值使用格式字符串將其解壓縮。我們基本上是這樣說的:跳到這個巨大的偏移量(在-leak字節處結束)并讀取leak + 4字節。
根據請求返回204字節。
通過傳遞一個大整數作為偏移量,我們在內存中開始BUFF之前返回了200個字節,然后讀取204個字節(所使用的生成格式字符串:)@18446744073709551416C204。
作為完整性檢查,我們可以在ASCII轉儲部分(BUFF)的末尾正確地看到我們應該使用的緩沖區的內容。如果Ruby不易受攻擊,那么在BUFF開始之前,它不應該跳過去讀取內存。
我們如何從PoC走向實際的利用?
我們首先需要找到一個運行在易受攻擊的Ruby(<= 2.5.1)應用程序上的應用程序。Ruby on Rails應用程序就很好,因為我們可以遠程攻擊它,并且通常包含有趣的秘密。這個Rails應用程序需要有一個參數受攻擊者控制的String#unpack調用format。String#unpack調用比你想象的更常見。它們通常用于解碼來自其他地方的數據(像數據庫驅動程序通常是此用戶的用戶)。因此,要知道您是否受到這個漏洞影響,您可能還需要查看所有依賴關系源代碼…
如果我們有這樣的應用程序,只需從上面發送我們生成的惡意格式字符串,就可以從應用程序中提取盡可能多的數據。這允許我們讀取并且可能提取存儲在存儲器中的所有秘密(數據庫憑證,令牌),還有那些僅通過應用程序(在并發請求中的客戶信用卡號碼或用戶會話)轉移的數據。
如何建立補救
當然,最簡單的修復方法是簡單地在您的機器上更新Ruby。在現實世界中,這并不總是可以快速實現的。現實促使我們制定了一個解決方案,即使目前還不能更新他們的Ruby版本,也可以保護所有Sqreen用戶免受CVE-2018-8778的攻擊。
Sqreen對于開發新的保護措施有兩項主要要求。
首先,我們不能以誤報(即阻止合法請求)來破壞用戶的應用程序 。
其次,對性能的影響應該是幾乎沒有的的。
分析幾個選項后,我們決定最好的解決方案是“簡單地”掛鉤該String#unpack方法,并檢查包含的參數@在格式字符串中是否包含大偏移量。這里的關鍵是確保這個格式字符串不是來自當前的請求參數。
TWO_GIGABYTES= 2**31
return false unless format_string.include?('@')
return false unless user_parameters.include?(format_string)
offset = parse(format_string)
return offset > TWO_GIGABYTES
現在我們來看一個例子:
1.格式字符串`C10` 在第一行停止處理?未檢測到攻擊
2.格式字符串“@10c12”
不在用戶參數中 停止在第二行?未檢測到攻擊
如果它來自用戶參數(代碼可能很脆弱),我們檢查偏移大小10并停止處理?未檢測到攻擊
以`@ 18446744073709551416C204`作為格式字符串,偏移量`18446744073709551416`大于2 ** 31?檢測到攻擊并將被阻止!
就是這樣。經過大量測試后,我們將此規則部署到我們的用戶。現在,它們都受到保護,免受此緩沖區未充分讀取的漏洞影響,并且可以在時間正確時更新其Ruby版本。所有這些都是在披露漏洞和充分保護我們的客戶之間不到21個小時內實現的。