基于CommonsCollections4的Gadget分析

Author:Welkin

0x1 背景及概要

随着Java应用的推广和普及,Java安全问题越来越被人们重视,纵观近些年来的Java安全漏洞,反序列化漏洞占了很大的比例。就影响程度来说,反序列化漏洞的总体影响也明显高于其他类别的漏洞。

在反序列化漏洞的利用过程中,攻击者会构造一系列的调用链以完成其攻击行为。如何高效的生成符合条件且可以稳定利用的攻击Payload成为了攻击链条中的重要一环,当前已经有很多现成的工具帮助我们完成Payload的生成工作。本文主要以Ysoserial工具为例分析了基于org.apache.commons.collections4类库的Gadget,其通过构造一个特殊的PriorityQueue对象,将其序列化成字节流后,在字节流反序列化的过程中触发代码执行。

文章主要分为两方面,其一是基于PriorityQueue类的序列化对象的构造,另一方面是PriorityQueue对象在反序列化过程中恶意代码的触发原理。下文将从这两方面展开描述一些细节以及实际测试时的一些问题,整体的流程如图1-1所示。

图1-1

0x2 Ysoserial

Ysoserial是一个可以产生反序列化漏洞利用payload的工具,其中包括了许多可用于攻击反序列化漏洞的Gadget的实现。

ysoserial is a collection of utilities and property-oriented programming "gadget chains" discovered in common java libraries that can, under the right conditions, exploit Java applications performing unsafe deserialization of objects. The main driver program takes a user-specified command and wraps it in the user-specified gadget chain, then serializes these objects to stdout. When an application with the required gadgets on the classpath unsafely deserializes this data, the chain will automatically be invoked and cause the command to be executed on the application host.

更多关于Ysoserial的说明可参考:https://github.com/frohoff/ysoserial

0x3 序列化对象的构造

首先,被序列化为字节流的对象实际是一个特殊的PriorityQueue对象,本小节主要分析构造该对象的过程,即图1-1的第一步。图3-1为ysoserial.payloads.CommonsCollections4中getObject方法的代码,是用于构造该PriorityQueue对象的代码:

图3-1

上图中需要注意的有如下两点:

  1. 通过createTemplatesImpl方法生成templates对象
  2. 通过PriorityQueue类的比较器将构造的一系列transformer串联起来
0x01 createTemplatesImpl方法生成攻击载荷

通过createTemplatesImpl方法生成templates对象是非常重要的一部分,因为这是实际承载我们恶意代码的对象,详细说一下,跟进分析createTemplatesImpl方法,其代码具体实现和关键点流程分别如下图3-2和图3-3所示:

图3-2

图3-3

首先生成TemplatesImpl实例,然后通过javassist类库修改StubTransletPayload类字节码,在其中插入执行命令的代码(这里是通过java.lang.Runtime.getRuntime().exec()方法执行命令,也可以插入其他利用代码,如反弹shell等),然后将其父类设置为abstTranslet类,最后将修改后的字节码通过反射写入到TemplatesImpl实例的_bytecodes变量中,这里还同时写入了Foo.class的字节码。除此之外,为了后续恶意代码的触发(如作者注释中所写:required to make TemplatesImpl happy),还要修改TemplatesImpl实例的_name和_tfactory变量,否则后面会在命令代码执行前抛出异常。
StubTransletPayload类代码实现如图3-4所示:

图3-4

StubTransletPayload类继承自AbstractTranslet类并实现了Serializable接口,通常我们构造一个恶意类可能会直接在static代码块或构造方法中写入我们想要执行的代码,这一步在上面通过javassist类库实现,关于StubTransletPayload类需要继承AbstractTranslet类的原因会在反序列化恶意代码触发时解释。
以上即为createTemplatesImpl方法的实现,其本质上是构造了一个特定结构的TemplatesImpl类实例,具体变量的值如图3-5所示,

图3-5

0x02 构造并串联transformer

回到图3-1本段开始处getObject方法的代码中,在35行和40行分别初始化了ConstantTransformer对象和InstantiateTransformer对象,47行将两个对象构造成Transformer数组作为参数初始化了ChainedTransformer对象chain,而在50行,这个ChainedTransformer对象chain又是我们要序列化的对象PriorityQueue中comparator构造方法的参数,comparator可以理解为在PriorityQueue中决定优先次序的比较器,此处用的是TransformingComparator对象。在44-45行、55-57行利用java的反射机制和引用传递的特性修改chain对象中的变量,ConstantTransformer对象中iConstant变量的值设为com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class,InstantiateTransformer对象中iParamTypes设为javax.xml.transform.Templates.class,iArgs设为此前构造的templates对象。

51、52行向队列中插入两个1,这里是为了后面堆化时触发一次堆排序。
最终构造了一个用TransformingComparator对象作比较器的PriorityQueue对象,其内存中变量示意图和抽象结构图分别如图3-6和图3-7所示。

图3-6

图3-7

接下来将分析下这个对象序列化后的字节序列如何在反序列化的过程中触发代码执行。

0x4 反序列化过程中恶意代码的触发原理

反序列化开始至触发代码执行的整体流程如图4-1所示:

图4-1

反序列化过程中首先进入ObjectInputStream类的readObject方法中,然后进入readObject0方法中读取字节流,其中会读取tc标记,然后根据tc标记的类型进入不同的逻辑处理函数中,标记类型可见图4-2,

图4-2

反序列化的是PriorityQueue对象,这里会进入TC_OBJECT的处理逻辑中,跟进到readOrdinaryObject方法里,其具体代码如图4-3,在1769行读取类描述信息,1780行通过类描述信息,初始化对象obj(即PriorityQueue对象),

图4-3

在图4-4中1793行判断是否实现Externalizable接口,通过Externalizable接口可以通过调用对象的readExternal方法实现自定义地完全控制某一对象及其超类的流格式和内容,这里代码进入默认的readSerialData方法中。

图4-4

在图4-5中1882行判断序列化对象是否有readObject方法,如果有则通过反射调用对象的readObject方法为成员变量赋值,接下来就进入了PriorityQueue对象的readObject方法中。

图4-5

图4-6为PriorityQueue对象的readObject方法,

图4-6

图4-7中在defaultReadObject方法中会调用defaultReadFields方法为成员变量赋值,

图4-7

defaultReadFields方法中1989行会递归调用readObject0方法为对象的成员变量赋值直至完成,逻辑与前面描述相似,此处不再赘述。

图4-8

defaultReadObject方法执行完成后,代码流程回到PriorityQueue对象的readObject方法(图4-6)中,读取被transient修饰的Object数组queue(此前被赋值为两个int型的数值1),这部分可以和PriorityQueue类的writeObject方法对照着看(图4-9)。

图4-9

然后代码流程进入图4-6中173行的heapify方法,PriorityQueue本质上是一个最小堆,通过siftDown方法进行次序的调整实现堆化,之前往PriorityQueue对象中插入两个1,可以使队列的SIZE满足for循环的条件从而进入siftDown方法中。

图4-10

继续跟进siftDown方法,次序的调整必然涉及比较,在这儿此前精心构造的比较器就派上用场了,跟进siftDownUsingComparator方法,在图4-11中699行调用了比较器的compare方法。

图4-11

跟进compare方法,在比较前会先通过transformer的transform方法转换一下对象。而此处的transformer正是我们此前构造的ChainedTransformer对象chain序列化成字节流后又反序列化所得(在递归调用readObject0方法时实现),如图4-12所示。

图4-12

继续跟进到ChainedTransformer的transform方法中,此时iTransformers中有ConstantTransformer对象和InstantiateTransformer对象,此处代码逻辑是将ConstantTransformer对象中transform方法的返回值作为参数传入InstantiateTransformer对象的transform方法中。

图4-13

ConstantTransformer对象中transform方法的返回iConstant变量,即com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class

图4-14

InstantiateTransformer对象中transform方法反射获取构造方法后生成了TrAXFilter类的实例,通过newInstance方法进入了TrAXFilter类含参构造方法TrAXFilter(Templates templates)中,并将TemplatesImpl实例作为参数传入,如图4-15所示。

图4-15

TrAXFilter(Templates templates)方法代码如图4-16所示,在64行调用了TemplatesImpl对象的newTransformer方法,newTransformer方法中又调用getTransletInstance方法(图4-17中410行),恶意代码的触发便是在该方法中。

图4-16

图4-17

如图4-18所示,getTransletInstance方法中第376行调用了defineTransletClasses方法后,380行会将_class数组中的某个类实例化,

图4-18

跟进defineTransletClasses方法发现有如图4-19所示这样一段代码:

图4-19

其在for循环里遍历_bytecodes数组并通过TransletClassLoader加载字节码,其中会判断_class[i]的父类是否为ABSTRACT_TRANSLET(”com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet”),这解释了为什么_bytecodes中的StubTransletPayload类要继承自AbstractTranslet类,_transletIndex变量初始化时为-1,若此处判断条件为false,_transletIndex的值仍为-1,则程序执行流程会进入后面if (_transletIndex < 0)的代码块中抛出异常。构造StubTransletPayload类为AbstractTranslet类的子类即可把恶意类的索引值i赋值给_transletIndex。defineTransletClasses方法执行完成后,跳回到getTransletInstance方法中,将_class[_transletIndex](即StubTransletPayload类)实例化触发我们之前通过javassist类库插入的代码块,实现代码执行(图4-20)。

图4-20

到这儿基本上整个Gadget的触发流程就走完了。此处通过调用TemplatesImpl对象的newTransformer方法去间接的调用getTransletInstance方法实现代码执行。除此之外,TemplatesImpl类中的getOutputProperties方法又调用了newTransformer,例如fastjson的反序列化中基于TemplatesImpl类的Gadget便是通过getOutputProperties方法去触发代码执行。
理论上只要构造特定的TemplatesImpl类对象,然后调用其getTransletInstance方法就可以实现代码执行。为方便理解,我写了一个简单的Demo,通过反射正向构造了一个TemplatesImpl对象并调用其getTransletInstance方法来触发代码执行,代码如下:

    public static void main(String[] args) throws Exception{String evilClassPath = "evil.class";ByteArrayOutputStream ba = new ByteArrayOutputStream();IOUtils.copy(new FileInputStream(new File(evilClassPath)),ba);byte[][] evilBytes = {ba.toByteArray()};Class  clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");Class[] paramsTypes = {byte[][].class,String.class,Properties.class,int.class,TransformerFactoryImpl.class};Constructor constructor = clazz.getDeclaredConstructor(paramsTypes);Method method = clazz.getDeclaredMethod("getTransletInstance");constructor.setAccessible(true);method.setAccessible(true);TemplatesImpl templates = (TemplatesImpl)constructor.newInstance(evilBytes,"Welkin",null,0,new TransformerFactoryImpl());method.invoke(templates);}

evil.java代码如下:

public class evil extends AbstractTranslet {static {try {Runtime.getRuntime().exec("calc");}catch (Exception e){e.printStackTrace();}}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}@Overridepublic void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {}
}

0x5 关于类的加载中静态代码的执行

在demo中是通过插入静态代码块的方式注入恶意代码,我看到后面defineClass对类的加载时一度以为这样的实现类似于fastjson中基于com.sun.org.apache.bcel.internal.util.ClassLoader类实现的POC(具体可参考文章DefineClass在Java反序列化当中的利用),在类加载的过程中实现的static代码块执行,但后来调试时发现static{}中插入的恶意代码仍然是在类实例化(即调用newInstance())时触发。
关于类加载的过程,在《深入理解Java虚拟机》中虚拟机类加载机制一节中有详细的说明,类加载可分为加载、验证、准备、解析和初始化这五个阶段。其中我们关心的static代码块的执行是在初始化阶段,初始化阶段实际是执行类构造器<clinit>()的过程,<clinit>()是在Javac编译过程中生成字节码时被添加到语法树中。

<clinit>()方法是编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生。——《深入理解Java虚拟机》

书中还提到虚拟机规范严格规定了有且只有四种情况必须立即对类进行初始化:

  • 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类还没有进行过初始化,则需要先触发其初始化。生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段(static)时(被static修饰又被final修饰的,已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法时。
  • 使用Java.lang.refect包的方法对类进行反射调用时,如果类还没有进行过初始化,则需要先触发其初始化。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先执行该主类。

前面在通过TransletClassLoader中的defineClass方法加载类时仅将字节码装载到了JVM中,没有执行类的初始化,而fastjson的Poc中通过Class.forName()加载类时,Class.forName()方法除了将对应的类装载到JVM中,还会执行类构造器<clinit>()对类进行初始化,从而执行static代码块。Class.forName()代码实现(JDK1.7)见图5-1:

图5-1

forName0()方法用native关键字修饰,说明这个方法是原生函数,非Java语言实现。可从forName()方法的注释中看到第二个参数决定类是否会被初始化,在forName(String className)中默认为true。以上基本解释了我在关于注入的静态代码触发位置的疑惑。

0x6 总结

整个Gadget的调用栈见图6-1:

图6-1

反序列化时首先从ObjectInputStream类的readObject方法中进入到PriorityQueue类的readObject方法里,其readObject方法中会进行堆化,堆化时队列中元素大于等于2时会进行堆排序,这时会调用自定义的比较器(TransformingComparator),TransformingComparator在比较次序时会将对象进行转换。转换时使用的transformer是基于ConstantTransformer对象和InstantiateTransformer对象构造的ChainedTransformer对象,ChainedTransformer对象在其转换方法(transform())中会依次调用ConstantTransformer对象和InstantiateTransformer对象的transform方法,并将前一个对象transform方法的返回值作为参数传入后一个对象的transform方法中,InstantiateTransformer对象中的transform方法会基于参数(这里即ConstantTransformer.transform()的返回值com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter)新建实例,则进入了TrAXFilter类的构造方法中,这里调用了TransformerImpl实例的newTransformer方法,又调用了getTransletInstance方法,加载_bytecodes中修改后的StubTransletPayload类字节码并生成实例,从而触发代码执行。

0x7 参考

https://github.com/frohoff/ysoserial
https://stackoverflow.com/questions/39504847/why-does-class-not-invoke-the-static-block-in-a-class
https://www.freebuf.com/articles/others-articles/167932.html

转载于:https://www.cnblogs.com/Welk1n/p/10511145.html

基于CommonsCollections4的Gadget分析相关推荐

  1. 基于TableStore的数据采集分析系统介绍

    摘要: 摘要 在互联网高度发达的今天,ipad.手机等智能终端设备随处可见,运行在其中的APP.网站也非常多,如何采集终端数据进行分析,提升软件的品质非常重要,例如PV/UV统计.用户行为数据统计与分 ...

  2. OpenCV基于LeNet-5和连接组件分析的数字识别的实例(附完整代码)

    OpenCV基于LeNet-5和连接组件分析的数字识别的实例 OpenCV基于LeNet-5和连接组件分析的数字识别的实例 OpenCV基于LeNet-5和连接组件分析的数字识别的实例 #includ ...

  3. 用FFmpeg搭建基于CNN的视频分析方案

    Photo by Lukas from Pexels FFmpeg作为一个集录制.转换.音/视频编码解码功能为一体的开源框架,自然也需要考虑怎样去和当下流行的视频分析技术融合.本文来自英特尔网络平台部 ...

  4. LiveVideoStack线上分享第五季(五):用FFmpeg搭建基于CNN的视频分析方案

    基于卷积神经网络(CNN)的视频分析在日常生活中应用地越来越广泛,也对软件开发人员提出了更多的挑战.FFmpeg作为一个集录制.转换.音/视频编码解码功能为一体的开源框架,自然也需要考虑怎样去和当下流 ...

  5. python hacklib_【入门】angr:基于python的二进制分析框架

    文章难易度:★★★ 文章阅读点/知识点:angr:基于python的二进制分析框架 文章作者:desword

  6. 基于Kubeadm的Flannel分析

    Flannel概述 Flannel是将多个不同子网(基于主机Node)通过被Flannel维护的Overlay网络拼接成为一张大网来实现互联的,通过官方的一张网络拓扑图我们可以对其基本原理一目了然: ...

  7. 基于上下文的rpn_构建事物-产品评论视频中基于上下文的情感分析

    基于上下文的rpn The word "Social" has taken a whole new meaning in today's digital era. Simply g ...

  8. 论文浅尝 - ICLR2020 | 知道什么、如何以及为什么:基于方面的情感分析的近乎完整的解决方案...

    论文笔记整理:余海阳,浙江大学硕士,研究方向为知识图谱.自然语言处理. 链接:https://arxiv.org/abs/1911.01616 动机 基于目标的情感分析或基于方面的情感分析(ABSA) ...

  9. 论文浅尝 | 嵌入常识知识的注意力 LSTM 模型用于特定目标的基于侧面的情感分析...

    MaY, Peng H, Cambria E. Targeted aspect-based sentiment analysis via embedding commonsense knowledge ...

最新文章

  1. 获取ip地址解析归属地
  2. Error while compiling statement: FAILED: LockException [Error 10280]
  3. @async 没有异步_玩转javascript异步编程
  4. Android 自定义属性(attrs.xml,TypedArray)
  5. springcloud工作笔记094---springcloud项目后端验证_hibernate validator后端校验字段_介绍了解
  6. c++嵌入linux指令以查找文件夹
  7. 列顺序对SQL Server复合索引的影响
  8. 去除ios手机端input输入框上方有阴影
  9. 汽车软件质量体系DIY(1)难题-价值-周期
  10. JetBrain全家桶教育账号申请,每年官方发送一封验证邮箱,验证身份,安全有效
  11. VMware如何安装windows10教程
  12. 今天七夕给大家送个“对象”吧!拿走不谢!
  13. Ubuntu下安装Nodejs, Nide
  14. oracle怎么使用Xmanager,Xmanager使用总结
  15. mysql假死_win7系统假死的5种情况和处理方法
  16. SQL注入闭合方式及万能密码
  17. ubuntu18.04 安装flash。
  18. MacOS技巧:如何禁用 Adob​​e 后台进程,保存 CPU、内存和网络活动
  19. #同余最短路# [51nod] 遥远的旅途
  20. 安装EA后win10提示系统资源不足,无法完成请求服务的解决方法

热门文章

  1. Java 并发编程实战演练
  2. 基于正交对立学习的改进麻雀搜索算法-附代码
  3. Modbus对接 - Java
  4. 关于区块链概念的理解
  5. 【Linux】Linux操作的一些基本指令
  6. 新版标准日本语初级_第六课
  7. sci论文分区是看中科院还是JCR
  8. 什么是真正的 HTAP ?(二)挑战篇
  9. 管理大规模容器集群能力包括_阿里巴巴大规模神龙裸金属 Kubernetes 集群运维实践...
  10. 文献记录(part109)--Self-Representation Based Unsupervised Exemplar Selection in a Union of Subspaces