郑 鹏,沙乐天
(南京邮电大学 计算机学院、软件学院、网络空间安全学院,南京 210003)
近年来,基于Web 技术的互联网应用日益增多,序列化方法作为数据传输的一种通用方案,涉及用户信息、数据存储、访问控制等诸多方面,一旦受到威胁,可能导致企业无法提供完整服务,用户信息财产安全无法得到有效保障。XStream 于2021 年爆出若干反序列化漏洞[1-2]。攻击者需要根据反序列化链的传播特点,在类路径中搜索节点组装成反序列化利用链,达到裹挟控制流执行危险函数的目的。因此,反序列化利用链是反序列化漏洞利用的关键,研究如何自动化检测反序列化利用链变得至关重要。
因为反序列化可以读入任何修改过的文本,为了分析写入文件的恶意代码数据是否可以流向危险函数,需要分析数据在系统中是如何传播和使用的,以确保反序列化过程不会被外界操作篡改。信息流分析技术通过分析程序中数据传播的合法性以保证信息安全,是防止数据的完整性和保密性被破坏的有效手段。
污点分析[3-4]是一种信息流分析方法,包含静态分析方法[5]和动态分析方法[6]。传统的动态分析方法包括fuzz[7-8]、插桩[9-10]等,此类方法程序精确度高,但是只依靠动态方法无法跑出所有可执行程序,因此可靠性不高;静态分析方法虽然可靠性高,但是消耗系统资源多。为了保持可靠性与时间性能之间的平衡,通常会对数据流分析中的分支进行合并,由此会产生大量的误报,通常要结合动态分析验证结果。郭帆等[11]提出了一种针对SQL 注入漏洞的静态检测和动态验证方法。ZUO 等[12]结合静态和动态方法,基于选择符号执行实现了App 的恶意URL 检测工具。
在现有的反序列化链分析方法中,KUANG等[13]提出了一种基于抽象语法树(AST)的漏洞挖掘方法,但这种方法无法完全覆盖语句编写方式,难以实现调用关系与数据流动的分析。HAKEN[14]通过字节码分析实现了反序列化漏洞调用链的挖掘工具Gadget Inspector,但其调用链不完整。杜笑宇等[15]将Gadget Inspector 中的搜索策略从广度优先改良成深度优先,挖掘出了一部分新的链,但是漏报率高。武永兴等[16]通过指针分析反序列化链对应的数据类型,但是指针分析会占用过多的计算资源。RASHEED 等[17]提出了一种混合方法,在静态分析的基础上使用堆抽象对Java 反序列化漏洞进行模糊测试,但是检出率低。
本文主要进行以下工作:1)将符号执行与污点分析结合,定义污点传播规则对传播路径进行约束,构建反序列化污点传播模型;2)采用动静态结合的方法还原污点传播路径,设计并实现字节码级别的反序列化利用链检测工具Taint Gadget。
污点分析是一种跟踪分析污点数据在程序之间流动的方法。程序分析将外来的数据当作污点信息来源,然后传递给程序内部进行数据监控和流向分析,以此判断程序中是否存在敏感数据泄露或者危险操作等违反数据完整性和保密性的安全漏洞。LIVSHITS 等[18]将上下文敏感的别名分析与污点分析相结合,静态检测Java 程序漏洞。Multi Flow[19]使用了一种基于多源绑定的方法,结合污点分析技术精确判别多组源能否在一次执行中绑定。
污点分析方法可以表示成Source、Sink 和Chain三元组的形式,其中:Source 代表污染源,即引入不受信任或机密的数据到应用程序中的地方,这些数据可能来自API 或者网络接口;Sink 代表汇聚点,即污点数据聚集的地方,此处会发生违反数据完整性、保密性、可用性的敏感操作;Chain 是传播的中间节点,Source 经过若干Chain 传播到Sink。污点分析的研究目标是进行污点传播路径分析,即利用程序的依赖信息来分析污点数据在程序中的传播路径。利用污点分析可以从源头跟踪传播链路。污点分析过程如图1 所示。
图1 污点分析过程Fig.1 Process of taint analysis
用反序列化链URLDNS 调用链举例说明,探讨如何搜索Java 反序列化的调用链,如图2 所示。在URLDNS 调用链中,Sink 是Java 内置的java.net.URL类,此类在对URL 对象进行比较时,会触发一次DNS 解析。通过查看目标URL 的DNS 解析是否被解析,可用于判断目标站点是否存在反序列化漏洞。此链路中Source 是java.util.HashMap。接下来利用反射[20]连接HashMap 和URL。
使用反射可以调用任意对象的任意方法和任意属性,即可以动态调用对象方法。基于此条件可以构造反序列化漏洞的调用链。使用反射将object 变量设置成URL,使HashMap 中的末尾函数hash 可以调用到URL 中的起始函数hashcode,最终调用到危险函数InetAddress.getByName(host)。如何寻找到这样的调用点并将污点的链路进行连接是反序列化污点分析的难点。
符号执行的目的是为了提高程序的效率和覆盖率,可以用于约束路径。孙基男等[21]提出一种基于符号执行的注入类漏洞分析方法,输入符号构造矩阵对注入类漏洞进行检测和分析。BALZAROTTI等[22]提出一种自动化方法,通过结合静态检测、动态检测和符号执行产生能够绕过防御的攻击向量。ALHUZALI 等[23]结合动 态分析 和静态分析,提 出一种自动生成攻击向量的污点分析模型。
符号执行的思想是使用符号输入值取代具体的输入值,多用于维护路径。符号执行为每个路径使用符号状态进行维护,符号状态包括路径约束π和符号状态σ,分别用于描述路径的分支条件和存储变量到符号表达式的映射。符号执行实例如下:
符号执行流程如图3 所示。程序由3 条执行路径分化成4 条路经,每条路径对应一个路径约束。最终返回true 的有2 条路径,经求解,路径约束是size>0;返回false 的也有2 条路径,路径约束是size≤0。
图3 符号执行流程Fig.3 Procedure of symbol execution
通过这个例子可以发现,在使用符号执行分析带有条件的控制转移语句时,可以将条件语句转化为符号取值约束,通过分析约束是否有解判断路径的可行性。对于复杂的表达式,路径求解约束面对复杂的约束需要将符号取值约束求解转化为可满足性模理 论(Satisfactory Model Theory,SMT)[24]进 行求解。SMT 是基于SAT[25]的,SAT 是基于布尔逻辑的,但是程序中的条件判断参数和返回值不全是布尔值,因此,SMT 在SAT 的基础上增加了对特定类型的条件检查。
针对当前反序列化漏洞污点分析过程中如何寻找到这样的调用点并将污点的链路进行连接这一难点,本文提出基于符号执行和污点分析的反序列化漏洞检测模型,整体框架如图4 所示。该模型包含静态分析、污点传播规则定义、污点传播3 个模块。静态分析模块获取控制流图,根据图数据库中定义的节点特征获取Source 和Sink。污点分析模块基于传播规则进行传播,传播过程中遇到约束时,根据约束规则进行约束求解并动态验证,不满足条件则丢弃传播链。
针对污点分析的重要节点,过滤Java 字节码文件中的无关指令得到敏感Java 字节码进行方法之间的分析,标记并追踪Java 虚拟机在执行方法时的栈和局部变量表。在调用方法前对相关参数进行入栈,入栈的数据在本地变量表中能标识一个方法内部的数据流动。在此基础上,通过遍历每一个类观察字节码文件获取所有的方法信息和类信息进行类继承,实现关系的整合分析。通过获得的信息,分别利用数据流分析、控制流分析和函数调用图(CG)分析得到参数和返回值之间的关系、每个方法的可达分支、方法与子方法之间的参数对应关系,最终生成控制流图(CFG)。
函数调用图记录的是同一个类中的函数传播。扩展的调用图(ECG)包括跨过程的隐式流函数方法调用。在函数调用图的基础上,从已有的方法中匹配调用方法名和被调用方法名,生成新的调用关系以此全面地记录方法调用。最终的整合图如图5所示。
图5 扩展的调用图Fig.5 Extended call graph
从静态分析中已提取出全部的数据流、控制流、调用图。从已有的3 个图中提取顶点,包括Source、Sink 和Chain。根据CFG 衍生出的ECG 得到图传播的边,符号执行提取的是一个Chain 到另一个Chain之间函数传播(Function fi)的约束,约束的边求解之后调用的下一个符合条件的Chain。使用符号执行确定ECG 传播过程中的约束规则。
2.2.1 污点节点定义
对于反序列化漏洞,一般是从重写了readObject等方法的Source 开始进行相关的调用,经过传播节点Chain,最终调用到一些可能造成危害的Sink,如执行命令、文件写入等。Source 节点和Chain 节点都必须声明序列化接口。
常出现的读取对象函数和代理函数的入口归类为Source,一般是重写读入函数。Sink 函数的特征是调用命令的函数,常见的Source和Sink 如表1 所示。
表1 常见的Source 和SinkTable 1 Common Sources and Sinks
2.2.2 污点传播约束
根据已有的数据流和调用图标记,获取所有的数据流关系和方法调用关系。依据过程间调用规则生成类图边,边满足条件语句的约束和过程间传播流的约束。对于路径中的每个方法,从ECG 中获取敏感变量,用符号执行追踪传播链。
约束传播需要提取路径的约束信息,以生成符合约束的对象,信息包括对象敏感信息和成员变量敏感信息,对象敏感信息为污点分析的调用函数建立正确的类,变量敏感信息为类方法中函数调用建立正确的参数。
函数调用传播类型分为过程间显示流调用和过程间隐式流调用,分别在Chain 内传播和Chain 之间传播,即类中调用是显示流调用,类间调用是隐式流调用。过程间隐式流调用根据过程间的变量位置又分为2 种类型,分别是针对成员变量的直接赋值的调用和针对函数中变量的非直接赋值的调用。针对成员变量和参数的过程间传播可以直接赋值,然而针对函数中变量非直接赋值的调用,不仅要满足变量能够递归传播到调用点的条件,还必须满足调用的对象拥有赋值函数的条件。
ECG 传播流程如下:
其中:→代表显示流传播;->代表隐式流传播。
扩展函数调用图传播流程如图6 所示,其中,var1、var2 是A 类成员变量,可以直接赋值。根据“(Function)getterObj”表达式分析继承关系,首先根据要实现Function 接口和序列化接口的条件找到GetterObj 类,然后发 现GeterObj 不 是A 类的成员变量,不能直接赋值,需要满足GetterObj 类存在赋值函数的条件,最后发现Getterobj 是GetterSlot 类中的成员变量,GetterSlot 中存在赋值函数,因此可以直接赋值。
图6 扩展函数调用图传播流程Fig.6 Propagation procedure of extended function call graph
根据以上分析给出以下约束规则:
规则1污点传播递归寻找隐式流传播中object变量可赋值的类。
规则2对类型限制的条件语句和对成员变量的值限制的条件语句进行污点追踪和约束。
为了防止约束求解中的路径爆炸,只对类型限制的条件语句和对成员变量的值限制的条件语句进行污点追踪和约束,根据规则生成约束的图边,记录所有成员变量的过程间传播条件,到达类间传播的调用点时求解路径约束,不满足约束则无法传播。
过程间污点传播过程如算法1 所示,构造反序列化链从Source 点方法出发,寻找该方法能污染的方法,并从本类的方法中继续寻找下一个能污染的Chain,直到遇到Sink点,污点以边的形式传播构成图,图的边根据调用规则生成,边的约束根据规则生成。将污染传递规则作为边限制的依据,传递到有效的条件语句时会根据规则对需要递归遍历的过程间对象变量进行二次检查,通过数据流检查是否可以赋值。
算法1过程间污点传播算法
动态验证阶段使用静态分析的输出,通过反射重构具有与静态分析中相同属性的对象。从Chain 的调用点得到另一个调用点。从静态分析路径构造对象的过程如算法2 所示,该过程以路径作为输入,从路径末尾开始反方向遍历路径实例化对象,每传播到一个对象,为前一个变量实例化一个对象并将当前对象加载到该字段,然后继续遍历路径的其余对象将其实例化,最后从Source 开始遍历,使用动态污点分析监测能否调用到Sink,为Sink设置若干恶意类。
算法 2对象构造算法
算法实现流程如图7 所示,其中包括污点标记模块、污点传播与验证模块。
图7 算法实现流程Fig.7 Implementation procedure of the algorithm
步骤1污点标记
使用ASM 读取全部类和方法的信息,解析类的继承关系、方法继承关系、构造类和方法的映射、条件语句等相关信息。模拟本地变量表和操作数栈的变化进行污点传播的追踪,分为以下2 段:
第1 段是方法内的污点传播,追踪方法的入参能否影响到方法返回结果。通过在模拟的栈顶数据打标记,并在访问字段和方法时根据规则判断是否能够传播污点,对栈顶数据使用打标记或清空的方式来进行污点传播的分析。直到方法return 时,取出栈顶返回数据的污点标记。
第2 段是方法间的污点传播,同样采用模拟本地变量表和操作数栈的变化进行污点追踪,使用调用者的入参索引作为污点,传播至被调用者入参时,其可以影响到下一个方法的调用。
将上述信息保存为类图数据,保存到Neo4j 数据库中。
步骤2污点传播与验证
依据传播规则构造反序列化链从Source 点方法出发,根据污点传播规则利用深度遍历传播,深度遍历过程中判断隐式流的传播对象是否可控。在传播过程中遇到约束时使用验证模块,直到遇到Sink 点。类图1->类图2->类图3 正向遍历直到类图N。类图之间的约束根据以下约束获得:
约束1根据数据流分析。判断隐式流的调用对象与其参数是否可以写入。若不能直接写入,根据数据流的参数流动递归,直到找到可以直接赋值函数的可疑类为止。接下来从数据流中提取出可疑类及其参数的父子树,通过数据流分析得到继承关系,分析父子关系是否成立,其中包含对接口的判断。
约束2抓取显示流信息。针对过程间传播变量提取当前Chain 中的类型判断指令的字节码,使用Z3 断言对约束1 中生成的可疑过程间对象判断。
将上述约束中的变量表示为Z3-API[26]可以求解的内容,将object 表示为object.tostring,其成员变量表示为object.attribute.toString。结合约束1 和约束2从Neo4j数据库中获取ECG 信息,求得下一个调用的类。Z3 求解采用惰性计算的形式,符号表达式约束在显示流传播中关联并保存,在隐式流传播时计算求解。完整的传播完成后根据符号执行的路径记录还原object 源,动态分析验证路径的有效性。根据约束信息返回对象和成员变量的值,反射构造反序列化类验证从Source 到Sink 的路径,生成测试用例。
本文选 用1 台基于Inter®CoreTMi7-6700H CPU 2.6 GHz 处理器、内存8 GB 的主机进行实验,系统为64 位Windows 10。
实验选取12 个包含反序列化漏洞的Java 库,如表2 所示,其中包括commons-collections-3.2.1、commonscollections-4.0、rome-1.0、groovy-2.3.9 等。实验工 具包括Taint Gadget、Gadget Inspector 以 及Threedr3am 改进后的T-Gadget Inspector,将3 种工具对于这12 种Java 库的反序列化漏洞调用链的检测情况进行对比,检查反序列化漏洞调用链是否有效。
表2 ysoserial 数据集Table 2 ysoserial dataset 单位:个
对3 个工具检测的命中率、漏报率和时间进行统计,如表3 所示,可以看出:Taint Gadget 对于反序列化漏洞调用链的检测命中率平均为70.6%,效果优于T-Gadget Inspector 的23.2% 和Gadget Inspector 的8.5%;Taint Gadget 对于反序列化漏洞调用链的挖掘漏报率平均约为4.1%,效果优于T-Gadget Inspector 的35% 和Gadget Inspector 的80.2%;Taint Gadget 对于不同Java 基础库的测试时间较为稳定,其平均时间约为 78.4 s,T-Gadget Inspector 平均时 间约为74.3 s,Gadget Inspector 的平均时间约为68.7 s。Taint Gadget 消耗更多时间的原因是路径约束消耗了更多的系统资源。
表3 静态数据对比Table 3 Static data comparison
根据符号执行的求解结果,利用动态方法构造到达Sink 的对象组成路径,与基于动静态结合的工具Serhybrid 动态运行对比结果如表4 所示,其中,TPs 代表路径存在的数量,FPs 代表失败路径不存在的数量。可以看出:Taint Gadget 的动态检出率平均为90.6%;Serhybridpub 的动态检出率平均28.5%;Taint Gadget 的动态运行时间约为20.8 s;Serhybridpub 的平均动态运行时间约为1 734.3 s。因为Serhybridpub 采用的是非定向的模糊测试方法,所以时间消耗远超本文的方法,可见本文的动态检出率和时间性能都好于Serhybridpub。cc3 中有2 条链没有检测出的原因是没有对动态代理做出约束,groovy 和mozilia 各有一条链没有检测出的原因是路径的条件过于苛刻。
表4 动态数据对比Table 4 Dynamic data comparison
下面将介绍针对commons-collections4-4.0.jar 包中CC2 反序列化漏洞链路进行复现的过程。首先从静态分析的Source 结果中搜索到有入口函数readObject 的类PriorityQueue,然后通过污点分析找到类间传播的调用点,记录类中传播的条件语句,到调用点根据约束生成对象,对象的成员变量满足路径条件。约束求解过程如图8 所示。
图8 污点分析过程Fig.8 Process of taint analysis
根据路径条件约束,comparator.compare 是第1 个隐式 传播调用点,tansformer.transform 是 第2 个隐式传播调用点,method.invoke 是危险函数调用点,可以调用恶意类TeamplatesImpl。记录类中的路径约束直到调用点求解,分别得到PriorityQueue、TransformingComparator、InvokerTransformer 的过程间传播约束条件,对约束求解构造3 个对象。
Priority Queue 的初始化需要反射设置成员变量size、comparator、queue。根据约束求解的结果构造对 象Priority Queue,将size 反射设置为2,将comparator 反射设置为Transforming Comparator,Tranfroming Comparator 初始化需要将成员变量comparator 设置为InvokerTransforming,反射创 建Invoker Transformer 对象,最终成功地将Priority Queue 构造为 Priority Queue
Taint Gadget的静态检测过程与T-Gadget Inspector和Gadget Inspector 的区别体现在静态分析、约束条件、污点传播等3 个方面。首先,Taint Gadget 静态分析过程中比其他2 种工具多收集了成员变量信息,方法中的参数传播信息和条件语句,为约束条件和污点传播提供条件;其次,Taint Gadget 基于条件语句生成约束条件,其他2 种工具没有记录条件语句,导致没有生成约束,所以会记录不存在的链路,造成路径爆炸,导致命中率较低;最后,Taint Gadget 污点传播采用过程间传播分析,准确记录了类的变量传播,提高了传播精度。其他2 种工具采用的是过程内传播分析方法,只记录函数的调用关系,不记录调用关系所需的变量信息是否符合调用要求,丢失污点传播精度,导致误报率较高。
Taint Gadget 的动态验证过程与Serhybridpub 的区别是Serhybridpub 使用Randoop 进行模糊测试,通过随机输入变量值测试到达危险函数,因此对类成员变量信息不敏感。如图8 所示,Taint Gadget 在静态的约束求解过程中已经求解出构造对象所需的类成员变量信息。相对而言,Taint Gadget 对类成员信息敏感,针对性更强,省去了模糊测试的时间。
本文利用符号执行和污点分析技术,针对污点传播描述规则提出约束,实现Java 反序列化漏洞检测和验证工具Taint Gadget。实验结果表明,Taint Gadget 在命中率和漏报率方面性能优于现有的工具Gadget Inspector 和T-Gadget Inspector,且没有消耗太多的时间,Taint Gadget 的动态性能也好于Serhybridpub。本文没有构建动态代理约束,下一步工作将针对动态代理特性进行约束构建和求解。