0%

Fastjson-1.2.24-漏洞分析

Fastjson漏洞分析

本篇文章以1.2.24版本出现的反序列化漏洞为基础学习Fastjson个版本漏洞,及POC。

环境准备

  • java version “1.8.0_152”
  • idea 2023.1
  • FastJson 1.2.24

漏洞原理1.2.24

我们先学习一下1.2.24的漏洞原理

演示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
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
package org.fvuln;

/**
* author: f19t
* Date: 2023/7/18 21:04
* File: User.java
*/

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

public class User {
public boolean isB() {
System.out.println("调用isB b");
return b;
}

public void setB(boolean b) {
System.out.println("调用setB b");
this.b = b;
}

public int getAge() {
System.out.println("调用getName age");
return age;
}

public void setAge(int age) {
System.out.println("调用setName age");
this.age = age;
}

public Flag getFlag() {
System.out.println("调用getName flag");
return flag;
}

public void setFlag(Flag flag) {
System.out.println("调用setName flag");
this.flag = flag;
}

public String getName() {
System.out.println("调用getName name");
return name;
}

public void setName(String name) {
System.out.println("调用setName name");
this.name = name;
}

private String name;
private int age;
private Flag flag;
private boolean b;
private Map<String, String> map;
public int i;

public Map getMap() {
System.out.println("调用getMap map");
return map;
}

// public void setMap(Map map) {
// System.out.println("调用setMap map");
// this.map = map;
// }

@Override
public String toString() {
System.out.println("调用User toString");
return "User{" +
"name='" + name + '\'' + ", age=" + age +
", flag=" + flag + ", map=" + map + ", i=" + i +
'}';
}

public User() {
System.out.println("调用User无参构造");
this.name = "f19t";
this.age = 8;
this.i = 2;
this.map = new HashMap<String, String>();
map.put("1", "2");
this.flag = new Flag();
}

}

class Flag {
private String flag;

public String getFlag() {
System.out.println("调用getName flag类的flag");
return flag;
}

public void setFlag(String flag) {
System.out.println("调用setName flag类的flag");
this.flag = flag;
}

public Flag() {
System.out.println("调用flag class无参构造函数");
this.flag = "flag{123123}";
}
}

下面是Mian程序,可以根据自己的需要设置断点和设置注释

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

import com.alibaba.fastjson.JSON;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

/**
* author: f19t
* Date: 2023/7/17 13:03
* File: Main.java
*/

public class Main {
public static void main(String[] args) throws ClassNotFoundException {
User user = new User();
System.out.println("================");
String serJson = JSON.toJSONString(user);
System.out.println(serJson);
System.out.println("================");
System.out.println(JSON.parse(serJson).getClass());
System.out.println("================");
System.out.println(JSON.parseObject(serJson).getClass());
System.out.println("================");
System.out.println(JSON.parseObject(serJson, User.class).getClass());
System.out.println("================");
String sertest = "{\"@type\":\"org.fvuln.User\",\"age\":8,\"b\":false,\"flag\":{\"flag\":\"flag{123123}\"},\"i\":2,\"map\":{\"1\":\"2\"},\"name\":\"f19t\"}";
System.out.println(JSON.parse(sertest).getClass());
System.out.println("================");
System.out.println(JSON.parseObject(sertest).getClass());
System.out.println("================");
System.out.println(JSON.parseObject(sertest, User.class).getClass());
System.out.println("================");
}
}

Fastjson反序列化效果

这里我们先运行这三行

运行结果如下:

我们可以发现parseObject会调用全部get与set方法、parse只会调用全部set方法与部分get方法

下图是parseObject方法我们可以看到他是调用了parse,那么接下来我们重点分析一下parse这条调用路径。

把其他代码都注释掉只留下下面两句关键代码,打断点进行分析。

Json字符串处理

第一步、首先parse会调用自己的重载函数。进入重载函数后给关键代码下断点。

首先进入到

1
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);

这里需要注意:

  • this.symbolTable的值为[null, null, null, null, $ref, 其他 @type,091]
  • 大括号 “{}”: 在Fastjson中,大括号表示一个JSON对象的开始和结束。
  • 方括号 “[]”: 在Fastjson中,方括号表示一个JSON数组的开始和结束。

然后进入到

1
Object value = parser.parse();

进入到case12

走到关键代码

skipWhitespace()是去除回车换行等字符串的函数。

接着走到关键代码

1
key = lexer.scanSymbol(this.symbolTable, '"');

接着就是通过双引号去分割取值

这里取出第一个双引号包裹的字符串”@type”

接着会走到key是”@type”的if分支

这里得到ref为org.fvuln.User

这里可以看到存在一个mappings一个数组,用来判断是不是白名单

接着判断是不是[开头的、还判断是不是L开头;结尾的,如果是则缺[1,-1]区间的值进行类加载。

因为我们这里都不满足,所以会走到当前线程的的类加载器。并且将我们的class加入到mappings里面。

反序列化处理

接着会走到生成ObjectDeserializer对象这个位置

先是从derializers获取类加载,如果找不到的话会继续向下走。

最后会通过clazz.getName();获取类名,并将’$’转换成.

接着进行类黑名单校验。

接着会走到createJavaBeanDeserializer

JavaBeanInfo.build

接着会走到build

build是是获取目标类变量名field与函数setter、getter的地方,有些不同点记录在下方:

  • Methods获取方式为Method[] methods = clazz.getMethods(); 然后通过if判断是否添加到JavaBeanInfo
  • Field是通过methodName里面的字符串拼接得到的

添加getter与setter的主要三个for循环

添加setter

这里首先获取所有Methods,然后遍历循环这些Methods,然后经过一些if判断,取出了set开头的函数。

这里因为boolean类型的Field如果找不到的话,会通过拼接is+XXX方式。最后将确定的setter与变量名称添加到JavaBeanInfo里面。

添加public的变量

Field[] var31 = clazz.getFields();也就是说public的变量可以添加到JavaBeanInfo里面。


这个地方没调试,估计是直接反射赋值。

添加getter

getter函数的添加是这里的第三个for循环。

这里也是获取所有Methods,然后遍历循环这些Methods,然后经过一些if判断,取出了get开头的函数。

不过这里的if判断需要注意:

1
2
methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && methodName.startsWith("get") && Character.isUpperCase(methodName.charAt(3)) && method.getParameterTypes().length == 0 && 
(Collection.class.isAssignableFrom(method.getReturnType()) || Map.class.isAssignableFrom(method.getReturnType()) || AtomicBoolean.class == method.getReturnType() || AtomicInteger.class == method.getReturnType() || AtomicLong.class == method.getReturnType())

这里简要分析一下:

  • methodName.length() >= 4 应该是说geta()多一个字符串就大于4了
  • !Modifier.isStatic(method.getModifiers()) 不是是静态函数
  • Character.isUpperCase(methodName.charAt(3)) 第四个字母要大写
  • method.getParameterTypes().length == 0 函数没有参数
  • getReturnType()是判断是否集成这些某个类

我们最开始写的map例子就是为了验证这个地方。
进入到getter for循环有个坑,就是这个地方。

我们进入getField可以看到。

这个地方的逻辑顺序是先添加setter和public的一些变量,如果fieldList里面存在了与getter一样名称的变量,则不会添加getter,也就是说如果fieldList里面之前被setter和public变量添加过Field,那么就不会添加getter。

可以将我们最开始的基类,取消注释setter然后再运行,就会发现没有getter了。

最后可以获得包含JavaBeanInfo的deserializer了。

反序列化

接着跟进deserializer.deserialze()。

接着会创建对象

然后就进行fieldDeser.setValue(object, fieldValue);设置值了。

value的值通过lexer.scanxxx获取。

设置值的时候就会进行函数调用了。

写在最后

本篇文章讲了Fastjson1.2.24产生的漏洞原理,下一篇文件讲解各个版本漏洞产生的原因,以及POC和利用Fastjson漏洞的一些技巧。