深入考察解釋型語言背后隱藏的攻擊面,Part 1(上)
在本文中,我們將與讀者一起深入考察解釋型語言背后隱藏的攻擊面。
簡介
攻擊面就像一層蛋糕。我們通常將軟件攻擊面定義為任何對攻擊者控制的輸入做出反應或受其影響的東西。在開發高級解釋型語言應用程序時,很容易做出這樣的假設:語言本身的運行時系統或代碼庫中的底層代碼是可靠的。
經驗證明,這種假設是錯誤的。
通常情況下,在高級語言的內存管理功能的實現代碼中,往往存在著相對脆弱的基于C/C++的攻擊面。這種問題可能存在于語言本身的核心實現中,也可能存在于將向高級語言提供基于C/C++的庫的第三方語言生態系統中。
這些第三方庫通常是通過顯式外部函數接口(FFI)或其他形式的API轉換包裝器來提供其功能的,因為這些API轉換包裝器便于從較低級別代碼中使用較高層的對象,或反之。它們通常被稱為本機模塊、擴展或FFI首字母縮寫的某種形式,這樣的攻擊面的特點在于,在高級應用程序的安全上下文中,將與C/C++代碼相關的各種內存管理漏洞都暴露給了攻擊者。
在本系列文章中,我們將探討在高級語言應用程序上下文中有時被忽略的C/C++攻擊面的例子,其中,有些示例是很久之前的,有些是當前發現的。在第一部分中,我們將為解釋語言的低級別攻擊面提供相應的背景知識,并展示一些跨語言的安全漏洞。在以后的文章中,我們將介紹針對現代解釋語言生態系統的新型攻擊技術,以及哪些特征傾向于使這些表面上看是輕微的編程錯誤成為實際可利用的安全漏洞。
本系列文章主要面向希望在如何、為何以及在何處將攻擊面暴露給潛在的惡意輸入等方面做出明智決定的開發人員,因此,我們將在需要時對軟件漏洞的利用理論給出相應的解釋。
為了便于討論,我們對軟件漏洞利用的定義大致如下:利用輸入內容的影響,將目標進程從其預期的狀態空間轉移到非預期的狀態空間的過程。
擺放餐具
在判斷某個代碼問題“只是”一個軟件bug,還是一個安全漏洞時,完全取決于相應的上下文。一個bug被判定為安全漏洞,應滿足下列條件:它們應以某種狀態、方式或形式幫助攻擊者發動進攻。這本身是高度依賴于上下文的,尤其是在處理核心語言問題時。受影響的API如何以及在何處暴露于攻擊者的輸入,決定了是否可以將其視為安全漏洞。
在解釋型語言的上下文中,存在兩種主要的攻擊方案。在第一種情況下,攻擊者可以在目標解釋器上運行其自己的程序,通常,他們的目標是破壞解釋器本身的安全措施,以誘使托管解釋器的進程跨越某種安全邊界。
這樣的示例包括Web瀏覽器使用的Javascript解釋器中的安全漏洞。這個主題的變體包括以下場景:攻擊者已在攻擊的第一階段獲得了運行任意解釋型代碼的能力,但解釋器本身實施了某種限制(例如:由于存在嚴格的PHP配置,使其無法使用命令執行功能),從而限制了他們進一步開展攻擊的能力。
當攻擊者對目標解釋器擁有完全訪問權限時,通常會在核心解釋器的實現中查找漏洞。例如,在各種Javascript解釋器中存在內存管理漏洞,通常能演化成Web瀏覽器中可利用的客戶端漏洞。由于攻擊者完全控制了解釋器的狀態,因此,從攻擊者的角度來看,解釋器本身的任何bug都可能是非常有用的。
在第二種情況下,攻擊者可以利用解釋型語言實現中的某些邏輯,向其提供輸入,但無法直接與解釋器進行交互。在這些情形中,攻擊者的攻擊范圍受到他們實際可以直接或間接向其傳遞數據的API的限制。
當攻擊者只能控制用高級語言實現的目標進程的輸入時,他們就只能影響接收這些輸入的邏輯,從而限制了他們的選擇余地。在這些情況下,通過深入挖掘找出處理輸入的底層攻擊面,可以發現從較高級別邏輯角度看不到的漏洞。
從攻擊者的角度來看,我們的主要目標是增加攻擊面的深度。不要橫著挖,而是往下挖!
剝開糖衣
關于解釋型語言可以在較低級別進行利用的漏洞,已經由來已久。在本文中,我們不會完整的介紹這些漏洞的歷史,但是,我們將深入研究一些有趣的例子——它們為我們展示了高級編程語言的bug是如何轉化為底層編程語言的安全漏洞的——希望這些“老洞”能夠激發讀者新的靈感。
歷史上的示例:當Perl格式化出錯時
2005年的Perl代碼中存在的格式字符串漏洞是一個有趣的案例。為了充分理解這個安全問題,我們首先要快速回顧一下C程序中格式字符串漏洞利用方面的基礎知識。
C格式字符串漏洞簡介
雖然許多人認為格式字符串錯誤由于易于檢測而在很大程度上已被根除,但它們仍然不時出現在意想不到的地方。
從解釋型語言與低級代碼交互的上下文中考慮格式字符串錯誤也很有趣,因為它們可能會在較高級別上預處理攻擊者控制下的格式字符串,然后直接傳遞給較低級別的格式化函數。這種延遲型格式化問題并不少見,尤其是當格式串的源和所述格式串的目的地之間存在強烈的邏輯分離時,特別容易出現這種問題。
簡單地說,格式字符串錯誤是一類錯誤,其中攻擊者將自己的格式字符串數據提供到格式化函數中,例如printf(attacker_controll)。然后,他們可以濫用對受控格式說明符的處理,以實現對目標進程空間的讀寫原語。
對此類漏洞的實際攻擊主要依賴于濫用%n和%hn類格式說明符的能力。這些格式符會命令格式化函數將打印字符的當前運行計數分別寫入整型(%n)或短整型(%hn)變量中,例如printf(“abcd%n”,&count)將通過指針參數將值4寫入整型變量count中。
同樣,在格式化函數的輸出對攻擊者可見的情況下,攻擊者只需提供期望打印變量值(例如printf("%x%x%x%x"))的格式標識符,即可轉儲內存內容。當將預期的目標指針值與其%n個對應值對齊時,這種“吃掉”堆棧的能力也變得非常重要。
如果攻擊者能夠向格式化函數的調用堆棧提供受控數據(通常是通過惡意格式字符串本身來實現的),并禁用所有編譯器緩解措施,則攻擊者可以將對寫入字符計數器的控制與對%n/%hn將寫入的指針值的控制相結合,這樣的話,他們就可以將自己控制的值寫入指定的內存位置了。
通過使用諸如在格式說明符上設置精度/寬度等技巧,將寫入字符計數器設置為特定值,并在支持的情況下設置直接參數訪問索引,即使是少量的格式的字符串輸入也能轉化為攻擊者強大的攻擊原語。
C語言格式化函數中的直接參數訪問(DPA)特性,允許我們指定用于格式標識符的參數的索引。例如printf("%2$s %1$s\n", "first", "second") 將打印“ second first”,因為第一個字符串格式標識符指定了參數 2 (2$) ,第二個字符串格式標識符指定了參數1(1$)。同樣,從攻擊者的角度來看,使用DPA可使您直接偏移到存放給定%n/%hn寫入所需的目標指針值的堆棧位置。理解DPA的用途對于回顧歷史上的Perl示例非常重要。
小結
通常情況下,在高級語言的內存管理功能的實現代碼中,往往存在著相對脆弱的基于C/C++的攻擊面。這種問題可能存在于語言本身的核心實現中,也可能存在于將向高級語言提供基于C/C++的庫的第三方語言生態系統中。本文中,我們為讀者介紹了與此緊密相關的C格式字符串漏洞方面的知識,在下一篇文章中,我們將為讀者介紹這些底層實現是如何影響解釋型語言的安全性的。