新聞中心
接上文:

- 《深入考察解釋型語言背后隱藏的攻擊面,Part 1(上)》
- 《深入考察解釋型語言背后隱藏的攻擊面,Part 1(下)》
在本系列關(guān)于解釋型語言底層攻擊面的第一篇文章中,我們了解到,即使在Javascript、Python和Perl等解釋型語言的核心實(shí)現(xiàn)中,內(nèi)存安全也不是無懈可擊的。
在本文中,我們將更加深入地探討,在通過外部函數(shù)接口(Foreign Function Interface,F(xiàn)FI)將基于C/C++的庫“粘合”到解釋語言的過程中,安全漏洞是如何產(chǎn)生的。正如我們之前所討論的,F(xiàn)FI充當(dāng)用兩種不同語言編寫的代碼之間的接口。例如,使一個(gè)基于C語言的庫可用于Javascript程序。
FFI負(fù)責(zé)將編程語言A的對(duì)象翻譯成編程語言B可以使用的東西,反之亦然。為了實(shí)現(xiàn)這種翻譯,開發(fā)人員必須編寫特定于語言API的代碼,以實(shí)現(xiàn)兩種語言之間的來回轉(zhuǎn)換。這通常也被稱為編寫語言綁定。
從攻擊者的角度來看,外部語言綁定代表了一個(gè)可能的攻擊面。當(dāng)處理一個(gè)從內(nèi)存安全語言翻譯成內(nèi)存不安全語言(如C/C++)的FFI時(shí),開發(fā)者就有可能引入內(nèi)存安全漏洞。
即使高層的語言被認(rèn)為是內(nèi)存安全的,同時(shí)目標(biāo)外部代碼也經(jīng)過了嚴(yán)格的安全審查,但是,在兩種語言之間架起橋梁的代碼中,仍可能潛伏著可利用的漏洞。
在這篇文章中,我們將仔細(xì)研究兩個(gè)這樣的漏洞,我們將一步步地了解攻擊者如何評(píng)估你的代碼的可利用性的。本文的目的是提高讀者對(duì)exploit開發(fā)過程的理解,而不僅僅是針對(duì)一個(gè)具體的案例,而是從概念的角度來理解。通過了解exploit開發(fā)人員如何思考您的代碼,幫您建立防御性的編程習(xí)慣,從而編寫出更安全的代碼。
在我們的案例研究中,我們將考察兩個(gè)看起來非常相似的bug,然而只有一個(gè)是bug,而另一個(gè)則是一個(gè)安全漏洞。兩者都存在于綁定Node.js包的C/C++代碼中。
node-sass
Node-sass是一個(gè)庫,它將Node.js綁定到LibSass(一款流行的樣式表預(yù)處理器Sass的C版本)。雖然node-sass最近被棄用了,但它每周仍有500萬次以上的下載量,所以,它是一個(gè)非常有價(jià)值的審計(jì)對(duì)象。
當(dāng)閱讀node-sass綁定時(shí),我們注意到以下代碼模式:
- int indent_len = Nan::To
- Nan::Get(
- options,
- Nan::New("indentWidth").ToLocalChecked()
- ).ToLocalChecked()).FromJust();
- [1]
- ctx_w->indent = (char*)malloc(indent_len + 1);
- strcpy(ctx_w->indent, std::string(
- [2]
- indent_len,
- Nan::To
- Nan::Get(
- options,
- Nan::New("indentType").ToLocalChecked()
- ).ToLocalChecked()).FromJust() == 1 ? '\t' : ' '
在[1]處,我們注意到一個(gè)受控于用戶輸入的32位整數(shù)值被用于內(nèi)存分配。如果該用戶提供的整數(shù)為-1,則整數(shù)算術(shù)表達(dá)式indent_len + 1的值將變成0。在[2]處,原始負(fù)值用于創(chuàng)建由indent_len字符組成的制表符或空格字符串,其中indent_len值為負(fù),現(xiàn)在將變成一個(gè)相當(dāng)大的正值,因?yàn)閟td::string構(gòu)造函數(shù)期望接收無符號(hào)的長度參數(shù),其類型為size_t。
在JS API級(jí)別,我們注意到indentWidth的檢索方式如下所示:
- /**
- * Get indent width
- *
- * @param {Object} options
- * @api private
- */
- function getIndentWidth(options) {
- var width = parseInt(options.indentWidth) || 2;
- return width > 10 ? 2 : width;
- }
此處的目的是確保indentWidth >= 2或 <= 10,但實(shí)際上這里僅檢查了上界,并且parseInt允許我們提供負(fù)值,例如:
- var sass = require('node-sass')
- var result = sass.renderSync({
- data: `h1 { font-size: 40px; }`,
- indentWidth: -1
- });
這將觸發(fā)一個(gè)整數(shù)溢出,從而導(dǎo)致分配的內(nèi)存不足,并進(jìn)一步導(dǎo)致后續(xù)的內(nèi)存被破壞。
為了解決這個(gè)問題,node-sass應(yīng)該確保在將用戶提供的indentWidth值傳遞給底層綁定之前,先檢查該值的下界和上界。
全面地檢查輸入,并明確地將它們的取值范圍限制在對(duì)程序邏輯有意義的范圍內(nèi),這將很好地幫助您養(yǎng)成一種通用的防御性編程習(xí)慣。
所以我們來總結(jié)一下。這里的bug模式是什么?整數(shù)溢出,導(dǎo)致堆分配不足,其后的內(nèi)存填充可能會(huì)破壞相鄰的堆內(nèi)存。聽起來確實(shí)值得分配CVE,不是嗎?
然而,雖然這個(gè)整數(shù)溢出確實(shí)會(huì)導(dǎo)致堆內(nèi)存分配不足,但這個(gè)bug并不代表就是一個(gè)漏洞,因?yàn)檫@個(gè)樣式表輸入很可能不是攻擊者控制的,并且在任何堆破壞發(fā)生之前,都會(huì)拋出std::string異常。即使發(fā)生了堆損壞,也只是一個(gè)非常有限的控制覆蓋(借助于一個(gè)非常大的indent_len的制表符或空格字符),所以,實(shí)際被利用的可能性很低。
- anticomputer@dc1:~$ node sass.js
- terminate called after throwing an instance of 'std::length_error'
- what(): basic_string::_S_create
- Aborted (core dumped)
結(jié)論:只是一個(gè)bug。
那么,什么情況下攻擊者才會(huì)對(duì)這樣的bug感興趣呢?攻擊者能夠?qū)τ|發(fā)bug的輸入施加影響。在這種情況下,不太可能有人為node-sass綁定提供受控于攻擊者的輸入。同時(shí),內(nèi)存破壞原語本身的控制能力也會(huì)非常有限。雖然確實(shí)存在這樣的情況:即使是非常有限的堆損壞也足以充分利用某個(gè)缺陷,但通常攻擊者會(huì)更樂于尋求具有某些控制權(quán)的情形,比如可以控制用于破壞內(nèi)存的東西,或者可以控制覆蓋的內(nèi)存數(shù)量。最好是兩者兼而有之。
在這種情況下,即使std::string構(gòu)造函數(shù)沒有退出,攻擊者也必須用空格或制表符進(jìn)行大規(guī)模的覆蓋,以控制進(jìn)程。雖然這并非完全不可能,但考慮到對(duì)周圍內(nèi)存布局的足夠影響和控制,可能性仍然偏低。
在這種情況下,我們通??梢酝ㄟ^回答下面的三個(gè)問題,來進(jìn)行一個(gè)簡單的可利用性“嗅覺測(cè)試”:
- 攻擊者是如何觸發(fā)這個(gè)bug的?
- 攻擊者控制了哪些數(shù)據(jù),控制到什么程度?
- 哪些算法受到攻擊者控制的影響?
除此之外,可利用性主要取決于攻擊者的目標(biāo)、經(jīng)驗(yàn)和資源。這些我們可能一無所知。除非您花了很多時(shí)間實(shí)際編寫exploit,否則很難確定某個(gè)問題是否可利用。特別是當(dāng)您的代碼被其他軟件使用時(shí),即您編寫的是庫代碼,或者是一個(gè)更大系統(tǒng)中的一個(gè)組件。在一個(gè)孤立的環(huán)境中,某個(gè)錯(cuò)誤看起來只是bug,在更大的范圍內(nèi)可能就是安全漏洞。
雖然常識(shí)對(duì)于確定可利用性有很大的幫助,但在時(shí)間和資源允許的情況下,任何可以由用戶控制(或影響)的輸入觸發(fā)的bug都是潛在的安全漏洞,因此,將其視為安全漏洞是非常明智的做法。
png-img
對(duì)于我們的第二個(gè)案例研究,我們將考察GHSL-2020-142。這個(gè)bug存在于提供libpng綁定的node.js png-img包中。
當(dāng)加載PNG圖像進(jìn)行處理時(shí),png-img綁定將使用PNGIMG::InitStorage函數(shù)來分配用戶提供的PNG數(shù)據(jù)所需的初始內(nèi)存。
- void PngImg::InitStorage_() {
- rowPtrs_.resize(info_.height, nullptr);
- [1]
- data_ = new png_byte[info_.height * info_.rowbytes];
- [2]
- for(size_t i = 0; i < info_.height; ++i) {
- rowPtrs_[i] = data_ + i * info_.rowbytes;
- }
- }
在[1]處,我們觀察到為一個(gè)大小為info_.height * info_.rowbytes的png_byte數(shù)組分配了相應(yīng)的內(nèi)存。其中,結(jié)構(gòu)體成員height和rowbytes的類型都是png_uint_32,這意味著這里的整數(shù)算術(shù)表達(dá)式肯定是無符號(hào)32位整數(shù)運(yùn)算。
info_.height可以直接作為32位整數(shù)從PNG文件提供,info_.rowbytes也可以從PNG數(shù)據(jù)派生。
這種乘法運(yùn)算可能會(huì)觸發(fā)整數(shù)溢出,導(dǎo)致data_內(nèi)存區(qū)域分配不足。
例如,如果我們將info_.height設(shè)置為0x01000001,而info_.rowbytes的值為0x100,那么生成的表達(dá)式將是(0x01000001 * 0x100) & 0xffffffff ,其值為0x100。這樣的話,data_將作為一個(gè)0x100大小的png_byte數(shù)組來分配內(nèi)存,這明顯不夠用。
隨后,在[2]處,將使用行數(shù)據(jù)指針填充rowPtrs_array,這些指針指向所分配的內(nèi)存區(qū)的邊界之外,因?yàn)閒or循環(huán)條件是對(duì)原始的info_.height值進(jìn)行操作的。
一旦實(shí)際的行數(shù)據(jù)被從PNG文件中讀取,任何與data_區(qū)域相鄰的內(nèi)存都可能被攻擊者控制的行數(shù)據(jù)覆蓋,最高可達(dá)info_.height * info_.rowbytes字節(jié),這給任何潛在的攻擊者提供了大量可控的進(jìn)程內(nèi)存。
需要注意的是,根據(jù)攻擊者的意愿,可以通過不從PNG本身提供足夠數(shù)量的行數(shù)據(jù)來提前停止覆蓋,這時(shí)libpng錯(cuò)誤例程就會(huì)啟動(dòng)。任何后續(xù)處理錯(cuò)誤路徑的程序邏輯都會(huì)在被破壞的堆內(nèi)存上運(yùn)行。
這很有可能導(dǎo)致一個(gè)高度受控(無論是內(nèi)容還是大小)的堆溢出漏洞,我們的直覺是,這個(gè)bug可能是一個(gè)可利用的安全漏洞。
下面,讓我們來回答可利用性問題,以確定這個(gè)bug是否對(duì)攻擊者具有足夠的吸引力。
攻擊者是如何觸發(fā)該bug的?
這個(gè)bug是由攻擊者提供的PNG文件觸發(fā)的。攻擊者可以完全控制在png-img綁定中作用于PNG的任何數(shù)據(jù),并廢除文件格式完整性檢查所施加的任何限制。
因?yàn)楣粽弑仨氁蕾囉诩虞d的惡意PNG文件,我們可以假設(shè)任何利用邏輯都可能必須包含在這個(gè)單一的PNG文件中。這意味著,攻擊者與目標(biāo)Node.js進(jìn)程反復(fù)交互的機(jī)“可能”更少,例如,實(shí)施信息泄露,以幫助后續(xù)的漏洞利用過程繞過任何系統(tǒng)級(jí)別的緩解措施,如地址空間布局隨機(jī)化(ASLR)。
我們說“可能”,是因?yàn)槲覀儫o法預(yù)測(cè)png-img的實(shí)際使用情況。換句話說,也可能存在這樣的使用情況:存在可重復(fù)的交互機(jī)會(huì),來觸發(fā)該bug或進(jìn)一步幫助利用該bug。
攻擊者能夠控制哪些數(shù)據(jù),控制到什么程度?
攻擊者可以提供所需的height和rowbytes變量,以便對(duì)整數(shù)運(yùn)算和后續(xù)的整數(shù)封裝(integer wrap)進(jìn)行精細(xì)控制。被封裝的值用于確定data_數(shù)組的最終分配內(nèi)存的大小。它們也可以通過PNG圖像本身提供完全受控的行數(shù)據(jù),這些數(shù)據(jù)通過rowPtrs數(shù)組中的越界指針值填充到越界內(nèi)存中。他們可以通過提前終止提供的行數(shù)據(jù),精細(xì)控制這個(gè)攻擊者提供的行數(shù)據(jù)有多少被填充到內(nèi)存中。
簡而言之,攻擊者可以通過精細(xì)控制內(nèi)容和長度來覆蓋任何與data_相鄰的堆內(nèi)存。
哪些算法會(huì)受到攻擊者控制的影響?
由于我們處理的是堆溢出,攻擊者的影響擴(kuò)展到任何涉及被破壞的堆內(nèi)存的算法。這可能涉及Node.js解釋器代碼、系統(tǒng)庫代碼,當(dāng)然還有綁定代碼和任何相關(guān)庫代碼本身。
小結(jié)
在本文中,我們將深入地探討,在通過外部函數(shù)接口(Foreign Function Interface,F(xiàn)FI)將基于C/C++的庫“粘合”到解釋語言的過程中,安全漏洞是如何產(chǎn)生的。由于篇幅過長,我們將分為多篇進(jìn)行介紹,更多精彩內(nèi)容,敬請(qǐng)期待!
本文翻譯自:?https://securitylab.github.com/research/now-you-c-me-part-two
文章名稱:深入考察解釋型語言背后隱藏的攻擊面,Part2(一)
本文地址:http://m.5511xx.com/article/dhcogig.html


咨詢
建站咨詢
