Fastjson反序列化分析

Java安全

简介

FastJson是Java的一个库,它可以将Java对象转换成JSON字符串,也可以将JSON字符串转换成Java对象

回显Fastjson使用版本

在实战中如果不知道使用的是什么版本的Fastjson,可以将请求包中的 Content-Type 改成字段内容改成 application/json,请求体中让后端报错,比如 [{"a":"a\x]

FastJSON使用

package com.fastjson_study;

import com.alibaba.fastjson.*;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class f1 {
    public static void main(String[] args) {
        Person person = new Person();
        String s = JSON.toJSONString(person, SerializerFeature.WriteClassName); // 将JavaBean对象转换成JSON字符串,并写入类名,调用构造方法和所有getter方法
        System.out.println(s);
        // 构造方法被调用...
        // getAge方法被调用...
        // getName方法被调用...
        // {"@type":"com.fastjson_study.Person","age":19,"name":"xiaowang"}

        Object ObjectParse = JSON.parse(s); // 将JSON字符串转换成Object对象,会调用构造方法和所有的setter方法
        System.out.println(ObjectParse.getClass().getName()); // com.fastjson_study.Person
        // 构造方法被调用...
        // setAge方法被调用...
        // setName方法被调用...
        // com.fastjson_study.Person

        Person PersonParse = JSON.parseObject(s,Person.class); // 将JSON字符串转换成指定的对象类型,会调用
        System.out.println(PersonParse);
        // 构造方法被调用...
        // setAge方法被调用...
        // setName方法被调用...
        // Person{name='xiaowang', age=19}

    }
}
package com.fastjson_study;

public class Person {
    public String name = "xiaowang";
    public Integer age = 19;
    public Person(){
        System.out.println("构造方法被调用...");
    }

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

    public void setAge(Integer age) {
        System.out.println("setAge方法被调用...");
        this.age = age;
    }

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

    public Integer getAge() {
        System.out.println("getAge方法被调用...");
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

FastJSON提供了特殊字段@type,该字段可以指定反序列化任意类,会自动调用其类属性的特定的set、get方法。

在反序列化时,public修饰符的属性会被反序列化赋值,private修饰符的属性不会直接的进行反序列化赋值而是通过setXxx的方法进行赋值。

Fastjson反序列化流程分析

根据这一特性,我们可以自己写一个类来测试一下,这里我在Person2类中定义了私有的属性_name,所以 setget 方法都是小写加_

import com.alibaba.fastjson.JSON;

class Person2{
    private String _name;

    public Person2() {
        System.out.println("构造方法执行");
    }

    public void get_name() {
        System.out.println("getName执行");
    }

    public void set_name(String cmd) throws Exception{
        Runtime.getRuntime().exec(cmd);
    }
}

public class Test {
    public static void main(String[] args) {
        String pstr = "{\"@type\":\"FastjsonResearch.Person2\",\"_name\":\"/System/Applications/Calculator.app/Contents/MacOS/Calculator\"}";
        Person2 p = JSON.parseObject(pstr, Person2.class);
    }
}

image-20230718140453379

程序从这开始将json字符串给Fastjson去解析

image-20230718140605002

我们来到这里,parseObject 有好几个重写方法,目前我们先熟悉一下Fastjson反序列化整个流程,后面遇到再说。

这个 pareObject 调用的是另一个重写方法的 pareObject

image-20230718140829336

这里还是调用了另一个重写的 pareObject 方法,继续跟进

image-20230718141324445

DefaultJSONParser 将我们输入的json字符串和全局解析配置创建了一个默认JSON解析器。

image-20230718141501281

再往下走,这个默认JSON解析器调用了 pareObject 方法

image-20230718141547072

image-20230718141805321

这里在反序列化器中尝试获取我们写的Person2类的反序列化器,很显然是没有的。

随后调用了 getDeserializer 方法,再跟进看一下

image-20230718142250471

在这里就是获取我们的Person2这个类的全类名,有个替换操作,然后再判断我们的这个类名在不在黑名单(拒绝生成反序列化器列表)内,这个列表里只有一个 java.lang.Thread,当然也是不在的。

image-20230718142607591

再往下走,走到了调用 createJavaBeanDeserializer 方法这里,看似是要创建一个JavaBean的反序列化器,继续跟进看一下。

image-20230718143433340

image-20230718142716825

这里获取Person2类上的JSONType注解,我们没有使用该注解,所以返回null

image-20230718143631016

再继续往下跟,发现这里实例化了 JavaBeanDeserializer

image-20230718144105154

进入 JavaBeanDeserializer 类,发现调用自己的另一个重写的构造方法。

image-20230718144202484

这里的操作其实就是获取所有的 set 方法,由于我们这里只有一个 set 方法,就继续往下看了。

image-20230718144636850

此时, createJavaBeanDeserializer 方法已经执行完毕了,使用 putDeserializer 把Person2类作为键,获取的JavaBean反序列化器作为值存到 ParserConfig 中的 derializers 变量中,该变量是一个HashMap,随后返回了这个JavaBean反序列化器。

image-20230718145212500

这里也返回了反序列化器

image-20230718145246136

继续往下走,可以看到反序列化器调用了 deserialze 反序列化方法。

image-20230718145348722

image-20230718145412974

image-20230718145553700

走到这里条件符合,是默认的TYPE KEY(@type)

image-20230718145658496

这里判断token是否等于JSONToken.RBRACE,JSONToken.RBRACE也就是 } 符号,符合条件调用了 lexernextToken 方法,继续下一次循环。

image-20230718150131961

image-20230718150141928

此时,key是 _name 也就是json字符串中的键名,已经不是默认的TYPE KEY(@type)了。

image-20230718150750125

再往下走,到这里调用了 createInstance 方法,跟进看一下

image-20230718150839656

它获取了JavaBean反序列化器的默认构造方法并且实例化了,返回了一个对象。

image-20230718150934685

这里已经得到了一个Person2类的对象了,object 自然不是null

image-20230718151014211

再往下,调用了 parseField 方法,看这个名字应该是解析字段,跟进看一下。

image-20230718151130807

image-20230718151142468

image-20230718151227499

image-20230718151253009

这一连串的操作是处理字段中的 _- 符号,再通过 getFieldDeserializer 方法就获取对应的字段反序列化器了。

后面调用这个字段反序列化器的 parseField 方法,继续跟进

image-20230718151438130

可以看到这里调用了 setValue 方法,参数 object 正是我们的Person2实例化出来的对象,value 则是71行反序列化获取的 _name 键的值,看看 setValue 方法里面是在干嘛。

image-20230718151914151

这里判断了 fieldInfo 的method方法是不是只有一个get方法,很显然不是。

image-20230718151943466

再继续往下,好像有点意思了,这里是真正调用 set_name 方法的地方,调用method的invoke方法,method是 set_name 方法,那么正好,我们只需要在Person2类中定义一个对应字段的setter方法,其方法有一个参数即可。value 就是 set_name 方法需要传入的参数,也就是执行的命令。

image-20230718152143873

这里,就弹出了计算器了,说明触发点是在这里。

好了,现在我们已经理清楚了Fastjson整个反序列化流程,接下来我们需要配合一些JDK内的一些链来利用。

Author: wileysec

Permalink: https://wileysec.github.io/1f78acdc2202.html

Comments