IEEE S&P收录论文 | 基于程序分析和Fuzz融合的Java反序列化漏洞挖掘技术
近日,蚂蚁安全非攻实验室与扬州大学、清华大学合作论文《ODDFUZZ: Discovering Java Deserialization Vulnerabilities via Structure-Aware Directed Greybox Fuzzing》被IEEE S&P 2023录用。
IEEE S&P是网络与系统安全领域四大顶级会议之一,也是中国计算机学会CCF A类推荐会议。该会议历史悠久,发表难度极高,2022年IEEE S&P录用147篇论文,录用率仅14.5%。这次论文中稿,表明蚂蚁团队成果的学术价值受到学术界的认可,体现了蚂蚁在漏洞挖掘技术方面的创新研究能力。
清华大学长聘副教授、蓝莲花战队教练张超老师表示,Java语言是银行、证券、保险、互联网、信创等关键领域的核心开发语言,但是Java程序面临着ODD反序列化漏洞的严重威胁。其中,ODD蚂蚁安全实验室推出的漏洞模式概念,揭示了反序列化漏洞的开放、动态性本质。本项研究是与蚂蚁安全非攻实验室合作成果。团队围绕ODD漏洞挖掘技术进行了探索,提出了基于定向模糊测试技术的创新性挖掘方案ODDFuzz,显著提高了ODD漏洞的挖掘效率和有效性。本项技术在多个真实软件中发现了ODD漏洞,帮助厂商提高了产品安全性,也为研究人员提供了新的研究思路。
蚂蚁安全非攻实验室主要聚焦深耕Java领域的安全研究,通过技术研究赋能业务安全。实验室对漏洞挖掘技术进行了创新探索,持续发现并修复了Spring、WebLogic、Apache Dubbo等多个具有社会影响力的远程命令执行漏洞,这些产品广泛应用在金融和互联网等多个重要行业的数字化基建。其中,Spring远程命令执行漏洞是一个潜藏于著名框架Spring十余年的严重漏洞,漏洞隐蔽,危害严重,影响非常广泛。这些漏洞的发现和修复对行业安全水位的提升做出了一定的贡献。在赋能业务安全的同时,提出的创新性方案受到学术界认可,研究工作被软工顶会ICSE、安全顶会IEEE S&P等录用。经过多年的努力,非攻安全实验室已经成长为蚂蚁安全实验室的重要成员,作为蚂蚁基础安全体系的重要力量,在Java安全领域持续给行业带来技术突破和影响。
序列化(Serialization)和反序列化(Deserialization)机制是让Java对象脱离运行环境传输的一种手段,多用于数据传输和存储。在微服务和远程调用大流行的情形下,序列化和反序列化在各种应用系统、中间件中随处可见,漏洞防守也是重中之重。攻击者可以通过构造恶意输入,让应用程序在对不可信数据进行反序列化处理时,产生非预期的对象及函数调用链,造成任意代码执行等攻击效果。尽管学术界和工业界已经提出了一些方案来自动化地挖掘项目中可以被用于发起反序列化攻击的调用链(Gadget Chain),但仍存在误报率和漏报率较高的问题,导致在实际部署运行使用时收效甚微。
在本文中,我们提出了一种新颖的动静态混合的解决方案ODDFuzz以有效地发现Java反序列化漏洞。首先,ODDFuzz执行轻量级静态污点分析,以识别所有可能导致反序列化漏洞的候选调用链。然后,ODDFuzz执行定向模糊测试来探索这些候选项,并生成PoC测试用例以减少误报。具体地说,ODDFuzz应用了一种结构感知的种子生成方法来保证测试用例的有效性,并采用混合反馈和步进式的变异策略来指导定向模糊测试。我们在Java反序列化库ysoserial上对ODDFuzz的有效性进行了评估,结果表明,ODDFuzz可以发现16个已知的调用链,而两个最先进的基线方法只能识别其中的3个。此外,我们在包括Oracle WebLogic Server、Apache Dubbo、Sonatype Nexus等市面上知名的开发框架与中间件上进行了测试,并发现了6个以前未报告的反序列化漏洞,其中5个被授予了CVE编号。
1. 背景
1.1 开放式动态反序列化(ODD)
在Java中,序列化机制是将对象的属性及其赋值转换为字节流以用于跨进程或跨平台数据传输和持久性存储的过程。相应的,序列化对象的接收方必须解析字节流以便重建新对象,这个重建的过程则称为对象反序列化。对此,蚂蚁安全实验室提出开放式动态反序列化(ODD,Open Dynamic Deserialization)的概念以揭示反序列化中的安全本质。ODD简单来说就是应用架构支持在反序列化过程中动态生成任意类型的对象。ODD的核心是“开放”和“动态”,是为了提升应用开发的灵活性和效率而设计出的一系列特性。但是从安全角度来说,“开放”和“动态”本质上是不安全的,在这种设计模式中容易失去对程序行为的控制,导致不可信输入对程序行为的任意劫持,从而形成一个集中的RCE(远程代码执行)突破奇点(ODD中文直译是奇偶的奇,也是奇点的奇)。这种漏洞类型在历史上已经引起了大量的安全问题,各类分布式技术以及系统均受到了非常大的挑战。
1.2 威胁模型
图1展示了我们在本文中探索的威胁模型。假设目标Java应用程序中存在攻击者可控制的反序列化入口点,如果攻击者1)将精心制作的恶意对象注入这些不受信任的入口点,目标应用程序将2)反序列化这些对象,并在应用程序的classpath上自动调用攻击者指定的调用链。然后,3)注入对象携带的恶意有效负载将流入安全敏感的调用点,使攻击者能够执行反序列化攻击。以流行的商业平台Oracle WebLogic Server(WLS)为例。WLS用于与其他Java程序传输序列化数据的T3协议暴露了一个巨大的攻击面,这为攻击者提供了不受信任的反序列化入口点,用于向受害应用程序发送有效负载。尽管如此,包含反序列化入口点并不意味着目标应用程序易受攻击。如红色虚线箭头所示,考虑到修复反序列化漏洞可能特别困难且成本高昂,开发人员更倾向于采用白名单或黑名单来限制不受信任对象的反序列化。然而,一旦发现了新的调用链,现有的防御解决方案就很容易被绕过。因此,在Java反序列化漏洞挖掘的过程中,如何准确高效地识别出应用程序的classpath上存在的可利用的调用链显得尤为重要。
图1:ODD漏洞威胁模型图
2. 挑战
尽管在实践中存在不安全反序列化的严重影响,但在自动发现Java ODD漏洞(尤其是可利用的反序列化调用链)方面的现有努力仍不令人满意。例如,最先进的Java反序列化调用链检测工具Gadge Inspector只能识别出3条ysoserial中已披露的调用链。我们观察发现,为了实现高召回率(识别更多可能的调用链)和高精确性(确认更多可利用的调用链),必须解决以下三个基本挑战。
2.1 Java运行时多态行为
导致Java ODD漏洞的根本原因是,不受信任的对象在其反序列化的过程中可以到达并影响目标应用程序的sink方法。因此,现有的工作大多通过静态分析来识别代码中可被攻击者可以利用来定制不安全的反序列化路径的可控方法。然而,由于Java语言的运行时多态性,现有的静态分析技术难以精确推断程序在运行时将采取的程序路径,导致较高的漏报率。尽管通过类层次分析(CHA)以考虑所有可能的方法调用可以一定程度上缓解这个问题,但由于候选可控方法的数量随着长度的增加呈指数增长,盲目地考虑应用程序classpath上的所有可用方法将不可避免地导致路径爆炸。
2.2 结构化输入构造
为了在应用程序的classpath上调用一系列可利用的方法,注入对象的结构通常被组织为具有多级子对象的嵌套形式。这给构造语法上(即,生成的注入对象可以(反)序列化)和语义上(生成的注入目标满足触发调用链的某些控制和数据流约束)的有效模糊输入带来了挑战,因为它需要1)塑造注入对象的多级层次结构以执行所报告的调用链,以及2)分配适当的属性值以触发安全敏感的Sink方法。如果事先不了解这样一种复杂的嵌套形式的对象结构,传统的模糊化技术无法彻底模糊整个调用链,因为它们很难找出每个注入对象背后的复杂结构。
2.3 目标导向的模糊测试
由于漏洞调用链由一系列攻击者可控的方法组成,这些方法在对象反序列化期间自动执行,因此传统的代码覆盖不适合指导模糊化,因为触发更多代码片段的生成的注入对象可能无法到达目标链的安全敏感调用站点。因此,Fuzzer将把大部分时间预算浪费在探索无法到达的路径上。定向模糊测试技术(DGF)利用种子执行轨迹上所有基本块的距离的算术平均值来选择和调度种子以快速到达目标站点。然而,不是每个基本块都驱动种子对象朝着所识别的调用链中预期的目标sink变异,使得通过这种方式计算的种子距离可能是偏置的。此外,由于对种子对象的属性的修改可能会同时激活调用链中的多个可控方法,不同种子对象的执行轨迹可能变化很大且只能在运行时获取。因此,需要能够有效评估生成的注入对象的质量的目标导向模糊反馈。
3. 方法
3.1 系统架构
图2展示了ODDFuzz的整体架构。ODDFuzz的工作流程包含两个主要模块:1)在调用链识别模块中,ODDFuzz将被测试程序的编译文件(例如,Jar、War或Class文件)作为输入,并进行轻量级污点分析,以自动枚举所有潜在的调用链。2)在调用链验证模块中,ODDFuzz基于所识别的调用链构造语法上有效的注入对象用于Fuzzing。在Fuzzing Loop中,ODDFuzz结合了步进式变异策略和混合反馈以引导模糊器将注入对象朝着期望的安全敏感的sink方法突变。当生成的注入对象到达sink方法时,给定的调用链将被报告为可利用于实施反序列化攻击的调用链。
图2:ODDFuzz整体架构图
3.2 轻量级污点分析
ODDFuzz首先执行轻量级的基于摘要的污点分析以识别可疑调用链。具体而言,主要包括方法摘要计算和调用链识别两个部分。
a. 方法摘要计算
对于每个方法,ODDFuzz首先提取所有变量关系,将其作为方法摘要。为了跟踪每个方法变量之间的信息传播,我们重点关注四个基本语句,包括1)Assign语句、2)Load语句、3)Store语句,和4)Call语句。数据依赖关系与方法参数的变量也将包含在方法摘要中。攻击者可以通过更改注入对象的属性值来控制这些语句的实际参数,从而达到传播恶意值的目的。
b. 调用链识别
为了根据先前计算的方法摘要识别可疑调用链,ODDFuzz需要指定可利用的Source方法和安全敏感的Sink方法。如图3所示,我们总共指定了16个Source方法和30个Sink方法,这些方法可以被利用来进行远程代码执行(RCE)、JNDI注入(JNDIi)、系统资源访问(SRA)和服务器端请求伪造(SSRF)攻击。
图3:source和sink方法图
一旦在classpath上找到已知的Source方法,ODDFuzz基于方法摘要开始执行深度优先搜索(DFS)以串联可利用的方法。为了避免由递归调用等逻辑导致陷入遍历陷入无限循环的问题,我们为候选调用链的最大长度设置了一个阈值。此外,为了处理Java语言的运行时多态性,我们在污点分析期间对每个调用语句执行类层次分析(CHA)。特别地,对于调用语句r=x.k(a, ...),如果调用者变量x被污染,则方法k的所有多态方法都将被列为候选方法。否则,ODDFuzz则等同于传统基于调用图的污点分析器。在调用安全敏感的Sink方法或枚举的链的最大长度超过阈值之前,此迭代分析过程不会停止。在分析了从Source方法到Sink方法的所有路径之后,ODDFuzz调用验证模块进行验证。得益于我们轻量级的污点分析,ODDFuzz可以很好地权衡调用链识别的准确性和可扩展性。
3.3 结构化种子生成
构造语法上有效的注入对象需要1)设计其嵌套对象层次结构,以反映给定调用链的执行流程,2)为相应的多级子对象分配适当的属性值,以便于注入对象到达安全敏感的Sink方法。然而,大量使用嵌套结构使得很难对调用链进行Fuzzing,因为它需要复杂对象结构的精心设计的属性布局,这对传统的Fuzzing方案并不友好。为此,我们设计了一种结构感知种子生成方法,通过采用属性树这一分层的数据结构来处理复杂的嵌套形式。如图4所示,其中根节点表示包含一个或多个可控方法所属的类对象,叶节点是一系列包含属性类型和名称的类字段。为了生成注入对象,我们实例化调用链中涉及的每个类,并利用反射动态收集每个类的可用属性/字段来构建属性树。如果属性树中某个字段节点的属性类型是由另一个属性树表示(或继承)的对象,而该类持有目标调用链中的下一个可控方法,则我们通过将该字段节点连接到其对应的类对象节点(即,另一属性树的根节点)来合并这两个属性树。
图4:属性树示例图
当ODDFuzz识别出可疑调用链时,它将被输入到输入生成器中,以构建相应的属性树。然后,模糊器开始遍历该树的主干,将其转换为可用于模糊化的初始注入对象。
3.4 基于混合反馈的种子调度
为了有效地选择和调度种子以到达给定调用链的Sink方法,我们提出了一种混合反馈驱动的种子调度方法。从根本上讲,我们的目标是将更多的能量分配给更接近目标Sink方法的种子。为此,ODDFuzz考虑了两种反馈度量:种子距离和可控方法覆盖率。不同于传统定向Fuzzing枚举在种子执行路径上的所有基本块,ODDFuzz收集属于目标调用链的可控方法的基本块以计算种子距离,避免Fuzzer探索不相关但更接近的路径。此外,我们还采用可控方法覆盖率(即,目标链中可控方法的分支覆盖率)作为另一个衡量标准,以确定覆盖更多程序路径的种子的优先级。在最初的模糊化阶段,可控方法覆盖率旨在引导模糊器选择不同的种子并对其进行优先级排序,避免因偏爱具有特定执行路径的特定种子而陷入局部最优。在能量调度阶段,它试图给予距离相同但是覆盖更多分支的种子以更高的优先级,从而增加突变的机会。
3.5 步进式种子变异
Fuzzing通常通过位翻转等操作随机改变二进制文件来产生新的输入,然而当应用于结构化输入时,这种位级突变可能导致无效语法。为了解决这个问题,我们采用了一种参数化的Fuzzing框架,从而将结构化输入映射到一系列非类型化比特(即参数),以在比特级变异生成的种子。参数上的这些位级突变对应于结构化注入对象上的属性级突变。然后,ODDFuzz应用步进式种子突变策略,以有效地将种子引导到目标调用链的所需安全敏感调用位置。
具体来说,Fuzzer首先遍历要变异的注入对象的属性树,并检查每个属性的类型。对于布尔型等primitive类型的属性,Fuzzer器使用伪随机方法将非类型化位参数转换为随机类型值。对于reference数据类型,Fuzzer为特定类型定制目标模板。例如,当属性类型为class时,Fuzzer从该调用链涉及的类及其子类中随机选择一个类。对于数组属性,Fuzzer随机设置数组大小,并根据元素类型(例如,继承数组类类型的实例)为数组分配随机值。
最后,当变异种子到达安全敏感的Sink时,Fuzzer将报告给定的调用链为可利用的调用链。
4. 实验结果
4.1 有效性
我们选择了认可度较高的ysoserial数据集作为基准来评估我们的方法。实验表明,如表1所示,相较于目前工业界常用的反序列化调用链挖掘工具Gadget Inspector和学术界提出SerHybrid,ODDFuzz可以有效地提高反序列化调用链的挖掘精度和效率。
表1:GadgetInspector、SerHybrid、ODDFuzz benchmark结果
4.2 漏洞挖掘产出
在漏洞挖掘⽅⾯,ODDFuzz在包括Oracle WebLogic Server、Apache Dubbo、Sonatype Nexus等市面上知名的开发框架与中间件上进行了测试,并发现了6个以前未报告的反序列化漏洞,其中5个被授予了CVE编号,如表2所示。同时该⼯具已成功在蚂蚁内部落地部署,证明该⼯具在实际⼯业环境中的实⽤性。
表2:新发现的反序列化漏洞表
5. 总结
基于结构感知定向模糊测试的ODDFuzz技术实现了面向Java ODD漏洞的⾃动挖掘框架。ODDFuzz的有效性和⾼效性通过⼴泛的评估得到了证实。总共发现了6个以前未报告的反序列化漏洞,其中5个被授予了CVE编号。此外,我们也在蚂蚁内部部署了ODDFuzz,进⼀步证明了ODDFuzz的优越性。为了促进未来的相关研究,我们将在https://github.com/ODDFuzz/ODDFuzz开放源代码,敬请期待。