Fastjson反序列化分析

Fastjson反序列化分析

Fastjson是一个Java库,可以将Java对象转换位JSON格式,当然也可以将JSON字符串转换位Java对象

Fastjson特性:

pom.xml

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

Person.java

package com.test;

public class Person {
private String name;
private Integer age;

public String getName() {
return name;
}

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

public Integer getAge() {
return age;

}

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

测试类

public static void main(String[] args) {
Person person1 = new Person();
person1.setName("tom");
person1.setAge(18);

String str1 = JSONObject.toJSONString(person1);
System.out.println(str1);

Person person2 = new Person();
person2.setName("lisa");
person2.setAge(20);

String str2 = JSONObject.toJSONString(person2, SerializerFeature.WriteClassName);
System.out.println(str2);
}

我们来看下这两种不同方法的区别

输出:

可以发现第二种在反序列化中,还多打印了get、set方法的键和值

{"age":18,"name":"tom"}
{"@type":"com.test.Person","age":20,"name":"lisa"}

Autotype功能

允许用户在反序列化数据种通过”@type”指定反序列化的Class类型

特性:反序列化过程种会触发get方法、set方法

测试代码:在Person类的set和get方法上加上打印语句


将字符转换为对象的方法

String str3 = "{\"@type\":\"com.test.Person\",\"age\":20,\"name\":\"lisa\"}";
//第一种
Object obj = JSONObject.parse(str3);
System.out.println(obj);

//第二种
JSONObject obj2 = JSONObject.parseObject(str3);
System.out.println(obj2);

输出:

第一种方法将json字符串转为相应的对象,第二种是将json字符串转为相应的JSONObject对象

com.test.Person@2077d4de
{"name":"lisa","age":20}

区别:

我们看下parseObject方法,实际上是将对象进行强转,本质上还是调用了parse


我们在对象上新增get/set方法打印说明,看下get/set调用情况

package com.test;

public class Person {
private String name;
private Integer age;

public String getName() {
System.out.println("call getName Method");
return name;
}

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

public Integer getAge() {
System.out.println("call getAge Method");
return age;

}

public void setAge(Integer age) {
System.out.println("call setAge Method");
this.age = age;
}
}

使用parse进行反序列化

String str3 = "{\"@type\":\"com.test.Person\",\"age\":20,\"name\":\"lisa\"}";
Object obj = JSONObject.parse(str3);
System.out.println(obj);

输出

call setAge Method
call setName Method
com.test.Person@2077d4de

使用parseObject进行反序列化

输出

call setAge Method
call setName Method
call getAge Method
call getName Method
{"name":"lisa","age":20}

总结:


Fastjson反序列化流程分析

如果我们能找到一个类,在反序列化这个类的对象时,fastjson调用其中的setter或者getter方法来给它赋值,同时这个赋值方法存在漏洞,可以执行恶意代码,那么就可以远程代码执行

Person.java

package com.test;

import java.io.IOException;

public class Person {
private String name;
private Integer age;

public String getName() {
System.out.println("call getName Method");
return name;
}

public void setName(String name) throws IOException {
Runtime.getRuntime().exec("calc");
System.out.println("call setName Method");
this.name = name;
}

public Integer getAge() {
System.out.println("call getAge Method");
return age;

}

public void setAge(Integer age) {
System.out.println("call setAge Method");
this.age = age;
}
}

打个断点尝试跟进

调用了另外一个重载,继续跟进

发现下面方法重新调用,继续跟进

调用到了构造方法

ch赋值json字符串的第一个字符,如果判断为{则,laxer获取下一个字符以及lexer.token赋值给12

之后又走进parser.parse(),我们走进看下

继续跟进

可以看到这里用到了我们前面赋值的lexer.token(),我们前面赋值12,就会走进12的分支

我们可以看到第一行创建了一个接收object,第二行调用了parseObject方法

我们继续跟进第二行,前面做了一系列的判断,lexer.skipWhitespace做了空白字符过滤

我们看下如何实现的,单引号不等于反斜杠,则进入对空格换行等进行判断

判断为false,我们直接跳回,走进这个判断

走进scanSymbol方法,从英文单词了解是扫描一个符号的意思,传进来的参数是一个双引号,可以看到方法最后是返回一个@type,所以说可以看得出是取两个双引号的值

后面又对空白字符,获取字符,这里判断不是冒号,继续往下走

可以看到这里,获取下一个双引号,又调用了lexer.scanSymbol方法,又截取到com.test.Person

之后用TypeUtils.loadClass加载我们这个Person

之后往下获取生成了解析器,我们看下如何获取的解析器,走进该方法

走进这个方法

获取到className

对类名进行黑名单排查

判断是不是java.awt.开头,为false继续往下

判断是否以java.time.为开头,我们直接走到后面的else if判断也不成立,继续往下走

前面做了一些检查,走到这里开始获取了classLoader

derializer判断为true走进方法,返回了null

直到这里终于生成一个解析器

走进这个方法,我们前面快速步过,到这里走到了build,这个beanInfo保存了Person类里面的所有方法和一些变量以及构造方法

beanInfo保存了构造方法,可以通过beanInfo获取构造方法,通过反射生成对象

可以看到下面获取了构造器和字段,之后这是一个循环获取字段

最后返回给了derializer

我们执行返回,往下走

下面就是最关键地方,调用解析器去解析,我们进入这个方法

后面我们一直步过是看不见方法调试,因为后面是asm机制临时生成的代码在调试的时候是不可见的,直接继续往下调试,最后调用了set方法

asm机制:ASM是一个通用的Java字节码操作和分析框架。 它可以用于修改现有类或直接以二进制形式动态生成类

最终调用了set方法,弹出计算器

如果是使用parseObject,最后还会到下面这里

我们可以跟进这个

又调用下面的toJSON

往下走

之后走到这个

继续走到了get方法,这里其实就调用到了get字段的方法


调用栈


条件

满足条件的setter:

满足条件的getter:

上一篇

Fastjson TemplatesImpl利用链分析