高版本触发toString的几种方法
字数 3655
更新时间 2026-02-27 03:12:07

高版本Java反序列化中触发toString的Gadgets教学文档

1. 背景与核心挑战

随着Java版本的演进,特别是从JDK9引入模块系统(JPMS)并在JDK17中强化了强封装(Strong Encapsulation)后,传统的反射调用方法受到了严格限制。关键限制如下:

  • exports声明:只有被exports声明的包中的类才能被外部代码直接访问。
  • opens声明:只有被opens声明的包中的类才能通过反射访问其私有(private)成员。

这种变化导致许多依赖深度反射的旧有反序列化利用链(Gadget)失效。为了在JDK17及更高版本中实现利用,安全研究者发展出了通过修改当前运行类的模块偏移(module offset)等方法来绕过限制。本文档旨在整合并详细说明在高版本Java环境下,几种可用于触发任意对象toString()方法的Gadget,这是构建完整反序列化攻击链的关键步骤。

2. EventListenerList 触发 toString

这是在高版本JDK(如JDK17)中触发toString首选链,其原理与在JDK8中基本一致。

  • 核心类javax.swing.event.EventListenerList
  • 链分析EventListenerList在反序列化的readObject方法中,会对其内部维护的监听器列表进行遍历和操作。当列表中包含精心构造的对象时,在反序列化过程中会触发该对象的toString方法。
  • 利用工具方法
    public static Object get_EventListenerList(Object obj) {
        EventListenerList listeners = new EventListenerList();
        // 通过反射或其他手段,将目标对象(obj)放入listeners的结构中,使其在反序列化时被调用toString
        // 具体构造方式通常涉及将对象包装进其内部的`listenerList`数组中
        return listeners;
    }
    
  • 触发方式:将上述方法返回的EventListenerList对象进行序列化,再进行反序列化操作,即可触发嵌入对象的toString()

3. BadAttributeValueExpException 触发 toString

此链在CC5、Fastjson原生链、Jackson原生链中均有应用,但存在明确的版本限制

  • 核心类javax.management.BadAttributeValueExpException
  • 链分析:该异常类在readObject方法中,会直接调用其val字段的toString()方法。
  • 利用工具方法
    public static BadAttributeValueExpException get_BadAttrExpEx(Object obj) {
        BadAttributeValueExpException exception = new BadAttributeValueExpException(null);
        // 通过反射将字段`val`的值设置为目标对象(obj)
        Field valField = BadAttributeValueExpException.class.getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(exception, obj);
        return exception;
    }
    
  • 版本限制与失效原因
    • JDK7及以前BadAttributeValueExpException没有实现自己的readObject方法,因此此链无效。
    • JDK8 ~ JDK14BadAttributeValueExpException实现了readObject,且其val字段为Object类型,可以接受任何对象,链有效。
    • JDK15及以后val字段的类型被修改为String。在反序列化过程中,如果val不是String类型,会抛出异常,导致此链完全失效

4. HashMap + XString 触发 toString

此链在Fastjson原生反序列化利用中也经常被提及。

  • 核心类java.util.HashMap, com.sun.org.apache.xpath.internal.objects.XString
  • 链分析
    1. 反序列化HashMap时,在readObject -> putVal -> hash的计算过程中,会调用键(Key)对象的hashCode()方法。
    2. XString类的hashCode()方法内部会调用this.toString()
    3. 因此,只要将HashMap的Key设置为一个XString对象,并在构造该XString时使其m_obj成员指向我们最终想要触发toString的目标对象,即可在反序列化HashMap时达成目的。
  • 利用工具方法
    public static HashMap get_HashMap_XString(Object obj) throws Exception {
        XString xString = new XString("anything"); // 参数无关紧要
        // 通过反射将xString的m_obj字段设置为目标对象(obj)
        Field objField = XString.class.getDeclaredField("m_obj");
        objField.setAccessible(true);
        objField.set(xString, obj);
    
        HashMap map = new HashMap();
        map.put(xString, "value"); // 关键:将XString对象作为Key放入HashMap
        return map;
    }
    

5. HotSwappableTargetSource + XString 触发 toString

这是Spring框架原生的一条链,需要项目引入Spring AOP依赖。

  • 核心类org.springframework.aop.target.HotSwappableTargetSource, com.sun.org.apache.xpath.internal.objects.XString
  • 链分析:此链本质上是上一条HashMap+XString链的变体,中间增加了HotSwappableTargetSource#equals的调用点。
    1. 反序列化过程最终会调用到HotSwappableTargetSourceequals方法。
    2. 在该equals方法中,会调用其target字段的equals方法。
    3. 通过将target设置为一个XString对象(其m_obj指向最终目标),并精心构造比较流程,最终会触发XStringtoString
  • 利用工具方法:构造相对复杂,通常涉及创建HotSwappableTargetSource实例和XString实例,并通过反射设置它们的内部字段,最后将它们放入一个HashMap中作为触发入口。序列化/反序列化该HashMap即可。

6. Hashtable + TextAndMnemonicHashMap 触发 toString

这是一个相对较新的触发方式。

  • 核心类java.util.Hashtable, javax.swing.plaf.synth.TextAndMnemonicHashMap
  • 链分析
    1. Hashtable在反序列化的readObject()中,会调用reconstitutionPut方法将元素放入哈希表。
    2. reconstitutionPut中,会进行键(key)的比较,最终可能调用到AbstractMap.equals()方法。
    3. AbstractMap.equals()中,如果传入的参数m是一个TextAndMnemonicHashMap实例,则会调用m.get(key)
    4. TextAndMnemonicHashMap.get()方法中,会对传入的key调用toString()方法。
    5. 因此,通过构造HashtableTextAndMnemonicHashMap,并控制key为我们最终的目标对象,即可在反序列化Hashtable时触发任意toString
  • 利用工具方法
    public static Hashtable get_Hashtable_TextMap(Object obj) throws Exception {
        TextAndMnemonicHashMap textMap = new TextAndMnemonicHashMap();
        // 构造Hashtable,使其在比较时传入的`m`为textMap,并且比较的key是目标对象(obj)
        Hashtable table = new Hashtable();
        // 具体构造涉及通过反射修改内部状态,将obj设置为比较的key,
        // 并确保table中的某个entry在reconstitutionPut时与textMap进行比较。
        // 构造略复杂,需仔细控制table和textMap的内容。
        return table;
    }
    

7. 总结与选择建议

Gadget 组合 适用JDK版本 依赖 特点
EventListenerList 全版本(高版本首选) JDK自带(Swing) 稳定,与低版本无差异,是JDK17+环境下的可靠选择。
BadAttributeValueExpException 仅限 JDK8 ~ JDK14 JDK自带(JMX) 构造简单直接,但被JDK15+彻底封堵。
HashMap + XString 全版本(需考虑模块访问) JDK自带 依赖XString的内部访问,在强封装下可能需要模块偏移技巧。
HotSwappableTargetSource + XString 全版本(需考虑模块访问) Spring AOP Spring环境下的利用链,同样依赖XString
Hashtable + TextAndMnemonicHashMap 全版本(需考虑模块访问) JDK自带(Swing) 较新的利用点,构造略复杂,但提供了另一种触发途径。

教学建议

  1. 环境适配:首先明确目标Java版本。对于JDK15+,应优先研究EventListenerList链。
  2. 理解原理:掌握每条链的触发入口(如readObjectequalshashCode)和最终的toString调用点。
  3. 构造练习:在安全实验环境中,尝试编写和调试上述工具方法,理解每一步反射设置的目的。
  4. 组合利用:触发toString通常只是第一步,需要将它与能够执行命令或造成危害的“后半部分链”(如TemplatesImpl)结合,才能形成完整的反序列化漏洞利用。

(注:本文档内容基于所提供链接的技术文章进行整合与阐述。关于具体的反射字段设置、绕过模块强封装的详细代码、以及如何与恶意类链拼接形成完整利用,链接中未提供全部代码细节,需结合Java安全编程知识进行深入研究与实践。)

 全屏