doop靜態(tài)分析框架由希臘雅典大學(xué)plast-lab Yannis Smaragdakis團(tuán)隊(duì)設(shè)計(jì)開(kāi)發(fā),目前看是一款開(kāi)源領(lǐng)域的比較先進(jìn)的程序靜態(tài)分析框架,一些程序靜態(tài)分析論文的理論也有通過(guò)doop的規(guī)則實(shí)現(xiàn)后實(shí)驗(yàn)。
(資料圖片僅供參考)
doop整體架構(gòu)簡(jiǎn)單明了,符合通常靜態(tài)代碼漏洞掃描工具掃描器內(nèi)核的設(shè)計(jì)思路。架構(gòu)上由groovy寫的調(diào)用程序“粘合”在一起,通過(guò)調(diào)用fact-generator和datalog分析器,得出自動(dòng)化的分析結(jié)果。
下面是筆者畫的doop整體架構(gòu)圖,包含doop中一些關(guān)鍵的組件模塊:
2. doop工作流程
doop的fact generator模塊會(huì)對(duì)輸入進(jìn)行解析(例如jar包的解析或者類的resolve從而加載進(jìn)必要的類信息到內(nèi)存中)調(diào)用soot、wala等工具生成jimple IR,在此基礎(chǔ)上生成后續(xù)分析引擎需要的facts文件。而后doop使用LogicBlox(目前doop已不維護(hù))或者Soufflé(開(kāi)源的datalog分析引擎)基于facts文件和既定的datalog分析規(guī)則文件進(jìn)行分析,得到最終的程序分析結(jié)果。doop支持對(duì)java源碼及字節(jié)碼的分析,不過(guò)源碼的jdk版本受限,建議直接使用字節(jié)碼進(jìn)行分析。
doop核心是其實(shí)現(xiàn)的一套datalog分析規(guī)則,其中包含了由粗糙到精細(xì)的context-insensitive、1-call-site-sensitive、1-call-site-sensitive+heap的豐富的靜態(tài)程序分析策略等等等,同時(shí)通過(guò)在addons中添加了額外的對(duì)信息流分析、對(duì)spring等生態(tài)框架、對(duì)java反射特性的支持,十分強(qiáng)大。
以上是對(duì)doop的架構(gòu)和功能的簡(jiǎn)單介紹,jar包信息的解析、規(guī)則的預(yù)處理、編譯執(zhí)行和解釋執(zhí)行、程序的并發(fā)設(shè)計(jì)或者由于大量sootclass加載造成的內(nèi)存溢出問(wèn)題等一些細(xì)節(jié)由于篇幅限制不在此介紹。
二、commons text rce漏洞簡(jiǎn)介先對(duì)該漏洞進(jìn)行簡(jiǎn)單介紹。
Apache Commons Text是一款處理字符串和文本塊的開(kāi)源項(xiàng)目,之前被披露存在CVE-2022-42889遠(yuǎn)程代碼執(zhí)行漏洞,這個(gè)漏洞目前網(wǎng)上的分析文章比較多,在此不做復(fù)述。該漏洞原理上有點(diǎn)類似log4j2,當(dāng)然影響不可相比,其代碼中存在可以造成代碼執(zhí)行的插值器,例如ScriptStringLookup(當(dāng)然這里提到這個(gè)插值器是因?yàn)槲覀兡繕?biāo)就是分析這一條sink污點(diǎn)流),同時(shí)沒(méi)有對(duì)輸入字符串的安全性進(jìn)行驗(yàn)證導(dǎo)致問(wèn)題。
借用網(wǎng)上公開(kāi)的poc觸發(fā)ScriptStringLookup中的代碼執(zhí)行,使用commons text 1.9版本 :
完整的漏洞調(diào)用棧如下:
從調(diào)用??梢钥闯觯ㄟ^(guò)調(diào)用commons text的字符串替換函數(shù),可以調(diào)用到ScriptStringLookup類的lookup方法,從而調(diào)用scriptEngine.eval執(zhí)行代碼??梢钥闯鲈摋l漏洞鏈路較淺,但鏈路關(guān)鍵節(jié)點(diǎn)也涉及了接口抽象類的cast、輸入字符串的詞法分析狀態(tài)機(jī)以及各種字符串的處理函數(shù),作為實(shí)驗(yàn)對(duì)象非常合適。
三、commons text rce污點(diǎn)信息流的doop識(shí)別規(guī)則我們選取上述二中commons text中
org.apache.commons.text.StringSubstitutor replace函數(shù)作為source,ScriptEngine eval函數(shù)作為sink。
doop設(shè)置app only模式去進(jìn)行分析,doop在app only模式下會(huì)將!ApplicationMethod(?signature)加入isOpaqueMethod(?signature),這樣一些分析不會(huì)進(jìn)入jdk的類中,可以大大提高doop的分析效率。依據(jù)萊斯定理,靜態(tài)程序分析難以達(dá)到完全的完備(truth或者perfect),也是盡可能優(yōu)化sound。類似在企業(yè)級(jí)的SAST部署使用也是如此,也需要在掃描精度、掃描速度以及實(shí)際可用性中進(jìn)行取舍或者平衡,所以doop的app only模式下在個(gè)人看來(lái)更接近實(shí)際嵌入到devsecops中的輕量級(jí)靜態(tài)代碼漏洞掃描的應(yīng)用。
3.1 doop的datalog分析規(guī)則簡(jiǎn)單介紹由于涉及doop app only規(guī)則的改造,首先先簡(jiǎn)單介紹doop使用的datalog規(guī)則。
doop目前維護(hù)使用開(kāi)源的Soufflé分析datalog規(guī)則。datalog是聲明式的編程語(yǔ)言,也是prolog語(yǔ)言的非圖靈完備子集,所以本質(zhì)上也是建立在形式邏輯中的一階邏輯上。所以基礎(chǔ)概念也是命題推導(dǎo),在Soufflé的形式上就是表現(xiàn)為關(guān)系(relation)。
如下例子:
很明顯可以看出該例子通過(guò)datalog定義的關(guān)系邏輯實(shí)現(xiàn)相等關(guān)系的自反性、對(duì)稱性和傳遞性,首先定義了equivalence關(guān)系,該關(guān)系可以由rel1和rel2關(guān)系蘊(yùn)涵得到,而equivalence的a需要滿足關(guān)系rel1,b需要滿足關(guān)系rel2。具體語(yǔ)法和高階特性可以通過(guò)souffle-lang.github.io網(wǎng)站進(jìn)行了解。
3.2 doop配置使用簡(jiǎn)單介紹doop可以通過(guò)gradle去編譯使用,需要提前在類unix系統(tǒng)中借助cmake編譯安裝Soufflé,doop的具體安裝使用可以在https://github.com/plast-lab/doop-mirror中了解。
對(duì)doop的命令行使用進(jìn)行簡(jiǎn)單,分析,有幾個(gè)關(guān)鍵的命令參數(shù),-i參數(shù)接受需要分析的文件(例如jar包),-a參數(shù)配置分析策略(例如是選擇context sensitive還是context insensitive),--app-only參數(shù)配置開(kāi)啟doop的app only模式,--information-flow開(kāi)啟doop的信息流分析模式(可以用來(lái)做污點(diǎn)分析),--platform設(shè)置分析需要的jdk平臺(tái),--fact-gen-cores配置生成facts的并發(fā)性。
本文使用的doop命令參數(shù):
-a context-insensitive --app-only --information-flow spring --fact-gen-cores 4 -i docs/commons-text.jar --platform java_8 --stats none
3.3 重新編譯打包c(diǎn)ommons text這是我最初使用doop分析commos text的方法,主要為了盡可能減輕的對(duì)原生規(guī)則的侵入。doop在使用jackee進(jìn)行分析事,分析入口的確定及一些mockobject的構(gòu)建都需要依賴于對(duì)springmvc注解的識(shí)別。
下載commons text的源碼,自定義兩條class和method注解TestctxTaintedClassAnnotation、TestctxTaintedParamAnnotation:
注解實(shí)現(xiàn)為一個(gè)空注解,主要是為了標(biāo)注一下我們的source,將注解打到對(duì)應(yīng)的class類和方法:
重新編譯打包為jar包,得到2中命令參數(shù)-i的commons-text.jar。
3.4 改造doop app only下的規(guī)則doop的污點(diǎn)信息流識(shí)別依賴于指針?lè)治鼋Y(jié)果,同時(shí)也依賴污點(diǎn)轉(zhuǎn)移函數(shù)。doop中已經(jīng)預(yù)置了多條污點(diǎn)轉(zhuǎn)移函數(shù),其中包含了字符串、鏈表、迭代器等基礎(chǔ)類方法。
ParamToBaseTaintTransferMethod(0, "
然而其中沒(méi)有包含String split函數(shù)的污點(diǎn)轉(zhuǎn)移規(guī)則,需要添加上:
BaseToRetTaintTransferMethod("
如上述,doop自有的jackee規(guī)則肯定沒(méi)有包含我們自定義的注解,所以需要在EntryPointClass、Mockobj等關(guān)系定義中添加對(duì)我們自定義的class污點(diǎn)注解的識(shí)別。
EntryPointClass(?type) :- //... Type_Annotation(?type, "org.apache.commons.text.TestctxTaintedClassAnnotation");//...MockObject(?mockObj, ?type) :- //... Type_Annotation(?type, "org.apache.commons.text.TestctxTaintedClassAnnotation");
同時(shí)也需要添加param污點(diǎn)的注解。doop需要通過(guò)這些注解識(shí)別分析入口方法,構(gòu)建污點(diǎn)mockobj,建立初始的指向關(guān)系等。
//...mainAnalysis.VarPointsTo(?hctx, cat(cat(cat(cat(?to, "::: "), ?type), "::: "), "ASSIGN"), ?ctx, ?to) :- FormalParam(?idx, ?meth, ?to), (Param_Annotation(?meth, ?idx, "org.springframework.web.bind.annotation.RequestParam"); Param_Annotation(?meth, ?idx, "org.springframework.web.bind.annotation.RequestBody"); Param_Annotation(?meth, ?idx, "org.apache.commons.text.TestctxTaintedParamAnnotation");
為了確保方法的可達(dá)性,我們還添加了
ImplicitReachable("") :- isMethod("").但后續(xù)看不一定有必要,僅供參考。
通過(guò)注解我們?cè)谝?guī)則中定義了source,接下來(lái)需要定義sink,我們將ScriptEngine的eval方法定義為sink:
LeakingSinkMethodArg("default", 0, method) :- isMethod(method), match("
正如前述,由于是在app only下,doop下通過(guò)OpaqueMethod關(guān)系過(guò)濾了jdk類的識(shí)別,這樣會(huì)導(dǎo)致相應(yīng)的上述預(yù)置的污點(diǎn)轉(zhuǎn)移函數(shù)無(wú)法完成污點(diǎn)轉(zhuǎn)移,所以需要另外定制規(guī)則流去將轉(zhuǎn)移函數(shù)包含進(jìn)數(shù)據(jù)流分析過(guò)程。
于是需要定義
OptTaintedtransMethodInvocationBase關(guān)系。
.decl OptTaintedtransMethodInvocationBase(?invocation:MethodInvocation,?method:Method,?ctx:configuration.Context,?base:Var)OptTaintedtransMethodInvocationBase(?invocation,?tomethod,?ctx,?base) :- ReachableContext(?ctx, ?inmethod),//Reachable(?inmethod), Instruction_Method(?invocation, ?inmethod), ( _VirtualMethodInvocation(?invocation, _, ?tomethod, ?base, _); _SpecialMethodInvocation(?invocation, _, ?tomethod, ?base, _) ).
在此基礎(chǔ)上,為了完成新的污點(diǎn)轉(zhuǎn)移,doop需要根據(jù)以下自定義規(guī)則分析出返回值的類型信息。
.decl MaytaintedInvocationInfo(?invocation:MethodInvocation,?type:Type,?ret:Var)MaytaintedInvocationInfo(?invocation, ?type, ?ret) :- Method_ReturnType(?method, ?type), MethodInvocation_Method(?invocation, ?method), AssignReturnValue(?invocation, ?ret)..decl MaytaintedTypeForReturnValue(?type:Type, ?ret:Var, ?invocation:MethodInvocation)MaytaintedTypeForReturnValue(?type, ?ret, ?invocation) :- MaytaintedInvocationInfo(?invocation, ?type, ?ret), !VarIsCast(?ret).
基于以上的污點(diǎn)轉(zhuǎn)移過(guò)程分析規(guī)則,應(yīng)用到污點(diǎn)變量的轉(zhuǎn)移分析規(guī)則中。
VarIsTaintedFromVar(?type, ?ctx, ?ret, ?ctx, ?base) :- //mainAnalysis.OptTaintedtransMethodInvocationBase(?invocation,?method,?base), mainAnalysis.OptTaintedtransMethodInvocationBase(?invocation,?method,?ctx,?base), MaytaintedTypeForReturnValue(?type, ?ret, ?invocation), BaseToRetTaintTransferMethod(?method). //mainAnalysis.VarPointsTo(_, _, ?ctx, ?base).
同時(shí)也需要重新定義LeakingSinkVariable關(guān)系,因?yàn)槲覀冞@里自定義的sink方法也是Opaque方法,這樣才能識(shí)別到我們的ScriptEngine 的eval方法。
LeakingSinkVariable(?label, ?invocation, ?ctx, ?var) :- LeakingSinkMethodArg(?label, ?index, ?tomethod), mainAnalysis.OptTaintedtransMethodInvocationBase(?invocation,?tomethod,?ctx,?base), //mainAnalysis.VarPointsTo(_, _, ?ctx, ?base),//here problem ActualParam(?index, ?invocation, ?var).
從上面規(guī)則的定義可以看出,改造的流程還是比較清晰的,并且通過(guò)關(guān)系的名字,這些關(guān)系的含義和用途也很容易理解。添加這些自定義規(guī)則到我們的doop分析中運(yùn)行,在結(jié)果中可以看出,doop完成了對(duì)commons text的污點(diǎn)信息流的識(shí)別。
在結(jié)果集中的LeakingTaintedInformation.csv文件中可以找到我們需要捕捉到的souce-sink流。
default default <
LeakingTaintedInformation.csv給出了污點(diǎn)信息。包括污點(diǎn)的標(biāo)簽(這里是默認(rèn)的default,可以自定義),sink方法的調(diào)用信息,該sink方法對(duì)應(yīng)的污點(diǎn)源頭souce信息。
如上圖可以看出,
org.apache.commons.text.lookup.ScriptStringLookup:
java.lang.String lookup(java.lang.String)中調(diào)用到
javax.script.ScriptEngine.eval,并且污點(diǎn)的源頭是
org.apache.commons.text.StringSubstitutor:
java.lang.String replace(java.lang.String)方法的參數(shù)@parameter0。
同時(shí),在結(jié)果集中的AppTaintedVar.csv文件也可以看到具體的應(yīng)用代碼中由于污點(diǎn)傳播過(guò)程中的被污染的變量.以上面commons text 漏洞執(zhí)行方法棧中的
org.apache.commons.text.StringSubstitutor的resolveVariable為例:
可以看出方法中被污染的入?yún)ariableName、buf,還有resolver,以及$stack7等(這是經(jīng)過(guò)soot生成jimple的過(guò)程中SSA pack部分優(yōu)化新增的棧變量)。
基于這兩個(gè)結(jié)果集基本可以看出漏洞的觸發(fā)流程或者說(shuō)污點(diǎn)的傳播過(guò)程(雖然不是特別直觀),如果需要也可以再搭配生成的CallGraphEdge.csv去更方便的進(jìn)行分析。
四、總結(jié)doop直接用來(lái)分析大型項(xiàng)目需要一定的計(jì)算資源,并且無(wú)論是規(guī)則的定制還是分析結(jié)果查看都不是特別直觀,畢竟它的設(shè)計(jì)初衷就是一款分析框架,用在實(shí)際漏掃漏洞挖掘中可能需要進(jìn)一步包裝修改 。但可以看出,doop作為一款優(yōu)秀的開(kāi)源靜態(tài)分析框架,在算法上毋庸置疑是比較先進(jìn)和豐富的,而且基于開(kāi)源的算法規(guī)則,我們可以任意去定制我們需要的分析邏輯。其與codeql在設(shè)計(jì)思路也較為相近,將程序信息提取后生成數(shù)據(jù)庫(kù),開(kāi)放查詢接口,將程序分析轉(zhuǎn)變?yōu)閿?shù)據(jù)關(guān)系的查詢,因此可以擴(kuò)展出更多的用途。
標(biāo)簽: