作者:vivo 互联网安全团队 - Chen Haojie
本文基于笔者对doop动态程序剖析框架源代码和规则学习,并联合对目前破绽公开技术细节的学习,批改加强doop app only模式下的剖析规定后,实现通过doop工具辨认commons text rce破绽(CVE-2022-42889)。内容蕴含三局部,第一局部简略介绍doop剖析框架,第二局部简略介绍commons text破绽的原理和代码调用栈,第三局部重点介绍如何革新doop app only模式下的规定以辨认commons text破绽的污点信息流。
一、doop动态剖析框架简介
1. doop动态剖析框架简介
doop动态剖析框架由希腊雅典大学plast-lab Yannis Smaragdakis团队设计开发,目前看是一款开源畛域的比拟先进的程序动态剖析框架,一些程序动态剖析论文的实践也有通过doop的规定实现后试验。
doop整体架构简单明了,合乎通常动态代码破绽扫描工具扫描器内核的设计思路。架构上由groovy写的调用程序“粘合”在一起,通过调用fact-generator和datalog分析器,得出自动化的剖析后果。
上面是笔者画的doop整体架构图,蕴含doop中一些要害的组件模块:
2. doop工作流程
- doop的fact generator模块会对输出进行解析(例如jar包的解析或者类的resolve从而加载进必要的类信息到内存中)
- 调用soot、wala等工具生成jimple IR,在此基础上生成后续剖析引擎须要的facts文件。而后doop应用LogicBlox(目前doop已不保护)或者Soufflé(开源的datalog剖析引擎)
- 基于facts文件和既定的datalog剖析规定文件进行剖析,失去最终的程序剖析后果。
doop反对对java源码及字节码的剖析,不过源码的jdk版本受限,倡议间接应用字节码进行剖析。
doop外围是其实现的一套datalog剖析规定,其中蕴含了由毛糙到精密的context-insensitive、1-call-site-sensitive、1-call-site-sensitive+heap的丰盛的动态程序剖析策略等等等,同时通过在addons中增加了额定的对信息流剖析、对spring等生态框架、对java反射个性的反对,非常弱小。
以上是对doop的架构和性能的简略介绍,jar包信息的解析、规定的预处理、编译执行和解释执行、程序的并发设计或者因为大量sootclass加载造成的内存溢出问题等一些细节因为篇幅限度不在此介绍。
二、commons text rce破绽简介
先对该破绽进行简略介绍。
Apache Commons Text是一款解决字符串和文本块的开源我的项目,之前被披露存在CVE-2022-42889近程代码执行破绽,这个破绽目前网上的剖析文章比拟多,在此不做复述。该破绽原理上有点相似log4j2,当然影响不可相比,其代码中存在能够造成代码执行的插值器,例如ScriptStringLookup(当然这里提到这个插值器是因为咱们指标就是剖析这一条sink污点流),同时没有对输出字符串的安全性进行验证导致问题。
借用网上公开的poc触发ScriptStringLookup中的代码执行,应用commons text 1.9版本 :
残缺的破绽调用栈如下:
从调用栈能够看出,通过调用commons text的字符串替换函数,能够调用到ScriptStringLookup类的lookup办法,从而调用scriptEngine.eval执行代码。能够看出该条破绽链路较浅,但链路要害节点也波及了接口抽象类的cast、输出字符串的词法剖析状态机以及各种字符串的处理函数,作为试验对象十分适合。
三、commons text rce污点信息流的doop辨认规定
咱们选取上述二中commons text中org.apache.commons.text.StringSubstitutor replace函数作为source,ScriptEngine eval函数作为sink。
doop设置app only模式去进行剖析,doop在app only模式下会将!ApplicationMethod(?signature)退出isOpaqueMethod(?signature),这样一些剖析不会进入jdk的类中,能够大大提高doop的剖析效率。根据莱斯定理,动态程序剖析难以达到齐全的齐备(truth或者perfect),也是尽可能优化sound。相似在企业级的SAST部署应用也是如此,也须要在扫描精度、扫描速度以及理论可用性中进行取舍或者均衡,所以doop的app only模式下在集体看来更靠近理论嵌入到devsecops中的轻量级动态代码破绽扫描的利用。
3.1 doop的datalog剖析规定简略介绍
因为波及doop app only规定的革新,首先先简略介绍doop应用的datalog规定。
doop目前保护应用开源的Soufflé剖析datalog规定。datalog是申明式的编程语言,也是prolog语言的非图灵齐备子集,所以实质上也是建设在形式逻辑中的一阶逻辑上。所以根底概念也是命题推导,在Soufflé的模式上就是体现为关系(relation)。
如下例子:
很显著能够看出该例子通过datalog定义的关系逻辑实现相等关系的自反性、对称性和传递性,首先定义了equivalence关系,该关系能够由rel1和rel2关系蕴涵失去,而equivalence的a须要满足关系rel1,b须要满足关系rel2。具体语法和高阶个性能够通过souffle-lang.github.io网站进行理解。
3.2 doop配置应用简略介绍
doop能够通过gradle去编译应用,须要提前在类unix零碎中借助cmake编译装置Soufflé,doop的具体装置应用能够在https://github.com/plast-lab/doop-mirror中理解。
对doop的命令行应用进行简略,剖析,有几个要害的命令参数,-i参数承受须要剖析的文件(例如jar包),-a参数配置剖析策略(例如是抉择context sensitive还是context insensitive),--app-only参数配置开启doop的app only模式,--information-flow开启doop的信息流剖析模式(能够用来做污点剖析),--platform设置剖析须要的jdk平台,--fact-gen-cores配置生成facts的并发性。
本文应用的doop命令参数:
-a context-insensitive --app-only --information-flow spring --fact-gen-cores 4 -i docs/commons-text.jar --platform java_8 --stats none
3.3 从新编译打包commons text
这是我最后应用doop剖析commos text的办法,次要为了尽可能加重的对原生规定的侵入。doop在应用jackee进行剖析事,剖析入口的确定及一些mockobject的构建都须要依赖于对springmvc注解的辨认。
下载commons text的源码,自定义两条class和method注解TestctxTaintedClassAnnotation、TestctxTaintedParamAnnotation:
注解实现为一个空注解,次要是为了标注一下咱们的source,将注解打到对应的class类和办法:
从新编译打包为jar包,失去2中命令参数-i的commons-text.jar。
3.4 革新doop app only下的规定
doop的污点信息流辨认依赖于指针剖析后果,同时也依赖污点转移函数。doop中曾经预置了多条污点转移函数,其中蕴含了字符串、链表、迭代器等根底类办法。
ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.Object)>").ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.String)>").ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.StringBuffer)>").ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(java.lang.CharSequence)>").ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(char[])>").ParamToBaseTaintTransferMethod(0, "<java.lang.StringBuffer: java.lang.StringBuffer append(char)>").BaseToRetTaintTransferMethod("<java.lang.Float: float floatValue()>").BaseToRetTaintTransferMethod("<java.lang.String: byte[] getBytes(java.lang.String)>").BaseToRetTaintTransferMethod("<java.lang.String: char charAt(int)>").BaseToRetTaintTransferMethod("<java.util.Enumeration: java.lang.Object nextElement()>").BaseToRetTaintTransferMethod("<java.util.Iterator: java.lang.Object next()>").BaseToRetTaintTransferMethod("<java.util.LinkedList: java.lang.Object clone()>").BaseToRetTaintTransferMethod("<java.util.LinkedList: java.lang.Object get(int)>").BaseToRetTaintTransferMethod("<java.util.Map: java.util.Set entrySet()>").BaseToRetTaintTransferMethod("<java.util.Map$Entry: java.lang.Object getValue()>").BaseToRetTaintTransferMethod("<java.util.Set: java.util.Iterator iterator()>").BaseToRetTaintTransferMethod("<java.lang.String: char[] toCharArray()>").BaseToRetTaintTransferMethod("<java.lang.String: java.lang.String intern()>").
然而其中没有蕴含String split函数的污点转移规定,须要增加上:
BaseToRetTaintTransferMethod("<java.lang.String: java.lang.String[] split(java.lang.String,int)>").
如上述,doop自有的jackee规定必定没有蕴含咱们自定义的注解,所以须要在EntryPointClass、Mockobj等关系定义中增加对咱们自定义的class污点注解的辨认。
EntryPointClass(?type) :- //... Type_Annotation(?type, "org.apache.commons.text.TestctxTaintedClassAnnotation"); //...MockObject(?mockObj, ?type) :- //... Type_Annotation(?type, "org.apache.commons.text.TestctxTaintedClassAnnotation");
同时也须要增加param污点的注解。doop须要通过这些注解辨认剖析入口办法,构建污点mockobj,建设初始的指向关系等。
//...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");
为了确保办法的可达性,咱们还增加了ImplicitReachable("") :- isMethod("").但后续看不肯定有必要,仅供参考。
通过注解咱们在规定中定义了source,接下来须要定义sink,咱们将ScriptEngine的eval办法定义为sink:
LeakingSinkMethodArg("default", 0, method) :- isMethod(method), match("<javax.script.ScriptEngine: java.lang.Object eval[(].*[)]>", method).
正如前述,因为是在app only下,doop下通过OpaqueMethod关系过滤了jdk类的辨认,这样会导致相应的上述预置的污点转移函数无奈实现污点转移,所以须要另外定制规定流去将转移函数蕴含进数据流剖析过程。
于是须要定义OptTaintedtransMethodInvocationBase关系。
.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, _) ).
在此基础上,为了实现新的污点转移,doop须要依据以下自定义规定剖析出返回值的类型信息。
.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).
基于以上的污点转移过程剖析规定,利用到污点变量的转移剖析规定中。
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).
同时也须要从新定义LeakingSinkVariable关系,因为咱们这里自定义的sink办法也是Opaque办法,这样能力辨认到咱们的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).
从下面规定的定义能够看出,革新的流程还是比拟清晰的,并且通过关系的名字,这些关系的含意和用处也很容易了解。增加这些自定义规定到咱们的doop剖析中运行,在后果中能够看出,doop实现了对commons text的污点信息流的辨认。
在后果集中的LeakingTaintedInformation.csv文件中能够找到咱们须要捕捉到的souce-sink流。
default default <<immutable-context>> <org.apache.commons.text.lookup.ScriptStringLookup: java.lang.String lookup(java.lang.String)>/javax.script.ScriptEngine.eval/0 <org.apache.commons.text.StringSubstitutor: java.lang.String replace(java.lang.String)>/@parameter0
LeakingTaintedInformation.csv给出了污点信息。包含污点的标签(这里是默认的default,能够自定义),sink办法的调用信息,该sink办法对应的污点源头souce信息。
如上图能够看出,org.apache.commons.text.lookup.ScriptStringLookup: java.lang.String lookup(java.lang.String)中调用到javax.script.ScriptEngine.eval,并且污点的源头是org.apache.commons.text.StringSubstitutor: java.lang.String replace(java.lang.String)办法的参数@parameter0。
同时,在后果集中的AppTaintedVar.csv文件也能够看到具体的利用代码中因为污点流传过程中的被净化的变量.以下面commons text 破绽执行办法栈中的
org.apache.commons.text.StringSubstitutor的resolveVariable为例:
能够看出办法中被净化的入参variableName、buf,还有resolver,以及$stack7等(这是通过soot生成jimple的过程中SSA pack局部优化新增的栈变量)。
基于这两个后果集根本能够看出破绽的触发流程或者说污点的流传过程(尽管不是特地直观),如果须要也能够再搭配生成的CallGraphEdge.csv去更不便的进行剖析。
四、总结
doop间接用来剖析大型项目须要肯定的计算资源,并且无论是规定的定制还是剖析后果查看都不是特地直观,毕竟它的设计初衷就是一款剖析框架,用在理论漏扫破绽开掘中可能须要进一步包装批改 。但能够看出,doop作为一款优良的开源动态剖析框架,在算法上毋庸置疑是比拟先进和丰盛的,而且基于开源的算法规定,咱们能够任意去定制咱们须要的剖析逻辑。其与codeql在设计思路也较为相近,将程序信息提取后生成数据库,凋谢查问接口,将程序剖析转变为数据关系的查问,因而能够扩大出更多的用处。