劉嘉華 萬 明 周晨曦 張 攀
1(南瑞集團(tuán)有限公司(國網(wǎng)電力研究院有限公司) 江蘇 南京 211106)2(南京南瑞信息通信科技有限公司 江蘇 南京 210009)3(國家電網(wǎng)有限公司信息通信分公司 北京 100761)
隨著計算機(jī)技術(shù)應(yīng)用的不斷深化,軟件的需求和規(guī)模不斷增加,給開發(fā)人員帶來了新的挑戰(zhàn)。有效地使用開源軟件可以提高軟件開發(fā)效率,降低軟件開發(fā)成本。為了更快速高效地完成開發(fā),開源軟件得到了推崇。數(shù)據(jù)顯示,2018年各大平臺的工具包的下載量均有了不同程度的上漲,僅npm在2018年全年的下載次數(shù)就達(dá)到了令人難以置信的3 170億次。
開源軟件的廣泛使用在給開發(fā)人員提供極大便利的同時,也帶來了很多問題。(1)開源軟件漏洞與日俱增。2018年,npm的漏洞數(shù)量增長了47%,RHEL、Debian和Ubuntu增加了4倍多。兩年內(nèi)開源軟件的漏洞數(shù)量增長了88%。(2)開源軟件漏洞得不到重視和修復(fù)。37%的開源開發(fā)者在持續(xù)集成(Continuous Integration,CI)期間沒有實施任何類型的安全測試。54%的開發(fā)者沒有對Docker鏡像進(jìn)行任何安全測試。從漏洞添加至開源軟件包到修復(fù)漏洞的時間中位數(shù)超過2年。(3)開源軟件的使用引入了更多的漏洞。78%的漏洞來源于對開源軟件直接或間接的引用[1]。因此,針對開源軟件的漏洞檢測有著極其重要的意義。
漏洞檢測技術(shù)主要分為靜態(tài)分析[2-5]和動態(tài)分析[6-8]兩類。隨著人工智能的興起,研究人員開始嘗試將傳統(tǒng)技術(shù)與機(jī)器學(xué)習(xí)結(jié)合起來?,F(xiàn)有的基于機(jī)器學(xué)習(xí)的方法多用于軟件缺陷預(yù)測,但與軟件缺陷相比,項目內(nèi)含有的漏洞數(shù)量更少,因此檢測難度更大。Shin等[9]利用人工神經(jīng)網(wǎng)絡(luò)識別二進(jìn)制代碼中的函數(shù),他們將二進(jìn)制代碼根據(jù)字節(jié)碼進(jìn)行劃分,對采集到的2 200個二進(jìn)制代碼進(jìn)行訓(xùn)練和實驗,結(jié)果表明采用遞歸神經(jīng)網(wǎng)絡(luò)能夠更高效、更準(zhǔn)確地識別出二進(jìn)制代碼中的函數(shù)。HSOMiner是一種基于機(jī)器學(xué)習(xí)的程序分析技術(shù)[10],能夠快速高效地發(fā)現(xiàn)移動應(yīng)用中隱藏的敏感行為。Chowdhury等[11]從代碼復(fù)雜度、耦合度和內(nèi)聚度等角度對程序模塊進(jìn)行度量。
本文以靜態(tài)分析為基礎(chǔ),融合抽象語法樹、數(shù)據(jù)流、控制流分析的結(jié)構(gòu)化信息形成中間表示,并運(yùn)用較前沿的雙向LSTM神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)其隱含的漏洞模式以檢測漏洞。雙向LSTM是傳統(tǒng)LSTM的擴(kuò)展,可以提高序列分類問題的模型性能。在輸入序列的所有時間步長可用的問題中,雙向LSTM在輸入序列上訓(xùn)練兩個而不是一個LSTM。輸入序列中的第一個是原有的,第二個是輸入序列的反轉(zhuǎn)副本。這可以為網(wǎng)絡(luò)提供額外的上下文,從而更快、更充分地學(xué)習(xí)問題。對比實驗結(jié)果證明了該方法的有效性和可行性。
設(shè)計漏洞檢測方案之前,首先需要對漏洞做一個簡單的分析。以CWE(Common Weakness Enumeration)[12]列表中較為常見的數(shù)組索引的不正確驗證(Improper Validation of Array Index)漏洞為例,漏洞的產(chǎn)生需要滿足兩個必要條件:不安全的數(shù)據(jù)和缺少自定義的數(shù)據(jù)驗證,二者缺一不可。
圖1所示的一段代碼在運(yùn)行時顯然會發(fā)生數(shù)組索引溢出。但是只要任意破壞其中某一條件,能夠確保數(shù)據(jù)安全(見圖2)或者擁有自定義的數(shù)據(jù)驗證(見圖3),則該漏洞不復(fù)存在。
圖2 數(shù)據(jù)安全
圖3 自定義的數(shù)據(jù)驗證
經(jīng)過上述分析,要想確保漏洞檢測的準(zhǔn)確率,提取的源代碼特征中需要同時包含方法的數(shù)據(jù)和控制依賴關(guān)系。因此,本文在使用靜態(tài)分析提取源代碼特征的過程中引入了數(shù)據(jù)流和控制流分析。
本文提出了一種Java開源軟件漏洞檢測方法,其框架設(shè)計如圖4所示。
圖4 框架設(shè)計
該方法主要分為兩個部分:特征提取和模型訓(xùn)練。特征提取部分先為方法生成抽象語法樹(Abstract Syntax Tree,AST),依據(jù)節(jié)點類型提取關(guān)鍵變量,該類變量或接收外部輸入,有可能是污染源,或位于外部輸入的數(shù)據(jù)流傳播路徑上,有可能被污染。在此基礎(chǔ)上,運(yùn)用數(shù)據(jù)流分析變量與語句、方法間的數(shù)據(jù)依賴關(guān)系,并將數(shù)據(jù)類型替換為完整類型,如將String s替換為java.lang.String s。然后,運(yùn)用控制流分析獲取方法內(nèi)各語句間的控制(條件判斷、循環(huán)等)依賴關(guān)系,結(jié)合數(shù)據(jù)流分析的結(jié)果生成控制流圖(Control Flow Graph,CFG),保存方法的控制流信息。最后,遍歷生成的控制流圖,生成包含數(shù)據(jù)和控制依賴關(guān)系的中間表示。
模型訓(xùn)練部分先為中間表示貼上“0”(沒有安全漏洞)或“1”(含有安全漏洞)標(biāo)簽,生成對的集合。接著,對Code字段做分詞處理,將生成的中間表示轉(zhuǎn)換為token序列,并為每個token從1開始分配一個連續(xù)的唯一的整數(shù)索引。然后,將原序列中的每個token替換為其對應(yīng)的整數(shù)編碼。再將每個整數(shù)按照詞表的大小展開,索引位置1,其余位填0,形成One-hot編碼。考慮到One-hot矩陣過于稀疏,在搭建神經(jīng)網(wǎng)絡(luò)的過程增設(shè)Embedding層以壓縮向量,為矩陣降維。最后,將數(shù)據(jù)集劃分為訓(xùn)練集和測試集,運(yùn)用雙向LSTM訓(xùn)練出漏洞檢測模型。
抽象語法樹是源代碼的一種樹狀的表現(xiàn)形式,樹上的每個節(jié)點都表示源代碼中的一種結(jié)構(gòu),如圖5所示。遍歷抽象語法樹,根據(jù)節(jié)點類型可以非常容易地提取出源代碼中的關(guān)鍵變量,同時對用戶自定義的變量名做統(tǒng)一替換。例如,同樣是bool類型的變量,程序員A命名為isTrue,程序員B命名為isRight,程序員C命名為exist等,在遍歷抽象語法樹的過程中將統(tǒng)一被替換為b,同一方法中的不同bool類型變量用b1、b2區(qū)分。本實驗中,抽象語法樹的實現(xiàn)基于開源工具ANTLR4[13]。
圖5 抽象語法樹
控制流圖是一個方法或程序的抽象表現(xiàn),代表了一個程序執(zhí)行過程中會遍歷到的所有路徑。它用圖的形式表示一個過程內(nèi)所有基本塊執(zhí)行的可能流向。
圖6為圖3中的代碼示例生成的控制流圖,該圖由Soot[14]開源工具生成。抽象語法樹分析為方法選取關(guān)鍵節(jié)點,確定數(shù)據(jù)流分析的需求對象。數(shù)據(jù)流分析在方法間追蹤該對象的數(shù)據(jù)流向,并為其添加數(shù)據(jù)依賴關(guān)系,如將SecureRandom擴(kuò)充為java.security.SecureRandom。為了更好地追蹤數(shù)據(jù)的依賴關(guān)系,數(shù)據(jù)流分析過程中會產(chǎn)生一些以“$”開頭的用于過渡的臨時變量??刂屏鞣治鲆赃@些數(shù)據(jù)作為節(jié)點,將數(shù)據(jù)流分析所得的數(shù)據(jù)依賴關(guān)系保存在節(jié)點內(nèi),分析節(jié)點在方法內(nèi)部的控制依賴關(guān)系,并用有向邊表示出來,繼而生成了控制流圖。
圖6 控制流圖
遍歷上述控制流圖可以生成如圖7所示的基于Jimple的代碼的中間表示。
圖7 中間表示
將代碼處理成如圖7所示的中間表示主要有以下幾點好處:(1)簡化了Java語句,將原來復(fù)雜的語句簡化為15種基本語句,抽象程度較高;(2)保留了變量的類型;(3)對變量名做了統(tǒng)一替換,壓縮了詞表的大小。
預(yù)處理的工作主要分為以下幾個部分:(1)根據(jù)空格對生成的中間表示做分詞處理,將每條方法轉(zhuǎn)化成token序列。(2)為了將向量轉(zhuǎn)換為固定的維度以供訓(xùn)練,需要篩除長度超過500的token序列,并將長度不足500的序列以某一特殊填充符填充至500(在樣本長度的分布統(tǒng)計中,絕大多數(shù)樣本的token序列長度均在500以內(nèi),超過500的樣本所占比重不到5%。預(yù)處理需要將所有的token序列填充至相同長度,如果遷就少數(shù)過長樣本,將導(dǎo)致序列過長且填充大量無意義填充符,因此篩除此部分樣本,該操作不會影響實驗結(jié)果的準(zhǔn)確率和召回率)。(3)建立詞表,將所有token不重復(fù)地加入到詞表中,從1開始為詞表中的每個token分配一個連續(xù)的唯一的整數(shù)索引。(4)將所有token替換為它對應(yīng)的索引值。
One-hot編碼,又稱為獨(dú)熱編碼、一位有效編碼,其方法是采用N位狀態(tài)寄存器來對N個狀態(tài)進(jìn)行編碼,每個狀態(tài)都有它獨(dú)立的寄存器位,并且在任意時候只有一位有效。預(yù)處理部分將token序列轉(zhuǎn)化為整數(shù)序列其實就是在為轉(zhuǎn)化為One-hot編碼做準(zhǔn)備。以token序列表示的文本雖然可讀性比較好,卻無法用深度學(xué)習(xí),因此我們需要對其進(jìn)行特征數(shù)字化,擬采用的方法就是轉(zhuǎn)換為One-hot編碼。這樣做的好處一是解決了分類器不好處理離散數(shù)據(jù)的問題,二是在一定程度上起到了擴(kuò)充特征的作用,三是解決了token長度不一致的問題。
當(dāng)然該編碼的缺點也很明顯:(1)這是一個詞袋模型,不考慮詞與詞之間的順序信息,該缺點可以通過選取雙向LSTM學(xué)習(xí)順序信息來有效解決。(2)它假設(shè)詞與詞之間相互獨(dú)立,雖然在大多數(shù)情況下,詞與詞之間是相互影響的,但是由于該實驗的研究對象是源代碼,恰好符合這一情境,進(jìn)而轉(zhuǎn)變?yōu)橐环N優(yōu)勢。(3)它得到的特征是離散稀疏的,因此需要在模型中加入Embedding層對向量進(jìn)行壓縮降維。
One-hot編碼的實現(xiàn)是基于整數(shù)編碼的結(jié)果,將每個整數(shù)按照詞表大小的長度展開,索引位置1,其余位置0。
雖然變量名的替換在一定程度上對詞表進(jìn)行了壓縮,但是隨著項目數(shù)量的不斷增多,詞表的大小還是很容易就能達(dá)到萬的級別,這意味著一個token序列展開后的長度是百萬級的。因此,需要在模型中增加Embedding層對One-hot編碼進(jìn)行壓縮。其原理如圖8所示。
圖8 Embedding原理
原矩陣是一個m×n的矩陣,m是樣本個數(shù),n是壓縮前向量的維度,長度等于詞表的大小。引入一個n×p的嵌入矩陣,p是壓縮后向量的維度。由于One-hot向量的特殊性,將原矩陣中的某一條向量與嵌入矩陣做乘法就相當(dāng)于從嵌入矩陣中取出了某一行,而該行即為原向量的壓縮向量。經(jīng)過Embedding之后,原來大小為m×n的矩陣被壓縮為大小為m×p的壓縮矩陣。
為了更好地學(xué)習(xí)代碼的序列信息,實驗選取了雙向LSTM神經(jīng)網(wǎng)絡(luò)用于訓(xùn)練。雙向LSTM組合了前向LSTM與后向LSTM,所以可以包含更多的信息。該神經(jīng)網(wǎng)絡(luò)的架構(gòu)如圖9所示。
圖9 雙向LSTM
為了對本文提出的方法做進(jìn)一步的分析,該部分從最新的基于機(jī)器學(xué)習(xí)做漏洞檢測的成果中選取了兩個較為相似的工作VulDeePecker[16]與AE-KNN[17]進(jìn)行了對比實驗。實驗的計算機(jī)環(huán)境為:處理器為Intel Core i7-4790 3.60 GHz,內(nèi)存32 GB,硬盤1 TB,GPU Geforece GTX 1070 Ti。
該實驗采用的數(shù)據(jù)集來源于美國國家標(biāo)準(zhǔn)與技術(shù)研究院(NIST)的軟件保障參考數(shù)據(jù)集(SARD)[15]。該數(shù)據(jù)集包含了對各種漏洞的測試用例,對應(yīng)于CWE列表中112種安全漏洞類型。以CWE129(數(shù)組索引的不正確驗證)為例,其統(tǒng)計信息如表1所示。數(shù)據(jù)集按照8∶2劃分訓(xùn)練集和測試集。
表1 CWE129數(shù)據(jù)集統(tǒng)計
本實驗以準(zhǔn)確率和召回率來衡量方法的檢測結(jié)果。相關(guān)計算公式如下:
式中:TP表示模型判斷方法存在安全漏洞且漏洞真實存在;FP表示模型判斷方法存在安全漏洞而漏洞真實不存在;TN表示模型判斷方法不存在安全漏洞且漏洞真實不存在;FN表示模型判斷方法不存在安全漏洞而漏洞真實存在。FP和FN均屬于誤判。Precision為準(zhǔn)確率,表示模型判斷出真實存在安全漏洞的方法占全部存在安全漏洞方法的比例。Recall為召回率,表示模型判斷出的含有漏洞的方法數(shù)占全部不安全方法數(shù)的比例。F-measure是Precision和Recall的調(diào)和平均數(shù),是一種對準(zhǔn)確率和召回率進(jìn)行綜合考量的指標(biāo)。
實驗在GPU上訓(xùn)練模型,使用隨機(jī)梯度下降(Stochastic Gradient Descent,SGD)來訓(xùn)練和更新參數(shù)。批處理大小設(shè)置為128,LSTM隱藏狀態(tài)和詞嵌入的維度設(shè)置為200。參數(shù)梯度的上限設(shè)置為5,為避免過擬合現(xiàn)象的發(fā)生,設(shè)置dropout為0.3。具體參數(shù)配置如表2所示。
表2 實驗參數(shù)設(shè)置
本文首先人為地觀察模型的檢測結(jié)果,以做初步評估。由于在源代碼的中間表示中引入了數(shù)據(jù)和控制依賴,因此即使是對于兩個差異性極小的代碼段,模型依然能夠準(zhǔn)確地做出區(qū)分。如圖10所示的兩段源代碼嘗試從用戶指定的值構(gòu)建列表,同時引入了自定義的數(shù)據(jù)驗證以檢查傳入的untrustedListSize參數(shù),函數(shù)名、參數(shù)甚至結(jié)構(gòu)完全相同。但是,在上段代碼中,如果提供了0值,代碼將構(gòu)建一個大小為0的數(shù)組,然后嘗試在第一個位置存儲一個新的Widget,從而引發(fā)數(shù)組越界。本實驗中,模型能夠有效地對該類情況做出分類,這是許多傳統(tǒng)的漏洞檢測方法所不具備的。
圖10 檢測示例
為了進(jìn)一步驗證該方法的有效性,本文基于SARD數(shù)據(jù)集與現(xiàn)有的兩個工作VulDeePecker和AE-KNN做了對比實驗。實驗結(jié)果如表3所示。
表3 不同方法實驗結(jié)果對比 %
VulDeePecker首先提取源代碼中的API和庫方法調(diào)用,運(yùn)用代碼切片將源代碼分割成一個個code gadgets。然后用統(tǒng)一的標(biāo)識符替換掉用戶自定義的變量名和函數(shù)名,并為每個code gadgets貼上標(biāo)簽。最后選取雙向LSTM在數(shù)據(jù)集上訓(xùn)練出相應(yīng)模型。但是該方法忽略了部分控制流信息,因此在準(zhǔn)確率和召回率上稍顯遜色。
AE-KNN首先應(yīng)用圖形數(shù)據(jù)庫形成源代碼的代碼屬性圖,然后依據(jù)某種規(guī)則遍歷代碼屬性圖形成API序列并量化為特征向量,最后對特征向量進(jìn)行聚類,提取每一類中異常值排序高的樣本函數(shù)匹配漏洞庫,得到代碼中的漏洞。
實驗結(jié)果表明,本文方法在準(zhǔn)確率和召回率上均優(yōu)于對比方法。
為檢測開源軟件中存在的大量漏洞,本文提出了一種基于雙向LSTM的Java漏洞檢測方法。首先運(yùn)用靜態(tài)分析提取源代碼語義特征并生成中間表示,然后在將生成的中間表示映射為向量的同時為其貼上“是否安全”的標(biāo)簽,最后運(yùn)用神經(jīng)網(wǎng)絡(luò)在數(shù)據(jù)集上訓(xùn)練生成漏洞檢測模型。靜態(tài)分析主要包括抽象語法樹、數(shù)據(jù)流和控制流分析,目的是提取方法完整的數(shù)據(jù)和控制依賴,以提高漏洞檢測的準(zhǔn)確性。神經(jīng)網(wǎng)絡(luò)選擇雙向LSTM,目的是可以更好地學(xué)習(xí)源代碼的序列信息。
實驗結(jié)果表明,模型在測試集上取得了93.8%的準(zhǔn)確率和90.1%的召回率,均優(yōu)于現(xiàn)有的基于機(jī)器學(xué)習(xí)的漏洞檢測方法,驗證了本文方法的優(yōu)越性。后續(xù)工作主要分為兩個方面,一是進(jìn)一步提升模型的精確度,減少人工參與,實現(xiàn)開源軟件漏洞的自動化檢測;二是嘗試將本文方法擴(kuò)展到除Java外的其他語言,驗證其適用性。