Fastjson 1.2.22-1.2.24 反序列化漏洞学习

fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴的工程师开发

Fastjson 1.2.22-1.2.24 反序列化漏洞

首先构建项目,我使用的是 Maven,在 pom.xml 里添加以下字段,并进行同步操作。为了方便分析,这里使用 Maven 将代码和文档也下载下来。

<dependencies>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
    </dependency>
</dependencies>

构造 Luna 类

public class Luna {
    private String name;
    private String position;

    public Luna() {
        System.out.println("Luna class constructor");
    }

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

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

    public String getPosition() {
        System.out.println("Luna getPosition()");
        return position;
    }

    public void setPosition(String position) {
        System.out.println("Luna setPosition()");
        this.position = position;
    }
}

使用 JSON.toJSONString 将其序列化数据输出

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

public class Ser {
    public static void main(String[] args) {
        Luna luna = new Luna();
        luna.setName("露娜一号");
        luna.setPosition("打野");
        String jsonString = JSON.toJSONString(luna, SerializerFeature.WriteClassName);
        System.out.println(jsonString);
    }
}

SerializerFeature.WriteClassNametoJSONString设置的一个属性值,设置之后在序列化的时候会多写入一个@type,即写上被序列化的类名,type可以指定反序列化的类,并且调用其getter/setter/is方法

当没添加 SerializerFeature.WriteClassName

接下来将他们解析,不含 WriteClassName

包含 WriteClassName

可以看到,第一个在不加上类型的时候不会触发构造方法、get/set 方法;而在第二个中 parse触发了 set 方法,parseObject 触发了 get/set 两个方法。那么如果在 get/set 中加入恶意操作呢?

将 Luna 类中的 setName() 作如下更改

public void setName(String name) {
    System.out.println("Luna setName() 此方法中有恶意操作");
    try {
        Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    this.name = name;
}

再将得到的序列化数据进行解析,成功触发命令执行

截屏2022-04-23 10.14.31

JSON.parse(jsonString) 下断点,进入调试,关键处

if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
    String typeName = lexer.scanSymbol(symbolTable, '"');
    Class<?> clazz = TypeUtils.loadClass(typeName, config.getDefaultClassLoader());

此处 typeName@type 中定义的 类,接着调用 TypeUtils.loadClass 加载类型

首先会在 mapping 中获取传入的 @type 这里找不到,于是使用 ClassLoader 加载类

try {
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

    if (contextClassLoader != null) {
        clazz = contextClassLoader.loadClass(className);
        mappings.put(className, clazz);

        return clazz;
    }
} catch (Throwable e) {
    // skip
}

调试如下

完成后进行如下操作:

ObjectDeserializer deserializer = config.getDeserializer(clazz);
return deserializer.deserialze(this, clazz, fieldName);

虽然 getDeserializer 有黑名单机制,但仅过滤了 Thread

继续跟进到 getContext() 后调用了 Luna 的方法

随后进入 setName 执行了定义好的命令

在调试过程中发现了 L[ 这样的检测字符,与后面的1.2.25 - 1.2.47各种绕过姿势有关。本文大量参考:[https://xz.aliyun.com/t/8979](