# FastJSON 反序列化漏洞

首先,在你的项目中引入 fastjson 的依赖。在 Maven 项目中,可以在 pom.xml 文件中添加以下依赖:

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

# fastJSON 原理流程

FastJson 对于 JSON 格式字符串、JSON 对象及 JavaBean 之间的相互转换

package Fsat;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Fastuser {
    public static void main(String[] args) {
    // JSON string
    String jsonStr = "{\"name\":\"John\",\"age\":30,\"city\":\"New York\"}";
    JSONObject obj = JSON.parseObject(jsonStr);// 将 JSON 字符串解析为 Java 对象
    System.out.println(obj);// 输出 Java 对象
        
    System.out.println(obj.getString("name"));// 输出 name 字段的值
    }
}

FsatJSON 还支持将 JSON 字符串解析为 javaBean 就是一种格式就是一个属性有属性具有 getter 和 setter 方法。

JavaBean 在 fastjson 中的使用

fastjson 可以方便地将 JavaBean 序列化为 JSON 字符串,以及将 JSON 字符串反序列化为 JavaBean。

package Fsat;
public class User {
    public String name;
    public int age;
    public User() {
        System.out.println("构造函数");
    }
    public String getName() {
        System.out.println("我是getName方法");
        return name;
    }
    public void setName(String name) {
        System.out.println("我是setName方法");
        this.name = name;
    }
    public int getAge() {
        System.out.println("我是getAge方法");
        return age;
    }
    public void setAge(int age) {
        System.out.println("我是setAge方法");
        this.age = age;
    }
}
package Fsat;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Fastuser {
    public static void main(String[] args) {
    // JSON string
        String jsonStr = "{\"name\":\"John\",\"age\":30}";
        // Java object
        User user = JSON.parseObject(jsonStr, User.class);// 将 JSON 字符串解析为 User 对象
        // 访问 Java 对象的属性
        System.out.println(user.getName());
    }
}
结果为:
    构造函数
我是setName方法
我是setAge方法
我是getName方法
John

他会去调用 set 给属性赋值,这个写法指定了是 User 类,但是 fastjson 是可以根据你指定的类让他去解析

由于 fastjson 可以根据 JSON 字符串中的 @type 字段来实例化对象

写法就是

package Fsat;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class Fastuser {
    public static void main(String[] args) {
    // JSON string
        String jsonStr = "{\"@type\":\"Fsat.User\",\"name\":\"John\",\"age\":30}";
        // Java object
       JSONObject user = JSON.parseObject(jsonStr);// 将 JSON 字符串解析为 User 对象
        // 访问 Java 对象的属性
        System.out.println(user);
    }
}
/* 结果
构造函数
我是 setName 方法
我是 setAge 方法
我是 getAge 方法
我是 getName 方法
{"name":"John","age":30}
他把我们传入的 User 类的构造函数 set,get 方法全部调用了一遍

@type 后面 Fsat.User 是我们指定的类,fastjson 就会根据这个类去解析,在这个过程里面他会把 json 字符串按照你传入的类进行解析赋值调用等,因为这个类是客户端可以控制的, fastjson 允许 JSON 数据指定反序列化后的类型,所以我们就可以通过构造包含恶意 @type 字段的 JSON 字符串,来加载并执行任意类中的代码,这个功能就导致的安全问题。

# 调试

image-20240708151954722

image-20240708152204649

这段代码用于将 JSON 字符串解析为 JSONObject 。它首先调用 parse 方法将 JSON 字符串解析为一个 Java 对象,然后检查该对象是否是 JSONObject 的实例,如果是则直接返回,否则调用 toJSON 方法将该对象转换为 JSONObject 并返回。

往下走走到这里他会用他会用 DefaultJSONParser 去对传进来的 JSON 字符串进行解析

image-20240708152903193

image-20240708162436468 再往下走核心代码就在 DefaultJSONParser 里面,解析第一步就是去字符串进行匹配

image-20240708162605944

这段代码是 fastjson 库中用于解析 JSON 数据的一部分。它通过检查 JSONLexer 的当前标记来决定如何处理输入,并将其解析为相应的 Java 对象

当前我们是左大括号标记是 LBRACE (表示对象的左大括号),则创建一个 JSONObject ,解析对象内容并返回。

image-20240708163028202

parseObject,反序列化的核心方法

# 逻辑步骤

  1. 初始化和验证
    • 获取 lexer (词法解析器)。
    • 如果当前的 lexer token 是 JSONToken.NULL ,则返回 null
    • 如果当前的 lexer token 是 JSONToken.RBRACE ,则返回传入的 object
  2. 处理 JSON 对象的开始
    • 如果 lexer 的 token 既不是 JSONToken.LBRACE 也不是 JSONToken.COMMA ,则抛出异常。
    • 初始化 ParseContext ,并设置一个标志 setContextFlagfalse
  3. 循环解析 JSON 对象中的每个键值对
    • 跳过空白字符。
    • 如果启用了 Feature.AllowArbitraryCommas ,则跳过连续的逗号。
    • 根据当前字符判断并解析键( key ),支持双引号、单引号、无引号、数字、嵌套对象或数组作为键。
    • 跳过键后的冒号,并再次跳过空白字符。
  4. 处理特殊键
    • 如果键为 @type 且未禁用 Feature.DisableSpecialKeyDetect ,则解析类型信息并根据类型创建对象。
    • 如果键为 $ref 且未禁用 Feature.DisableSpecialKeyDetect ,则解析引用并返回引用的对象。
  5. 设置解析上下文
    • 如果尚未设置上下文,则设置当前解析上下文。
  6. 解析值
    • 根据键后的字符判断并解析值( value ),支持字符串、数字、数组、嵌套对象等各种类型的值。
    • 将解析的键值对添加到 object 中。
  7. 处理对象结束
    • 如果遇到右花括号 } ,则结束对象的解析并返回 object
    • 如果遇到逗号 , ,则继续解析下一个键值对。
    • 如果遇到其他字符,则抛出异常。

走到这个 if 判断

image-20240709101711216

检查键和配置项

  • key == JSON.DEFAULT_TYPE_KEY 检查当前键是否为 @type
  • !lexer.isEnabled(Feature.DisableSpecialKeyDetect) 检查是否启用了特殊键检测功能。如果禁用了,则跳过后续处理。

扫描类型名称

  • String typeName = lexer.scanSymbol(symbolTable, '"'); 从 JSON 中读取 @type 的值,即类型名称。类型名称是用双引号括起来的字符串。

加载类

  • Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader()); 根据类型名称加载对应的 Java 类。使用 TypeUtils.loadClass 方法,通过类加载器 config.getDefaultClassLoader() 来加载类。

跟到 TypeUtils.loadClass

image-20240709102403960

这段代码定义了一个 loadClass 方法,用于根据类名和类加载器加载类。它首先检查缓存中是否已有该类,如果没有则尝试从给定的类加载器、当前线程的上下文类加载器或系统类加载器中加载类。具体逻辑如下:

  1. 参数检查

    • 如果类名为空或长度为零,则返回 null
  2. 缓存检查

    • mappings 缓存中获取类名对应的类,如果存在则直接返回。
  3. 处理数组类型

    • 如果类名以 [ 开头,表示是数组类型,递归调用 loadClass 方法加载组件类型,并返回该组件类型的数组类。
  4. 处理对象类型

    • 如果类名以 L 开头并以 ; 结尾,表示是对象类型,去掉 L; 后递归调用 loadClass 方法加载类。
  5. 尝试使用给定的类加载器加载类

    • 如果提供了类加载器,则尝试使用它加载类,并将加载的类缓存到 mappings 中。
  6. 尝试使用当前线程的上下文类加载器加载类

    • 如果类加载器加载失败,则尝试使用当前线程的上下文类加载器加载类,并将加载的类缓存到 mappings 中。
  7. 尝试使用系统类加载器加载类

    • 如果上下文类加载器加载失败,则尝试使用系统类加载器加载类,并将加载的类缓存到 mappings 中。
  8. 返回加载的类或 null

    • 如果所有尝试都失败,则返回 null

    后面的绕过黑名单就因为去除了 L 和;

    image-20240709102540344

解析器解析完后下一步,走到这里回来后才是我们的 User 类,前面还是对字符串操作

image-20240709103243947

从配置中获取特定类的反序列化器,并使用它进行反序列化。具体逻辑如下:

  1. 获取反序列化器
    • 调用 config.getDeserializer(clazz) 方法,根据类 clazz 从配置 config 中获取对应的 ObjectDeserializer 反序列化器。
  2. 使用反序列化器进行反序列化
    • 调用反序列化器的 deserialze 方法,传入当前解析器 this 、类 clazz 和字段名 fieldName 进行反序列化,返回反序列化后的对象。

# 1.2.24 利用

FastJSON 和原生反序列化区别

不要需要实现 Serializable,不需要 readObject 而是 setter getter,变量不需要 transient

相同点 sink 反射,动态类加载