Fastjsonfan反序列化(一)

时间:2022-11-24 15:12:56

Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。
Fastjson 可以操作任何 Java 对象,即使是一些预先存在的没有源码的对象(通过远程调用的方式)

//序列化
String text = JSON.toJSONString(obj); 
//反序列化
VO vo = JSON.parse(); //解析为JSONObject类型或者JSONArray类型
VO vo = JSON.parseObject("{...}"); //JSON文本解析成JSONObject类型
VO vo = JSON.parseObject("{...}", VO.class); //JSON文本解析成VO.class类

对象转字符串(序列化)

实体类User

package org.example;

public class User {
    public int id;
    public String name;
    public int age;

    public User() {
    }
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    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;
    }
}

定义一个测试类

package org.example;

import org.example.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1);
        user.setName("zhangsan");
        user.setAge(18);

        //对象转字符串
        String s1 = JSON.toJSONString(user);
        String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(s1);
        System.out.println(s2);
    }
}

输出结果

{"age":18,"id":1,"name":"zhangsan"}
{"@type":"org.example.User","age":18,"id":1,"name":"zhangsan"}

字符串转对象(反序列化)

package org.example;

import org.example.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class fastjsonTest {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1);
        user.setName("zhangsan");
        user.setAge(18);

        //对象转字符串
        String s1 = JSON.toJSONString(user);
        String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
        System.out.println(s1);
        System.out.println(s2);

        System.out.println("s1==========================s1");
        //字符串转对象
        Object o1 = JSON.parse(s1);
        JSONObject jo1 = JSON.parseObject(s1);
        User user1 = JSON.parseObject(s1, User.class);
        System.out.println(o1);
        System.out.println(jo1);
        System.out.println(user1);

        System.out.println("s2=========================s2");
        Object o2 = JSON.parse(s2);
        JSONObject jo2 = JSON.parseObject(s2);
        User user2 = JSON.parseObject(s2, User.class);
        System.out.println(o2);
        System.out.println(jo2);
        System.out.println(user2);
    }
}

输出结果

{"age":18,"id":1,"name":"zhangsan"}
{"@type":"org.example.User","age":18,"id":1,"name":"zhangsan"}
s1==========================s1
{"name":"zhangsan","id":1,"age":18}
{"name":"zhangsan","id":1,"age":18}
User{id=1, name='zhangsan', age=18}
s2=========================s2
User{id=1, name='zhangsan', age=18}
{"name":"zhangsan","id":1,"age":18}
User{id=1, name='zhangsan', age=18}

fastjson通过JSON.toJSONString()将对象转为字符串(序列化),当使用SerializerFeature.WriteClassName参数时会将对象的类名写入@type字段中,在重新转回对象时会根据@type来指定类,进而调用该类的setget方法。因为这个特性,我们可以指定@type为任意存在问题的类,造成一些问题

利用

漏洞是利用fastjson autotype在处理json对象的时候,未对@type字段进行完全的安全性验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机,通过其中的恶意类执行代码。攻击者通过这种方式可以实现远程代码执行漏洞的利用,获取服务器的敏感信息泄露,甚至可以利用此漏洞进一步对服务器数据进行修改,增加,删除等操作,对服务器造成巨大的影响。

set,get,is自动调用问题

构造Evil类

package org.example;

public class Evil {
    private String cmd;
    public Evil(){
        System.out.println("Evil()"+ this.hashCode());
    }

    public String getCmd() {
        System.out.println("getCmd()"+this.hashCode());
        return cmd;
    }

    public void setCmd(String cmd) {
        System.out.println("setCmd"+this.hashCode());
        this.cmd = cmd;
    }
}

写一个test测试下

package org.example;

import com.alibaba.fastjson.JSON;

public class Test {
    public static void main(String[] args) {
        String evil = "{\"@type\":\"org.example.Evil\",\"cmd\":\"calc\"}";
        JSON.parse(evil);
        System.out.println("-------------------");
        JSON.parseObject(evil);
        System.out.println("------------------");
        JSON.parseObject(evil, Object.class);
    }
}

Fastjsonfan反序列化(一)

跟进parseObject(evil)发现其方法发现也是使用parse解析的,但是多了一个(JSONObject)toJSON(obj)

这个方法调用的get,堆栈如下

getCmd:11, Evil (org.example)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
get:451, FieldInfo (com.alibaba.fastjson.util)
getPropertyValue:105, FieldSerializer (com.alibaba.fastjson.serializer)
getFieldValuesMap:439, JavaBeanSerializer (com.alibaba.fastjson.serializer)
toJSON:902, JSON (com.alibaba.fastjson)
toJSON:824, JSON (com.alibaba.fastjson)
parseObject:206, JSON (com.alibaba.fastjson)
main:13, Test (org.example)

那么setter在哪调用的?

com.alibaba.fastjson.util.JavaBeanInfo#build

Fastjsonfan反序列化(一)

在通过@type拿到类之后,通过反射拿到该类所有的方法存入methods,接下来遍历methods进而获取get、set方法,如上图。总结set方法自动调用的条件为:

  1. 方法名长度大于4
  2. 非静态方法
  3. 返回值为void或当前类
  4. 方法名以set开头
  5. 参数个数为1

当满足条件之后会从方法名截取属性名,截取时会判断_,如果是set_name会截取为name属性,具体逻辑如下:
Fastjsonfan反序列化(一)

当截取完但是找不到这个属性
Fastjsonfan反序列化(一)

会判断传入的第一个参数类型是否为布尔型,是的话就在截取完的变量前加上is,截取propertyName的第一个字符转大写和第二个字符,并且然后重新尝试获取属性字段。

比如:public boolean setBoy(boolean t) 会寻找isBoy字段。

set的整个判断就是:如果有setCmd()会绑定cmd属性,如果该类没有cmd属性会绑定isCmd属性。

get的判断
Fastjsonfan反序列化(一)

总结下就是:

  1. 方法名长度大于等于4
  2. 非静态方法
  3. 以get开头且第4个字母为大写
  4. 无传入参数
  5. 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

当程序绑定了对应的字段之后,如果传入json字符串的键值中存在这个值,就会去调用执行对应的setter、构造方法

小结:

  1. parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()
  2. parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()
  3. parseObject(jsonStr,Object.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()