写点什么

使用 doop 识别最近 commons text 漏洞的污点信息流

  • 2023-05-16
    广东
  • 本文字数:6064 字

    阅读完需:约 20 分钟

作者: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 工作流程

  1. doop 的 fact generator 模块会对输入进行解析(例如 jar 包的解析或者类的 resolve 从而加载进必要的类信息到内存中)

  2. 调用 soot、wala 等工具生成 jimple IR,在此基础上生成后续分析引擎需要的 facts 文件。而后 doop 使用 LogicBlox(目前 doop 已不维护)或者 Soufflé(开源的 datalog 分析引擎)

  3. 基于 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 在设计思路也较为相近,将程序信息提取后生成数据库,开放查询接口,将程序分析转变为数据关系的查询,因此可以扩展出更多的用途。

发布于: 刚刚阅读数: 3
用户头像

官方公众号:vivo互联网技术,ID:vivoVMIC 2020-07-10 加入

分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。

评论

发布
暂无评论
使用doop识别最近commons text漏洞的污点信息流_SAST_vivo互联网技术_InfoQ写作社区