郭 帆,范威威
江西師范大學(xué)計(jì)算機(jī)信息工程學(xué)院,南昌 330022
雖然Web 應(yīng)用提供了方便性、靈活性、可用性和互操作性,但是它們也容易受到各種各樣的安全威脅。在《OWASP Top 10-2017》發(fā)布的十大Web 應(yīng)用安全風(fēng)險(xiǎn)[1]中,處于A1 級別的注入漏洞是其中最重要的一種,而SQL 注入攻擊(structured query language injection attack,SQLIA)漏洞最為常見并且使用范圍最廣。SQLIA 會(huì)讓攻擊者無限制地訪問底層數(shù)據(jù)庫,一旦攻擊成功,結(jié)果往往是災(zāi)難性的。
污點(diǎn)分析(taint analysis)可以有效防御SQLIA,它是一種信息流分析技術(shù),通過對敏感數(shù)據(jù)進(jìn)行標(biāo)記進(jìn)而跟蹤標(biāo)記數(shù)據(jù)在程序中的傳播過程和路徑,以找到系統(tǒng)中的安全漏洞[2],主要分為靜態(tài)分析和動(dòng)態(tài)分析。
靜態(tài)污點(diǎn)分析從源代碼中抽取語法、語義特征并記錄數(shù)據(jù)流向,判斷污染數(shù)據(jù)的產(chǎn)生、傳遞和使用[3]。Medeiros等[4]結(jié)合多種方法發(fā)現(xiàn)源代碼中的漏洞并使其具有較少誤報(bào),首先使用污點(diǎn)分析發(fā)現(xiàn)候選漏洞,然后采用數(shù)據(jù)挖掘方法來預(yù)測假陽,誤報(bào)率較低。Maltitz 等[2]提出了一種基于靜態(tài)污點(diǎn)分析的形式化的隱私分析方法,用來簡化軟件體系結(jié)構(gòu)的高級隱私屬性的設(shè)計(jì)和審核過程。Dahse 等[5]通過對數(shù)據(jù)庫、文件名和會(huì)話變量的字符串分析來得到二階數(shù)據(jù)庫訪問信息流,可以檢測二階SQLIA 和多階漏洞。Hauzar 等[6]提出了一個(gè)PHP(hypertext preprocessor)靜態(tài)分析框架,能夠自動(dòng)解析動(dòng)態(tài)語言通用的特性,能夠獨(dú)立地定義動(dòng)態(tài)語言的值和堆分析,并能自動(dòng)組合它們來查找實(shí)際存在的安全漏洞,假陽性率較低。Stiévenart 等[7]開發(fā)了一個(gè)高度模塊化的靜態(tài)分析框架SCALA-AM,該框架通過分離操作語義實(shí)現(xiàn)模塊化,能夠擴(kuò)展支持多種語言且包含多種機(jī)器抽象,能夠按需實(shí)現(xiàn)不同的污染分析方案。王蕾等[8]創(chuàng)新地將數(shù)據(jù)流分析框架擴(kuò)展成稀疏形式,利用稀疏優(yōu)化方法對靜態(tài)污點(diǎn)分析中無關(guān)聯(lián)的污點(diǎn)傳播進(jìn)行消除,以此達(dá)到時(shí)間和空間的開銷優(yōu)化,但未實(shí)現(xiàn)別名間的強(qiáng)更新和保守子域的精確計(jì)算。馬金鑫等[9]提出并實(shí)現(xiàn)了一種基于執(zhí)行蹤跡離線索引的污點(diǎn)分析方法,以字節(jié)為粒度,首次描述并解決了即時(shí)翻譯執(zhí)行導(dǎo)致的污點(diǎn)丟失問題。Tripp 等[10]實(shí)現(xiàn)了一種精確且可擴(kuò)展的新型靜態(tài)污點(diǎn)分析方法,將污點(diǎn)分析看作需求驅(qū)動(dòng)問題,只需計(jì)算易受攻擊性信息流,而不是計(jì)算完整的數(shù)據(jù)流解決方案。對易受影響的信息流進(jìn)行需求驅(qū)動(dòng)的跟蹤和計(jì)算,以提升分析的精確度和效率,同時(shí)可以將分析擴(kuò)展到大型代碼庫。
動(dòng)態(tài)污點(diǎn)分析在程序執(zhí)行時(shí)即時(shí)檢測數(shù)據(jù)是否來自外部的非可信輸入,通過實(shí)時(shí)監(jiān)控程序的污點(diǎn)數(shù)據(jù)在系統(tǒng)程序中的傳播來檢測數(shù)據(jù)能否從污點(diǎn)源傳播到污點(diǎn)匯聚點(diǎn)。Amir-Mohammadian 等[11]為Java開發(fā)了一個(gè)動(dòng)態(tài)完整性污染分析模型,該模型以一種in-depth 方法解決了不完善的消毒問題,結(jié)合預(yù)期措施和追溯措施,對消毒結(jié)果是前瞻性認(rèn)可的且回顧性污染的情況進(jìn)行完全消毒,很好地避免了假陽性的出現(xiàn),但模型擴(kuò)展性有待加強(qiáng)和開發(fā)。Cai 等[12]開發(fā)了一個(gè)二進(jìn)制的動(dòng)態(tài)污點(diǎn)分析工具SwordDTA,以期最大化地檢測軟件漏洞。該工具適用于硬件和軟件,通過漏洞建模和污點(diǎn)檢查來發(fā)現(xiàn)漏洞,對緩沖區(qū)溢出、整數(shù)溢出、被零除、UAF(use after free)等漏洞有很好的效果,但對其他類型的漏洞暫時(shí)無能為力,且路徑覆蓋率不高。Karim 等[13]為JavaScript提供了一個(gè)獨(dú)立于平臺的動(dòng)態(tài)污染分析,將污點(diǎn)傳播過程編碼為抽象機(jī)器指令,執(zhí)行這些指令就可得到關(guān)于應(yīng)用的污染流報(bào)告,但是沒有對隱式流進(jìn)行跟蹤,且污點(diǎn)傳播策略不夠靈活,不支持用戶自定義操作。
由于動(dòng)態(tài)數(shù)據(jù)的不可判定性以及源代碼語義的描述能力有限,靜態(tài)分析結(jié)果的精確度有限。而動(dòng)態(tài)分析只能觀測到程序的有限次執(zhí)行,有效性依賴于測試集的完備性。研究人員進(jìn)而提出了結(jié)合靜態(tài)分析與動(dòng)態(tài)分析的混合分析方法,文獻(xiàn)[3]提出了一種編譯級的軟件脆弱性檢測和預(yù)防解決方案,首先使用靜態(tài)分析排除不可執(zhí)行路徑,然后在問題點(diǎn)進(jìn)行基于插樁的數(shù)據(jù)驗(yàn)證。Balzarotti 等[14]基于靜態(tài)分析對應(yīng)用程序沿著執(zhí)行路徑修改輸入的行為進(jìn)行建模,基于動(dòng)態(tài)分析,重構(gòu)應(yīng)用程序用于修改輸入的代碼,然后執(zhí)行代碼并使用大量惡意輸入值來識別消毒過程中可利用的缺陷,以此來分析消毒過程的正確性。
為了降低靜態(tài)污點(diǎn)分析假陽性率高的問題,提高靜態(tài)分析的精確度,本文提出了一種靜態(tài)分析與動(dòng)態(tài)驗(yàn)證相結(jié)合的污點(diǎn)分析方法。首先靜態(tài)提取所有的Source 和Sink 語句,并且對Source 集合進(jìn)行預(yù)處理,剔除不是外部輸入的Source。然后,應(yīng)用基于同方法、Request 請求、Session 會(huì)話和方法調(diào)用的匹配規(guī)則,結(jié)合靜態(tài)污點(diǎn)分析和活躍變量分析,剔除不可能存在污點(diǎn)傳播路徑的Source 和Sink 對。動(dòng)態(tài)驗(yàn)證首先對程序進(jìn)行插樁,然后應(yīng)用自動(dòng)化測試工具自動(dòng)生成測試路徑,在程序執(zhí)行時(shí)結(jié)合動(dòng)態(tài)污點(diǎn)傳播分析對Source 和Sink 之間的潛在污點(diǎn)傳播路徑進(jìn)行驗(yàn)證。如果漏洞真實(shí)存在,則對相應(yīng)的污點(diǎn)傳播路徑進(jìn)行消毒處理,檢測是否正確清除SQLIA 漏洞。
本文的貢獻(xiàn)包括以下兩點(diǎn):
(1)提出一種高效的面向Java Web 程序的SQL注入漏洞靜態(tài)檢測方法,基于匹配規(guī)則裁剪無效Source和Sink 對,然后綜合污點(diǎn)分析和活躍變量分析裁剪不可能存在污點(diǎn)傳播漏洞的Source和Sink 對;
(2)實(shí)現(xiàn)了靜態(tài)檢測原型工具TASAT(taint analysis static analysis tool),并結(jié)合程序插樁和Selenium 自動(dòng)測試工具對靜態(tài)檢測結(jié)果進(jìn)行動(dòng)態(tài)驗(yàn)證,針對真實(shí)Web 程序的實(shí)驗(yàn)結(jié)果驗(yàn)證了方法的有效性。
靜態(tài)分析階段的總體框架如圖1 所示。首先根據(jù)預(yù)定義策略提取所有可能的Source 和Sink 語句,然后對每條Source 語句進(jìn)行分析,排除不是讀取外部輸入的Source,然后根據(jù)自定義匹配規(guī)則排除不可能發(fā)生SQLIA 的Source 和Sink 對,最后應(yīng)用靜態(tài)污點(diǎn)分析并結(jié)合活躍變量分析對剩余的Source 和Sink對進(jìn)行反向分析,進(jìn)一步排除不存在污點(diǎn)傳播路徑的Source 和Sink 對。原型系統(tǒng)TASAT 實(shí)現(xiàn)了靜態(tài)分析階段的預(yù)處理Source、過濾無關(guān)Source 和Sink、活躍變量分析這三個(gè)方法組件。
Fig.1 Overall framework of static analysis圖1 靜態(tài)分析的總體框架
1.1.1 預(yù)處理Source集合
根據(jù)Source 方法讀取的參數(shù)是否對應(yīng)實(shí)際的外部輸入,將Source 語句分為原生Source(參數(shù)對應(yīng)外部輸入)和非原生Source(參數(shù)不對應(yīng)外部輸入)。
原生Source 的判斷方法包括:基于Servlet 映射尋找來源action、基于action 確定來源Jsp 及對應(yīng)表單、基于表單內(nèi)容確定對應(yīng)來源,如果來源為用戶外部輸入就可以判斷Source為原生Source。
把所有原生Source 放入原生Source 集合,對于非原生Source,通過匹配方法、匹配Request 和匹配Session 尋找對應(yīng)的原生Source,然后建立與原生Source的映射關(guān)系并加入非原生Source集合。
預(yù)處理Source 方法能夠捕捉所有的原生Source以及和原生Source 形成映射關(guān)系的非原生Source,目前沒有處理來自其他源(如Cookie)的非原生Source。
圖2 的代碼片段示例1 展示了原生Source 的判斷過程,通過HandleLogin.java 的兩條Source 語句獲取Jsp 頁面?zhèn)鬟f的值,第1 句獲取參數(shù)“l(fā)oginname”的對應(yīng)值,同時(shí)賦值給loginname 變量,第2 句為獲取參數(shù)“password”的對應(yīng)值,賦值給password 變量。
首先基于Servlet 尋找來源action,將Servlet Information 的Servletclass 字段和Source 所在Java 類進(jìn)行匹配,得知HandleLogin 類與Servlet 類名“myservlet.control.HandleLogin”相匹配,推導(dǎo)出Web 程序通過名為“l(fā)ogin”的action 跳轉(zhuǎn)到HandleLogin.java。
然后基于action 尋找來源Jsp,在Jsp_action 中匹配actions 字段包含“l(fā)ogin”的記錄,得到相應(yīng)jsp_class字段值為org.apache.jsp.login_jsp(即login.jsp 編譯后的class),推導(dǎo)出HandleLogin.java 中Source 的參數(shù)來自login.jsp。
最后搜索login_jsp 類的jspSevice 方法,尋找與login.jsp 中action 為“l(fā)ogin”的表單內(nèi)容對應(yīng)的語句,發(fā)現(xiàn)名為“l(fā)oginname”和“password”的input 語句并且相應(yīng)value 值為隱含表示,說明找到Source 對應(yīng)的原始外部輸入,因此判斷HandleLogin 類的兩條Source都是原生Source。
示例2 展示了一個(gè)非原生Source 找到對應(yīng)原生Source 的過程。首先按照與示例1 相同的方式找到Source 方法的參數(shù)對應(yīng)的輸入,位于lookShopCar.jsp中action 為“deletegood”的表單中,對應(yīng)名為“delete”的input 語句,value 為“car.getNumber()”,表明Source為非原生Source。
Fig.2 Example of Source pre-process圖2 Source預(yù)處理示例
接著搜索方法內(nèi)其他Source 語句,讀取Request對象屬性語句和讀取Session 對象屬性語句,尋找可能的原生Source。示例中Session_get 集合為預(yù)先遍歷程序得到的讀取Session 對象屬性的語句集合,其中l(wèi)ookShopCar.jsp 中的讀取Session 對象屬性“l(fā)ogin”語句根據(jù)Session 對象屬性匹配關(guān)系,將變量login 與HandleLogin.java 中的login 對象相關(guān)聯(lián),進(jìn)一步在Handle-Login.java 中找到兩個(gè)Source,第4 和第5 行都是原生Source,因此建立input 語句與這兩個(gè)原生Source的映射關(guān)系。
1.1.2 過濾無關(guān)Source和Sink
如果直接簡單地對每對Source 和Sink 進(jìn)行數(shù)據(jù)流分析,會(huì)浪費(fèi)大量計(jì)算資源,因?yàn)樵S多Source 和Sink 之間不存在任何的可執(zhí)行路徑。
根據(jù)Web 應(yīng)用中Source 和Sink 所處位置和跨文件傳播的特點(diǎn),本文提出了一種多重匹配的預(yù)處理方法,在進(jìn)行復(fù)雜數(shù)據(jù)流分析之前首先排除不可能存在可執(zhí)行路徑的Source 和Sink 對,從而減少了無謂的資源消耗。
多重匹配預(yù)處理方法包括:(1)相同方法匹配,判斷Source 和Sink 是否位于相同文件的相同方法;(2)相同的Request 信息匹配,判斷Source 和Sink 是否可以根據(jù)相同請求信息的Get 方法返回值和Set 方法參數(shù)匹配;(3)Session 信息匹配,判斷Source 和Sink 是否可以通過Session 對象的屬性匹配;(4)方法參數(shù)匹配,判斷Source 是否作為方法調(diào)用語句的參數(shù)傳遞給Sink 所在方法。目前,匹配方法可以支持Request 和Session 對象屬性的常量傳播。
另外,針對匹配成功的Source 和Sink 對,如果Source 不是原生Source,那么需要將該Source映射的原生Source與Sink 進(jìn)行匹配。
Fig.3 Example of multiple matching圖3 多重匹配示例
圖3 的第1~2 行給出了相同方法匹配的例子,Source 方法getParameter 和Sink 方法executeQuery 都位于login.jsp 文件的_jsp-Service 方法中。第3~6 行給出相同Request 信息匹配的例子,Source(第3 行)在Search-Cosmetic.java 中,而Sink(第6 行)在Search-Cosmetic.jsp 中,根據(jù)req 對象的getRequest-Dispatcher 方法調(diào)用的參數(shù)信息,Source 可以與SearchCosmetic.jps文件中的Sink進(jìn)行關(guān)聯(lián)匹配。第7~12 行給出Session 信息匹配的例子,Session信息匹配支持鍵值對中的常量傳播,將變量lg 替換為對應(yīng)的常量字符串“l(fā)ogin”,根據(jù)session 對象信息的“l(fā)ogin”屬性,可以將HandleLogin.java 中的Source(第7 行)和GenerateOrder.java 中的Sink(第12 行)進(jìn)行匹配。第13~15 行給出方法參數(shù)匹配的例子,Source 變量password 作為DaAccessMethods 類的getUserRole 方法調(diào)用的參數(shù),而Sink 位于getUser-Role方法中,因此匹配。
1.1.3 污點(diǎn)傳播規(guī)則
污點(diǎn)傳播規(guī)則是后續(xù)活躍變量分析和動(dòng)態(tài)驗(yàn)證的基礎(chǔ),在活躍變量分析中需要結(jié)合污點(diǎn)傳播規(guī)則生成活躍的污點(diǎn)變量。
給出形式化描述污點(diǎn)變量信息使用的符號,status表示變量的污點(diǎn)狀態(tài)取值集合,包括污染(tainted)和可信(trusted),locs 表示在語句中污點(diǎn)變量可能出現(xiàn)的位置集合:等式左值Lvalue、等式右值Rvalue、調(diào)用方法對象base 和方法參數(shù)arg_i,i 表示參數(shù)的下標(biāo)順序。vars 表示程序中出現(xiàn)的變量及訪問路徑集合,如a、a.f、a[i]、class.f 等。taint_info 記錄各個(gè)位置上的變量是否被污染,taint_set 記錄每條語句的所有污點(diǎn)變量的位置集合。Г函數(shù)跟蹤記錄每個(gè)變量和當(dāng)前語句不同位置的污點(diǎn)狀態(tài),操作符∪計(jì)算兩個(gè)污點(diǎn)狀態(tài)值的并,兩個(gè)相同值的并值不變,而trusted∪tainted=tainted。
污點(diǎn)狀態(tài)信息使用的符號:
定義1Unit 使用了變量v,即use(Unit,v)成立當(dāng)且僅當(dāng)以下任意條件成立。
(1)Unit是AssignStmt且Rvalue不是InvokeExpr,v出現(xiàn)在Unit的Rvalue中。
(2)Unit 是AssignStmt 且Rvalue 是InvokeExpr:①Rvalue ∈InterfaceInvoke,v出現(xiàn)在Unit 的arg_i;②Rvalue ∈SpecialInvoke,v出現(xiàn)在Unit 的arg_i;③Rvalue ∈VirtualInvoke,v出現(xiàn)在Unit 中的base 或arg_i;④Rvalue ∈StaticInvoke,v出現(xiàn)在Unit的base或arg_i。
(3)Unit 是InvokeStmt,v出現(xiàn)在Unit 的base 或arg_i。
定義2Unit 定義了變量d,def(Unit,d)成立當(dāng)且僅當(dāng)以下任意條件成立。(1)Unit 是AssignStmt,d出現(xiàn)在Unit 的Lvalue;(2)Unit 是InvokeStmt,d沒出現(xiàn)在Unit中且d是base對象的某個(gè)屬性。
使用符號Use 存儲(chǔ)所有use(Unit,v)為真的變量v,表示Unit 使用的變量集合,符號Def 存儲(chǔ)def(Unit,d)為真的變量d,表示Unit 定義的變量集合。下面是一些在污傳播規(guī)則中使用的變量。
(1)system_method:系統(tǒng)類方法集合。
(2)custom_method:自定義方法集合。
(3)method:被調(diào)用方法。
(4)taint_custom(method):處理自定義方法的方法內(nèi)污點(diǎn)傳播,如果method 有返回值,那么返回該值的污點(diǎn)狀態(tài)。
(5)xX:對象的屬性。
(6)SetXX:屬性xX 的Set方法集合。
(7)GetXX:屬性xX 的Get方法集合。
污點(diǎn)傳播規(guī)則分為單文件傳播和跨文件傳播。單文件傳播包括兩類指令:
(1)賦值語句
污點(diǎn)信息由Rvalue 傳播到Lvalue,即Г(Lvalue)=Г(Rvalue),Lvalue 和Rvalue 都包括多種類型,具體如下。
①a=C,Lvalue 是變量a,Rvalue 是常量C。常量賦值可以消除左值的污點(diǎn)狀態(tài),將污點(diǎn)變量驗(yàn)證為可信變量,對應(yīng)式(1)。
②a=b,Rvalue 為變量b;a=bbinopc,Rvalue為二元表達(dá)式;a=unopb,Rvalue 為一元表達(dá)式;a=b.f,Rvalue 為對象實(shí)例的成員變量;a=class.f,Rvalue 為全局靜態(tài)變量;a.f=b,Lvalue 為對象實(shí)例的成員變量;class.f=b,Lvalue 為全局靜態(tài)變量。在上述任意情形,Rvalue的污點(diǎn)狀態(tài)為所有當(dāng)前Unit使用變量的污點(diǎn)狀態(tài)值的并值,對應(yīng)式(2)。
③a=b[i],Rvalue 為數(shù)組元素且索引為變量;a=b[constant],Rvalue 為數(shù)組元素且索引為常量。當(dāng)右值為數(shù)組元素時(shí),污點(diǎn)傳播采用保守策略,Rvalue的污點(diǎn)狀態(tài)值為所有在程序中出現(xiàn)的數(shù)組元素的污點(diǎn)狀態(tài)值的并值,對應(yīng)式(3)。
④b[i]=a,Lvalue 為數(shù)組元素且索引為變量;b[constant]=a,Lvalue 為數(shù)組元素且索引為常量。當(dāng)Lvalue為數(shù)組元素時(shí),只需要把Rvalue的污點(diǎn)狀態(tài)值賦予Lvalue 即可,不需要修改其他數(shù)組元素。如果該元素是tainted,那么所有數(shù)組元素的并值一定也是tainted,如果該元素被驗(yàn)證為trusted,那么不影響其他元素的污點(diǎn)狀態(tài),從而保證污點(diǎn)傳播的可靠性,對應(yīng)式(4)。
⑤a=invoke_expr b.f(arg_1,arg_2...),將方法調(diào)用語句分為三類,一是對象屬性的Get方法即GetXX,二是庫方法system_method,三是自定義方法custom_method。GetXX 方法沒有參數(shù),直接返回屬性b.xX的值,因此Lvalue 的污點(diǎn)狀態(tài)等于b.xX 的污點(diǎn)狀態(tài)值。如果system_method 的實(shí)例對象b 或任意參數(shù)的污點(diǎn)狀態(tài)為tainted,Lvalue 的污點(diǎn)狀態(tài)保守地設(shè)置為tainted。對于自定義方法,根據(jù)方法內(nèi)污點(diǎn)傳播taint_custom 計(jì)算返回值的污點(diǎn)狀態(tài),對應(yīng)式(5)~式(7)。
⑥a=new_expr(arg_1,arg_2...),Rvalue 為創(chuàng)建對象表達(dá)式,如果表達(dá)式中的任意參數(shù)的污點(diǎn)狀態(tài)為tainted,那么Lvalue 為tainted,否則為trusted,對應(yīng)式(8)。
(2)方法調(diào)用語句
形如invoke b.f(arg_1,arg_2...),分為三類,一是對象屬性的Set 方法即SetXX,二是庫方法system_method,三是自定義方法custom_method。對于Set方法,修改對象b 的屬性b.xX 為相應(yīng)參數(shù)的污點(diǎn)狀態(tài)。對于沒有返回值的system_method,沒有給出任何定義,忽略了庫方法可能改變?nèi)肿兞康奈埸c(diǎn)狀態(tài),可能改變對象b 和參數(shù)對象的屬性的污點(diǎn)狀態(tài),忽略了對象b 可能受到參數(shù)的污點(diǎn)傳播而改變污點(diǎn)狀態(tài)。對于custom_method,根據(jù)方法內(nèi)污點(diǎn)傳播taint_custom 改變其他變量的污點(diǎn)狀態(tài),對應(yīng)式(9)。
對于傳播方法taint_custom,靜態(tài)分析和動(dòng)態(tài)分析有所不同。靜態(tài)分析采用標(biāo)準(zhǔn)的方法內(nèi)數(shù)據(jù)流分析,根據(jù)賦值語句和方法調(diào)用語句定義的相應(yīng)傳播規(guī)則在控制流圖中傳播污點(diǎn)信息,遇見分支語句時(shí)進(jìn)行變量的污點(diǎn)狀態(tài)并值計(jì)算,如果存在環(huán)(即循環(huán)),則迭代分析至污點(diǎn)狀態(tài)集合為最小不動(dòng)點(diǎn)。動(dòng)態(tài)分析伴隨著指令的順序執(zhí)行,根據(jù)每條指令的污點(diǎn)傳播規(guī)則動(dòng)態(tài)修改相應(yīng)變量的污點(diǎn)狀態(tài)即可。
跨文件污點(diǎn)傳播發(fā)生在待分析語句是Request和Session 對象的屬性讀寫語句時(shí),如getParameter 和setAttribute 等,根據(jù)屬性的鍵值對信息傳播污點(diǎn)。在單個(gè)文件污點(diǎn)傳播過程結(jié)束后,根據(jù)跨文件傳播規(guī)則獲取目標(biāo)文件的污點(diǎn)信息集合,然后對目標(biāo)文件開啟新一輪污點(diǎn)傳播分析。跨文件污點(diǎn)傳播在目標(biāo)文件中僅保留和傳播被讀寫的屬性所對應(yīng)的污點(diǎn)狀態(tài)值,并以此作為單文件傳播的初始taintset,開始目標(biāo)文件的污點(diǎn)傳播分析。
1.1.4 活躍變量分析
如果在每條從Source 到Sink 的執(zhí)行路徑上都存在一個(gè)程序點(diǎn)位置,在該位置上所有的活躍變量都是可信變量,或者在Source 點(diǎn)的活躍變量集合不包含Source 方法返回值變量,那么每條路徑都不可能有污點(diǎn)信息傳播到Sink,即Source 與Sink 之間不可能存在真實(shí)污點(diǎn)傳播路徑。
活躍變量分析與預(yù)定義污點(diǎn)傳播規(guī)則相結(jié)合,僅記錄可能是污點(diǎn)的活躍變量,即在標(biāo)準(zhǔn)活躍變量分析基礎(chǔ)上,按照1.1.3 小節(jié)定義的污點(diǎn)傳播語義,計(jì)算每條指令的活躍污點(diǎn)變量。如果污點(diǎn)變量通過方法調(diào)用參數(shù)傳遞,根據(jù)預(yù)定義的實(shí)參和形參的映射關(guān)系計(jì)算新的活躍變量;如果通過Request方法傳遞,根據(jù)Request 對象的鍵值對計(jì)算新的活躍變量;如果通過Session 對象的屬性傳遞,根據(jù)Session 對象的屬性和活躍變量的匹配關(guān)系計(jì)算新的活躍變量。
圖4 給出了兩個(gè)代碼片段示例,說明活躍變量分析過程。在第5 行的Sink 語句位置,方法參數(shù)query是活躍污點(diǎn)變量,在第4 行活躍污點(diǎn)變量是query 和name,進(jìn)而得知在第3 行只有name 是活躍污點(diǎn)變量,然后可以計(jì)算出在第2 行,s 是活躍污點(diǎn)變量,最后s是Source 方法的返回值,說明示例1 存在潛在的污點(diǎn)傳播路徑。
Fig.4 Example of live variable analysis圖4 活躍變量分析示例
示例2 給出了3 種不存在污點(diǎn)傳播執(zhí)行路徑的Source 和Sink 對,示例污點(diǎn)傳播過程中的污點(diǎn)驗(yàn)證、Session 屬性匹配和Request屬性匹配。在第9 行Sink語句,方法參數(shù)s 是活躍污點(diǎn)變量,在第8 行,根據(jù)屬性方法getLoginname 的污點(diǎn)傳播語義,活躍污點(diǎn)變量變?yōu)閘g.loginname,在第7 行,sanitize 方法對login對象進(jìn)行了消毒處理后賦予lg 變量,此時(shí)不存在任何活躍污點(diǎn)變量,活躍變量分析中止,表明第1行Source 和第9 行的Sink 之間不可能存在污點(diǎn)傳播路徑。
在第14 行Sink 語句,query 是活躍污點(diǎn)變量,在第13 和第12 行,活躍污點(diǎn)變量分別是{query,name}和name。在第11 行,方法調(diào)用login.getLoginname 獲取login 對象的loginname 屬性,因此活躍污點(diǎn)變量是login.loginname,而不是login 對象。在第10 行,根據(jù)Session 對象屬性名“l(fā)ogin”匹配,污點(diǎn)變量追溯至文件1 中的login 對象的屬性login.loginname(第4 行)。在第4 行,活躍污點(diǎn)變量還是login.loginname,第2 行設(shè)置login 對象的“password”屬性為password 變量,活躍污點(diǎn)變量依然是login.loginname,與第1 行的Source 返回值變量password 無關(guān),表明password 在Source 位置不活躍,即第1 行的Source 無法將污點(diǎn)變量傳播到第14 行的Sink。
在第17 行的Sink 語句,login.password 為活躍污點(diǎn)變量,在第16 行對login 對象執(zhí)行污點(diǎn)驗(yàn)證,此時(shí)活躍污點(diǎn)變量為空集,活躍變量分析中止,login 對象在第15 行根據(jù)Request 屬性“l(fā)ogin”匹配文件1 的login 對象(第5 行),表明第1 行的Source 與第17 行的Sink 不存在污點(diǎn)傳播路徑。
靜態(tài)分析主要完成原生Source 的正確判斷和轉(zhuǎn)換處理,發(fā)現(xiàn)存在潛在SQL 注入漏洞的Source 和Sink 對,通過自定義的污點(diǎn)傳播語義規(guī)則并使用活躍變量分析方法對它們進(jìn)行反向數(shù)據(jù)流分析,排除不可能存在SQ 注入漏洞的Source 和Sink 對。預(yù)處理過程有效地過濾無關(guān)Source 和Sink 對,極大減少了待分析的Source 和Sink 對數(shù)量,進(jìn)而提高了分析的性能。通過自定義一套完整的指令級污點(diǎn)傳播語義規(guī)則,并結(jié)合經(jīng)典活躍變量分析方法,進(jìn)一步排除不可能存在SQLIA 漏洞的Source 和Sink 對,提高了分析的精確度。
1.2.1 流程概述
動(dòng)態(tài)驗(yàn)證基于污點(diǎn)傳播規(guī)則對Jimple 中間代碼進(jìn)行插樁,執(zhí)行插樁后程序獲得污點(diǎn)傳播的指令序列Trace,基于Trace 驗(yàn)證靜態(tài)分析結(jié)果的Source 和Sink 之間是否存在真實(shí)污點(diǎn)傳播路徑。驗(yàn)證過程如圖5 所示,首先將Java 和Jsp 文件編譯為class,然后應(yīng)用Soot 框架反編譯為Jimple 中間代碼,并基于污點(diǎn)傳播規(guī)則對相應(yīng)語句進(jìn)行插樁,插樁代碼用于動(dòng)態(tài)跟蹤污點(diǎn)變量的傳播,接著將插樁后的Jimple 代碼重新編譯為class,最后打包為jar 文件并進(jìn)行網(wǎng)站部署。應(yīng)用自動(dòng)化測試工具對網(wǎng)站進(jìn)行測試,根據(jù)插樁代碼動(dòng)態(tài)跟蹤的污點(diǎn)傳播信息打印相應(yīng)的代碼片斷Trace,然后將每條Trace 與靜態(tài)分析結(jié)果的Source和Sink 進(jìn)行匹配,判斷是否存在真實(shí)污點(diǎn)傳播路徑。如果存在,嘗試在源代碼中增加驗(yàn)證方法,并重新執(zhí)行靜態(tài)分析,檢測漏洞是否已經(jīng)被安全消除。
1.2.2 插樁處理
動(dòng)態(tài)插樁定義一個(gè)輔助類用于實(shí)現(xiàn)污點(diǎn)傳播,其中設(shè)置不同的靜態(tài)方法用于實(shí)現(xiàn)不同語句的污點(diǎn)傳播語義。靜態(tài)遍歷程序的每條語句,根據(jù)污點(diǎn)傳播規(guī)則判斷其是否需要插樁,是則根據(jù)其類型進(jìn)行相應(yīng)插樁,即插入一條調(diào)用輔助類的相應(yīng)方法的語句并傳入相應(yīng)參數(shù)。不用插樁的語句類型包括definitionstmt、ifstmt、return 和goto 等。
插樁根據(jù)不同類型的語句(Unit)調(diào)用不同類型的輔助方法,傳入不同的方法參數(shù),同時(shí)不影響原來代碼的業(yè)務(wù)邏輯,對AssignStmt 和InvokeStm 類型語句的插樁方式如表1所示。對于AssignStmt類型分為Source、Sink、右值為InvokeExpr 和other 等情形進(jìn)行處理,對于InvokeStmt 類型分為request_set、session_set、invokeexpr 等情形進(jìn)行處理。傳入?yún)?shù)Unit 指語句自身,left 指語句左值,base 指方法調(diào)用對象,arg 表示方法參數(shù),itsClass 表示語句所屬類,key 指request參數(shù)或session 屬性名,value 表示key 對應(yīng)的值,method 表示被調(diào)用方法。
Fig.5 Flowchart of dynamic verification圖5 動(dòng)態(tài)驗(yàn)證流程
Table 1 Statement instrumentation parameter example表1 語句插樁參數(shù)示例
對于AssignStmt 中的Source、Sink 和other 情形,left和arg參數(shù)用于跟蹤污點(diǎn)變量傳播,Unit和itsClass參數(shù)用于記錄產(chǎn)生污點(diǎn)傳播的語句。對于InvokeExpr、base 和method 參數(shù),用于處理方法調(diào)用的污點(diǎn)傳播。對于InvokeStmt 中的request_set 和session_set,key 和value 參數(shù)用于處理匹配Request 和Session 對象的鍵值對進(jìn)行污點(diǎn)傳播。
圖6 展示了一段Java 代碼轉(zhuǎn)換為對應(yīng)的Jimple代碼以及根據(jù)Jimple 代碼的特性進(jìn)行插樁操作后的效果展示圖??偣灿? 段代碼片段:第1 段為Java 代碼。第2 段為Java 代碼對應(yīng)的Jimple 代碼,在該段Jimple 代碼中,第1 句為接口調(diào)用方法,調(diào)用Http-ServletRequest 接口對象的getParameter 方法來獲得對應(yīng)參數(shù)的值,第2 句為普通調(diào)用方法,調(diào)用String 對象的trim 方法來去除String 對象的字符串的頭尾空白符,第3、4 句與第1、2 句類似。第3 段為插樁后得到的Jimple 代碼,第2 和第4 句為插樁代碼,在第2 段代碼的每條語句后面插入一條調(diào)用DynamicUtil輔助類的靜態(tài)方法的語句。第1 句是AssignStmt 類型的Source語句,插入靜態(tài)findAndjudgeSource方法,傳入unit、left、arg 和itsClass 參數(shù)進(jìn)行污點(diǎn)傳播。第3 句為AssignStmt 類型的InvokeExpr 語句,插入taintanalysis_assignstmt_invokeexpr 方法,并傳入unit、left、base、method、arg 和itsClass等參數(shù)進(jìn)行污點(diǎn)傳播。
1.2.3 驗(yàn)證
Fig.6 Example of instrumentation result圖6 插樁效果展示圖
驗(yàn)證過程首先對原始Web 程序進(jìn)行人工測試,盡量覆蓋程序中所有可能執(zhí)行數(shù)據(jù)庫操作的路徑,同時(shí)應(yīng)用Selenium 工具錄制瀏覽器與Web 程序的交互過程,錄制的全部路徑作為自動(dòng)化測試模塊對插樁后的Web 程序進(jìn)行自動(dòng)測試。
圖7 給出了一條測試路徑示例,從用戶登錄開始,包括瀏覽化妝品、加入購物車、查看購物車和用戶退出。主要記錄交互過程,即定位到特定元素并執(zhí)行對應(yīng)操作,如點(diǎn)擊元素click 和元素賦值sendKeys等。
Fig.7 Example of test path圖7 測試路徑示例
驗(yàn)證過程通過重放執(zhí)行錄制的測試路徑實(shí)現(xiàn),在程序執(zhí)行的同時(shí),插入的代碼執(zhí)行動(dòng)態(tài)污點(diǎn)分析并打印與污點(diǎn)傳播相關(guān)的指令序列即Trace。然后,驗(yàn)證過程搜索每條Trace 的Source 和Sink 集合,判定是否與靜態(tài)分析結(jié)果的Source 和Sink 對匹配,是則表明找到了對應(yīng)Source 和Sink 的一條真實(shí)污點(diǎn)傳播路徑,同時(shí)驗(yàn)證了靜態(tài)分析結(jié)果的正確性。
圖8 是一條Trace 示例。第1 行是Source 語句,獲取login.jsp 中“password”對應(yīng)的外部輸入并賦值給password 變量,password 變量成為污點(diǎn)變量。第2行,password 作為查詢語句的一部分,在查詢操作完成后將污點(diǎn)信息傳播給ResultSet類型的變量rs,污點(diǎn)信息在第3 行從rs 傳播至userid 變量。第4 行是session 對象的寫屬性語句,將污點(diǎn)變量userid 以鍵值對<“userid”,userid>的形式保存在session 對象中。在第5 行,通過session 對象的讀屬性語句將loing_jsp文件中的污點(diǎn)變量userid 傳播給basket_jsp 文件中的字符串變量userid。第6 行是Sink 語句,污點(diǎn)變量userid 作為查詢語句的一部分,可能造成相關(guān)數(shù)據(jù)表的數(shù)據(jù)泄漏。
Fig.8 Trace example圖8 Trace示例
動(dòng)態(tài)驗(yàn)證使用程序插樁技術(shù),基于自定義的污點(diǎn)傳播規(guī)則對Jimple 中間代碼進(jìn)行插樁,使用自動(dòng)測試工具動(dòng)態(tài)遍歷執(zhí)行插樁后程序的不同路徑,針對程序運(yùn)行后產(chǎn)生的Trace 進(jìn)行跟蹤驗(yàn)證,檢查Source是否能夠成功傳播到相應(yīng)Sink,進(jìn)而判斷漏洞是否真實(shí)存在。
靜態(tài)分析結(jié)果的誤報(bào)率較高,人工驗(yàn)證的效率很低,本文將靜態(tài)分析與動(dòng)態(tài)驗(yàn)證相結(jié)合,實(shí)現(xiàn)了靜態(tài)分析的自動(dòng)化和動(dòng)態(tài)驗(yàn)證過程的半自動(dòng)化,實(shí)驗(yàn)結(jié)果表明本文方法在大幅提高分析效率的同時(shí)保持了很好的準(zhǔn)確率。
實(shí)驗(yàn)環(huán)境為Windows 7 64 位操作系統(tǒng),CPU 為Intel?CoreTMi5-4200M,8 GB 物理內(nèi)存,瀏覽器為加載SeleniumIDE 3.15.1 插件的Firefox。開發(fā)環(huán)境基于JDK 7、Soot-2.5.0 以及Kepler 和Mars 等Eclipse 工具,代碼量為7 600 行,其中原生Source 判斷及轉(zhuǎn)換模塊1 100行,多重匹配模塊600行,污點(diǎn)規(guī)則定義及活躍變量分析模塊代碼量為2 300行,動(dòng)態(tài)插樁模塊1 600行。
實(shí)驗(yàn)整體框架如圖9 所示,靜態(tài)分析檢測潛在SQL 注入漏洞,作為動(dòng)態(tài)驗(yàn)證過程的數(shù)據(jù)集。動(dòng)態(tài)驗(yàn)證通過驗(yàn)證數(shù)據(jù)集定位真實(shí)漏洞,排除誤報(bào),并對漏洞進(jìn)行消毒處理。
Fig.9 Experimental overall framework圖9 實(shí)驗(yàn)整體框架
實(shí)驗(yàn)數(shù)據(jù)集包括6 個(gè)網(wǎng)站,其中3 個(gè)漏洞測試網(wǎng)站為bodgeit、security-shepherd 和webgoat,3 個(gè)開源網(wǎng)站為contineo、cosmetic_shop 和puzzlemall。靜態(tài)分析階段的實(shí)驗(yàn)結(jié)果如表2 所示,經(jīng)過多重匹配(P_decrement rate)后,需要驗(yàn)證的Source 和Sink 對的數(shù)量極大減少,最少減少88.6%,最多減少99.4%,平均減少98.7%。將活躍變量分析與污點(diǎn)傳播相結(jié)合進(jìn)一步消除不可能存在污點(diǎn)傳播路徑的Source 和Sink,最少減少6.2%,最多減少84.4%,平均減少69.6%,表明靜態(tài)分析階段能夠有效排除無關(guān)Source和Sink 對,避免無謂的計(jì)算資源浪費(fèi)。
動(dòng)態(tài)驗(yàn)證的結(jié)果如表3 所示,F(xiàn)inds 表示成功驗(yàn)證的Source和Sink 對數(shù)量。bodgeit項(xiàng)目的15 個(gè)潛在漏洞中有9 個(gè)是Source 和Sink 位于相同方法,其余6個(gè)是經(jīng)過Session 對象跨文件傳遞,動(dòng)態(tài)驗(yàn)證確認(rèn)了其中的12 個(gè)漏洞。contineo 項(xiàng)目的100 個(gè)潛在漏洞有89 個(gè)被成功地驗(yàn)證,都是通過方法調(diào)用進(jìn)行跨文件污點(diǎn)傳播。cosmetic_shop、security_shephred 和puzzlemall 等項(xiàng)目的潛在漏洞全部被成功地驗(yàn)證。webgoat 項(xiàng)目潛在的30 個(gè)漏洞有25 個(gè)被成功驗(yàn)證。所有數(shù)據(jù)集中無法驗(yàn)證的漏洞都是因?yàn)镾elenium 沒有生成相應(yīng)執(zhí)行路徑導(dǎo)致,這是由于動(dòng)態(tài)測試無法做到100%覆蓋測試路徑。
Table 2 Static analysis results表2 靜態(tài)分析實(shí)驗(yàn)結(jié)果
Table 3 Results of dynamic verification表3 動(dòng)態(tài)驗(yàn)證結(jié)果
Table 4 Comparison of time performance表4 時(shí)間性能比較
將分析結(jié)果與經(jīng)典Java EE 程序分析工具ANDROMEDA[10]進(jìn)行對比,結(jié)果如表3 所示。在ANDROMEDA 的數(shù)據(jù)集中,因?yàn)橹怀晒\(yùn)行了contineo 和webgoat 兩個(gè)項(xiàng)目,所以表3 和表4 只針對這兩個(gè)項(xiàng)目比較準(zhǔn)確率和時(shí)間性能。表4 以靜態(tài)分析得到的Source 和Sink 對作為潛在漏洞總數(shù),以動(dòng)態(tài)驗(yàn)證確認(rèn)的Source 和Sink 對作為Findings,得出TP和FP,準(zhǔn)確率P=TP/(TP+FP),表示動(dòng)態(tài)驗(yàn)證通過的漏洞數(shù)量與潛在漏洞數(shù)量的比值,虛警率FPR=FP/(FP+TP),表示動(dòng)態(tài)驗(yàn)證未通過的漏洞數(shù)量與潛在漏洞數(shù)量的比值,T/F=Time/Findings,表示找到一個(gè)Finding 的平均時(shí)間。ANDROMEDA 的有關(guān)數(shù)據(jù)來自文獻(xiàn)[10]。
對于contineo,本文方法和ANDROMEDA 的TP和FP接近,對于webgoat,本文方法的TP和FP都要好于ANDROMEDA,上述結(jié)果表明本文方法的準(zhǔn)確率優(yōu)于ANDROMEDA,因?yàn)楸疚姆椒▽W⒎治鯯QL 注入漏洞,并且將靜態(tài)分析與動(dòng)態(tài)驗(yàn)證相結(jié)合以排除虛警,而ANDROMEDA 是單純的靜態(tài)分析工具,用于分析多種Web 程序漏洞,在保證可靠性的同時(shí)虛警率會(huì)上升。
FN 表示靜態(tài)分析沒有發(fā)現(xiàn)的漏洞數(shù)量,在本文中表示沒有和原生Source 形成映射關(guān)系的非原生Source 數(shù)量。召回率R=TP/(TP+FN),表示驗(yàn)證通過的漏洞數(shù)量與所有實(shí)際漏洞數(shù)量的比值。綜合評價(jià)指標(biāo)F=2PR/(P+R),表示準(zhǔn)確率和召回率的加權(quán)調(diào)和平均,綜合了準(zhǔn)確率和召回率的結(jié)果,有利于對檢測結(jié)果綜合性能的評價(jià)[15]。
ANDROMEDA 和本文方法的綜合情況對比如表5 所示,由于ANDROMEDA 論文中沒有對應(yīng)的召回率數(shù)據(jù),因此將其保守地設(shè)置為1。對于contineo項(xiàng)目,ANDROMEDA 和本文方法的誤報(bào)率和綜合評價(jià)相差無幾,對于webgoat 項(xiàng)目,本文方法的誤報(bào)率顯著低于ANDROMEDA,準(zhǔn)確率和綜合評價(jià)指標(biāo)更好。總體來看,本文方法的誤報(bào)率明顯低于ANDROMEDA,具有更好的準(zhǔn)確率,同時(shí)召回率也維持在一個(gè)較高水平,綜合評價(jià)指標(biāo)也略優(yōu)于ANDROMEDA,說明本文方法具有更好的檢測效果。
時(shí)間性能以平均Time/Finding 進(jìn)行比較,即找到一個(gè)漏洞所用的平均時(shí)間,如圖10 所示。ANDROMEDA 在兩個(gè)項(xiàng)目中總計(jì)找到297 個(gè)漏洞,花費(fèi)848 s,平均為2.85 s,本文方法在6 個(gè)項(xiàng)目中共找到220 個(gè)漏洞,花費(fèi)598 s,平均為2.71 s,兩者的時(shí)間性能相差無幾。
Table 5 Comparison of comprehensive performance表5 綜合性能比較
Fig.10 Average time for a finding圖10 Finding 平均時(shí)間
本文提出了一種面向SQLIA 漏洞的靜態(tài)檢測和動(dòng)態(tài)驗(yàn)證方案。靜態(tài)分析階段首先搜索程序中所有的Source 和Sink,然后對Source 集合進(jìn)行預(yù)處理,區(qū)分原生Source 和非原生Source,同時(shí)定位和映射非原生Source 對應(yīng)的原生Source。接著根據(jù)Request 和Session 對象屬性,采用多重匹配方法,尋找存在潛在污點(diǎn)傳播路徑的Source和Sink,然后應(yīng)用活躍變量分析結(jié)合污點(diǎn)傳播,排除不可能存在污點(diǎn)傳播路徑的Source 和Sink,極大減少了待分析的潛在漏洞數(shù)量。動(dòng)態(tài)驗(yàn)證首先對程序插樁,插入污點(diǎn)傳播跟蹤代碼,然后應(yīng)用seleniumIDE 插件對原始Web 項(xiàng)目產(chǎn)生自動(dòng)化測試路徑,接著對插樁后的Web 項(xiàng)目重放這些測試路徑并根據(jù)生成的Trace 驗(yàn)證潛在漏洞是否真實(shí)。最后嘗試對傳播的污點(diǎn)變量進(jìn)行可信驗(yàn)證,阻斷污點(diǎn)傳播路徑從而修復(fù)漏洞。針對真實(shí)項(xiàng)目的實(shí)驗(yàn)結(jié)果表明了方法的有效性,同時(shí),本文方法與經(jīng)典工具ANDROMEDA 對比,在時(shí)間性能和準(zhǔn)確度上均有一定提高。
方法不足之處主要有:(1)在Source 預(yù)處理和多重匹配時(shí),目前只能處理action 和屬性名為常量字符串的情況,無法處理對于運(yùn)行時(shí)確定的action 和屬性名,解決該問題需要引入字符串分析技術(shù)。(2)庫方法的建模采用保守方式,可能影響污點(diǎn)傳播的精確度,擬應(yīng)用污點(diǎn)摘要定義庫方法的污點(diǎn)傳播語義。(3)動(dòng)態(tài)驗(yàn)證采用人工測試并自動(dòng)重放執(zhí)行路徑,存在路徑覆蓋率低的問題,未來擬引入模糊測試和動(dòng)態(tài)符號執(zhí)行技術(shù)改善路徑覆蓋。