尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
CC6反序列化链
CC6不受 JDK 版本限制的入口替换链CC6 可以看成 CC1 的入口替换版。后半段的触发、串联和执行能力基本原样保留变化点集中在反序列化入口由谁在readObject()阶段把调用送进攻击者可控的Map。1. 适用条件JDK版本不受版本限制组件版本commons-collections 3.1 ~ 3.2.12. JDK 更新限制了 AnnotationInvocationHandler 的自动调用能力CC1 链分为写时触发链和读时触发链入口集中在同一个位置sun.reflect.annotation.AnnotationInvocationHandler.readObject()以下简称 AIH。两条链的区分在触发动作写时触发链的入口动作是一次写入entry.setValue(...)读时触发链的入口动作是一次读取LazyMap.get()。AIH 的readObject()方法会遍历一张注解成员名 - 成员值的表并据此接入 CC1 的两条链读时触发链memberValues.entrySet()- 触发 map 代理类的 AIHinvoke()-LazyMap.get()-ChainedTransformer.transform()写时触发链成员值类型不匹配时调用entry.setValue(...)-TransformedMap.checkSetValue()-valueTransformer.transform()这个方法在 JDK 8u71 被整体重写两个版本对比如下。JDK 7u21 中的实现s.defaultReadObject();...for(Map.EntryString,ObjectmemberValue:memberValues.entrySet()){// ▶ 遍历攻击者可控的 memberValuesStringnamememberValue.getKey();Class?memberTypememberTypes.get(name);if(memberType!null){ObjectvaluememberValue.getValue();if(!(memberType.isInstance(value)||valueinstanceofExceptionProxy)){memberValue.setValue(// ▶ 写时链触发点newAnnotationTypeMismatchExceptionProxy(value.getClass()[value]).setMember(annotationType.members().get(name)));}}}JDK 8u71 中的实现ObjectInputStream.GetFieldfieldss.readFields();...MapString,ObjectstreamVals(MapString,Object)fields.get(memberValues,null);...MapString,ObjectmvnewLinkedHashMap();for(Map.EntryString,ObjectmemberValue:streamVals.entrySet()){// ▶ 遍历仍在StringnamememberValue.getKey();Objectvaluenull;Class?memberTypememberTypes.get(name);if(memberType!null){valuememberValue.getValue();if(!(memberType.isInstance(value)||valueinstanceofExceptionProxy)){valuenewAnnotationTypeMismatchExceptionProxy(...)...;}}mv.put(name,value);}UnsafeAccessor.setMemberValues(this,mv);区别不只是少了一行setValue(...)。旧版readObject直接在攻击者可控的memberValues上原地修值新版readObject把成员值先复制进一张新建的LinkedHashMap最后通过Unsafe写回字段。写时触发链所依赖的那次自动setValue(...)调用自此消失。这次重写到底拦了什么、没拦什么它拦的是 readObject() 阶段那次原地修值的自动调用setValue 已移除 它没拦的是 Commons Collections 包里的任何类—— LazyMap、ChainedTransformer、InvokerTransformer 均未受影响 它留下的是 streamVals.entrySet() 这次遍历仍以某种形式存在 但已经只是 JDK 内部类的实现细节对应出处AnnotationInvocationHandler.javajdk7u21-b11AnnotationInvocationHandler.javajdk8u71-b153. 链上还保留着触发、串联和执行能力入口被限制不等于整条链子都失效了。JDK 修改的是自己包里的一个内部类Commons Collections 包中的组件并未受影响CC1 积累下来的能力大部分仍然成立类承担的能力LazyMap缺失 key 时把get()接进transform()触发能力ChainedTransformer把多个Transformer串成一条调用链串联能力ConstantTransformer给链一个固定起点InvokerTransformer单步方法调用后半段执行手段后半段原样可用LazyMap.get(key) - ChainedTransformer.transform(key) - ConstantTransformer(Runtime.class) - InvokerTransformer(getMethod) - InvokerTransformer(invoke) - InvokerTransformer(exec)从LazyMap.get()到Runtime.exec()这一整段都不需要重新分析。只需要补齐自动调用的入口就可以把链串起来。4. 链的前半段缺少把调用接进可控 Map 的入口能力反序列化阶段缺的是一次自动发生的方法调用作用在攻击者可控的对象上并最终落到LazyMap.get()。缺口可以分成两个半块1. 自动调用点某个 Serializable 类的 readObject() 里 自动对攻击者可控对象调用一个方法 2. 桥存在一个对象把这次方法调用的内部 转成 map.get(key)CC1 中这两块分别由AnnotationInvocationHandler.readObject()和动态代理 按方法名取值承担。第一块已不可靠第二块本身未被修改但它是为了承接entrySet()这次特定调用而引入的——调用点发生变化桥接也需要随之更换。还有一个更隐蔽的要求。这次要找的自动调用不能再是反序列化之后附加的修正动作——这类动作一次修补即可删除。它应当是某个数据结构在重建时不可省略的步骤删掉它这个类自身便不再成立。5. 反序列化期的自动 hashCode 调用成为新的入口方向沿不可省略的步骤往下找候选范围收窄到容器类。哈希容器在readObject()中的工作本质是把流中的 key-value 重新摆回桶中。摆放位置由 key 的哈希值决定因此对每个 key 调用一次hashCode()不是附加动作而是重建哈希表的必经步骤。只要这个类仍是哈希表这一步就在任何版本都无法删除。HashMap正是如此。JDK 8 中它的readObject()末尾for(inti0;imappings;i){Kkey(K)s.readObject();Vvalue(V)s.readObject();putVal(hash(key),key,value,false,false);}hash(key)的第一步就是调用key.hashCode()staticfinalinthash(Objectkey){inth;return(keynull)?0:(hkey.hashCode())^(h16);}JDK 7 写法不同动作相同putForCreate()中先计算hash(key.hashCode())。版本之间实现细节屡有变化这次调用始终存在。这一判断可以逐版本核实JDK 17 中的hash()与putVal()和 JDK 8 完全一致HashMap、HashSet自 JDK 1.2 引入重算哈希是重建自身的固有步骤不存在被修补删除的理由。入口一侧CC6 不受 JDK 版本限制。第一块支点至此确定HashMap这类反序列化时会重建哈希结构的容器。尚缺第二块一个hashCode()内部自带map.get(key)的对象把这次哈希计算桥接进LazyMap。对应出处HashMap.javajdk8uHashMap.javajdk17u6. 替代支点开始收敛出来先补第二块把hashCode()桥接到map.get(key)[读时触发链LazyMap.get()]的对象。符合这个条件的是org.apache.commons.collections.keyvalue.TiedMapEntry它把一张Map和一个 key 绑在一起充当这张表的一个条目。关键在两处方法publicObjectgetValue(){returnmap.get(key);}publicinthashCode(){ObjectvaluegetValue();return(getKey()null?0:getKey().hashCode())^(valuenull?0:value.hashCode());}hashCode()为了计算条目哈希需要先取得条目的值取值的方式就是map.get(key)TiedMapEntry.hashCode() - getValue() - map.get(key) // map 换成 LazyMap就是 LazyMap.get(key)构造时将LazyMap和一个不存在的 key 绑入之后任何一次对该条目的哈希计算都会进入LazyMap的懒加载分支ChainedTransformer被带起。此外TiedMapEntry的equals()与toString()内部同样调用getValue()可桥接的调用不止hashCode()一种CC5 走的是toString()一路。出处commons-collections-3.2.1-sources.jar中的TiedMapEntry.java第一块支点上一节已经确定。ysoserial 在HashMap外再包一层HashSetHashSet本身不存数据内部委托给一张HashMap其readObject()逐个读出元素后执行map.put(e, PRESENT)同样落到HashMap.put() - hash(key) - key.hashCode()。对应出处HashSet.javajdk8u两块支点都确定后拼合时会遇到一个新问题链在构造时就会先执行一遍。组装 payload 时总要执行一步map.add(entry)或map.put(entry, ...)。向哈希容器中放入元素当场就要计算entry.hashCode()——这次计算发生在攻击者自己的机器上后果有两个1. 链提前触发命令在构造 payload 的进程中被先执行 2. 链在目标侧失效LazyMap.get() 会把生成的值回填进 innerMap 反序列化时再 get 同一个 key命中缓存 懒加载分支不会再走因此构造时的核心要求是组装过程中的任何时刻都不能真正对TiedMapEntry执行哈希计算。ysoserial 的做法是把放入和替换拆成两步先向HashSet中放入一个无害的占位对象再通过反射沿内部结构把占位 key 原地换成TiedMapEntry多版本兼容的 try/catch 分支略TiedMapEntryentrynewTiedMapEntry(lazyMap,foo);HashSetmapnewHashSet(1);// 变量名沿用 ysoserial 原文类型为 HashSetmap.add(foo);// 先放无害占位 keyString 的 hashCode 与链无关// 反射HashSet.map - HashMap.table - 节点.keyFieldfHashSet.class.getDeclaredField(map);...HashMapinnimpl(HashMap)f.get(map);Fieldf2HashMap.class.getDeclaredField(table);...Object[]array(Object[])f2.get(innimpl);Objectnodearray[0]null?array[1]:array[0];FieldkeyFieldnode.getClass().getDeclaredField(key);...keyField.set(node,entry);// 占位 key 原地换成 TiedMapEntry整个构造过程中TiedMapEntry.hashCode()一次也未执行序列化时HashMap按桶序写出键值反序列化时重建流程重新对每个 key 计算哈希——链只在目标侧执行。出处ysoserial CommonsCollections6.java另一种常见写法思路相同只是替换的位置不同先塞一个空的ChainedTransformer到LazyMap让构造时那次hashCode()空转序列化前再通过反射设置一个带payload的 transformer 数组并清除回填进innerMap的 key。7. CC6 由此重新成立把入口、桥接和 CC1 留下的后半段接起来CC6 的完整链路不包HashSet、直接使用HashMap.readObject() - putForCreate / putVal的版本链路结构一致。CC1 和 CC6 的前半段对比CC6 保留 CC1 的触发、串联和执行能力把入口从AnnotationInvocationHandler的版本相关行为换成哈希容器重建结构时对 key 的必然hashCode()调用再用TiedMapEntry把这次调用桥进LazyMap.get()。
RELATED

相关推荐

AI生成产品展示图:97%的设计师不知道的5个合规避坑技巧,今天不看明天被下架!

AI生成产品展示图:97%的设计师不知道的5个合规避坑技巧,今天不看明天被下架!

更多请点击: https://intelliparadigm.com 第一章:AI生成产品展示图:合规风险的底层逻辑 AI生成产品展示图正迅速渗透电商、广告与品牌营销场景,但其背后潜藏的合规风险并非源于技术缺陷,而根植于数据来源、生成机制与…

📅 2026/8/31 8:52:07
空调PTC电辅热原理与使用指南:制热效果与耗电实测

空调PTC电辅热原理与使用指南:制热效果与耗电实测

1. 先搞清楚 PTC 电辅热到底解决空调制热的什么问题空调制热时,室外温度越低,传统热泵从室外吸热就越困难。很多人在冬天开空调会发现制热效果差、升温慢,甚至室外机结霜导致频繁停机除霜。PTC 电辅热就是在这种场景下介入的辅助加热方案。它…

📅 2026/9/6 0:53:54
从零设计八路抢答器:单片机方案、硬件电路与软件状态机实战

从零设计八路抢答器:单片机方案、硬件电路与软件状态机实战

1. 项目缘起:从课堂到赛场的“抢答”需求在各类知识竞赛、课堂互动、企业培训甚至家庭游戏中,“抢答”都是一个能瞬间点燃气氛、检验反应与知识储备的核心环节。一个稳定、公平、直观的抢答系统,是保证活动顺利进行的关键。然而,市…

📅 2026/8/31 17:48:33
MORE NEWS

更多资讯

📰

YOLOv11+ByteTrack+单目测距:自动驾驶感知融合实战与踩坑指南

简介:面向自动驾驶感知、目标检测与跟踪领域的工程师与研究者,这份PDF资源系统梳理了YOLOv11多目标跟踪与距离测量融合方案的完整技术路径。文档共41页,作为单文件PDF约2MB,结构清晰,支持目录章节跳转与大纲快速定位。…

📰

283页Java面试核心知识点解析:HashMap、JVM与并发编程实战

简介:这是一份面向Java面试者的核心知识点整理文档,完整覆盖JVM内存区域(程序计数器、虚拟机栈、本地方法区、堆、方法区/永久代)的线程私有与共享划分、JVM运行时内存(新生代Eden、ServivorFrom、ServivorTo及MinorGC…

📰

IPC-A-620线束验收标准全解析:从压接到绝缘防护的判定要点

简介:IPC-A-620线束加工标准PDF文档,是面向电缆组装与线束加工行业从业者、质量检验人员及工艺工程师的规范性参考。资源完整呈现电缆组装的外观接受标准,涵盖检查与测试要求、术语定义、产品等级划分(一般、专用、高科技电子产品…

📰

Schmidt 握手报 0x7E?用 TaoToken 给 OpenClaw 配模型通道查适配层

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

NVIDIA控制面板消失?五种修复方案与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

IEC 61851-1:2017核心解读:CP信号、状态机与充电模式实战指南

简介:电动汽车充电接口标准IEC 61851-1-2017的中文译本,是一份面向充电桩制造商、电动汽车研发人员、通信协议工程师及运维测试人员的专业规范文献。该标准围绕车与充电桩之间的物理连接与信息交互,系统定义了交流充电和直流充电的接口结构、…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬