Fastjson 反序列化漏洞史

本文详细介绍了Fastjson从1.1.157到1.2.68版本的反序列化漏洞及其修复过程,包括autotype功能的开启与关闭、黑名单与白名单的演变。Fastjson在不同版本中对类名检测和安全策略的变更,以及如何利用DNSLog进行探测。文中提供了多个关键版本的测试示例和RCEPayload,展示了漏洞利用的原理和修复方法。
摘要由CSDN通过智能技术生成

作者:Longofo@知道创宇404实验室
时间:2020年4月27日
英文版本:https://paper.seebug.org/1193/
原文地址:https://paper.seebug.org/1192/
Fastjson没有cve编号,不太好查找时间线,一开始也不知道咋写,不过还是慢慢写出点东西,幸好fastjson开源以及有师傅们的一路辛勤记录。文中将给出与Fastjson漏洞相关的比较关键的更新以及漏洞时间线,会对一些比较经典的漏洞进行测试及修复说明,给出一些探测payload,rce payload。

Fastjson解析流程

可以参考下@Lucifaer师傅写的fastjson流程分析,这里不写了,再写篇幅就占用很大了。文中提到fastjson有使用ASM生成的字节码,由于实际使用中很多类都不是原生类,fastjson序列化/反序列化大多数类时都会用ASM处理,如果好奇想查看生成的字节码,可以用idea动态调试时保存字节文件:

在这里插入图片描述

插入的代码为:

BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = null;
String filePath = "F:/java/javaproject/fastjsonsrc/target/classes/" + packageName.replace(".","/") + "/";
try {
   
    File dir = new File(filePath);
    if (!dir.exists()) {
   
        dir.mkdirs();
    }
    file = new File(filePath + className + ".class");
    fos = new FileOutputStream(file);
    bos = new BufferedOutputStream(fos);
    bos.write(code);
} catch (Exception e) {
   
    e.printStackTrace();
} finally {
   
    if (bos != null) {
   
        try {
   
            bos.close();
        } catch (IOException e) {
   
            e.printStackTrace();
        }
    }
    if (fos != null) {
   
        try {
   
            fos.close();
        } catch (IOException e) {
   
            e.printStackTrace();
        }
    }
}

生成的类:

在这里插入图片描述

但是这个类并不能用于调试,因为fastjson中用ASM生成的代码没有linenumber、trace等用于调试的信息,所以不能调试。不过通过在Expression那个窗口重写部分代码,生成可用于调式的bytecode应该也是可行的(我没有测试,如果有时间和兴趣,可以看下ASM怎么生成可用于调试的字节码)。

Fastjson 样例测试

首先用多个版本测试下面这个例子:

//User.java
package com.longofo.test;

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 + '\'' +
                '}';
    }
}
package com.longofo.test;

import com.alibaba.fastjson.JSON;

public class Test1 {
   
    public static void main(String[] args) {
   
        //序列化
        String serializedStr = "{
   \"@type\":\"com.longofo.test.User\",\"name\":\"lala\",\"age\":11, \"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");
    }
}

说明:

这里的@type就是对应常说的autotype功能,简单理解为fastjson会自动将json的key:value值映射到@type对应的类中
样例User类的几个方法都是比较普通的方法,命名、返回值也都是常规的符合bean要求的写法,所以下面的样例测试有的特殊调用不会覆盖到,但是在漏洞分析中,可以看到一些特殊的情况
parse用了四种写法,四种写法都能造成危害(不过实际到底能不能利用,还得看版本和用户是否打开了某些配置开关,具体往后看)
样例测试都使用jdk8u102,代码都是拉的源码测,主要是用样例说明autotype的默认开启、checkautotype的出现、以及黑白名白名单从哪个版本开始出现的过程以及增强手段

1.1.157测试

这应该是最原始的版本了(tag最早是这个),结果:

serializedStr={
   "@type":"com.longofo.test.User","name":"lala","age":11, "flag": true,"sex":"boy","address":"china"}

-----------------------------------------------


JSON.parse(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
parse反序列化对象名称:com.longofo.test.User
parse反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

-----------------------------------------------

JSON.parseObject(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User getAge
call User isFlag
call User getName
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{
   "flag":true,"sex":"boy","name":"lala","age":11}

-----------------------------------------------

JSON.parseObject(serializedStr, Object.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
parseObject反序列化对象名称:com.longofo.test.User
parseObject反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

-----------------------------------------------

JSON.parseObject(serializedStr, User.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
parseObject反序列化对象名称:com.longofo.test.User
parseObject反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

-----------------------------------------------

下面对每个结果做一个简单的说明

JSON.parse(serializedStr)

JSON.parse(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
parse反序列化对象名称:com.longofo.test.User
parse反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName),最终结果是User类的一个实例,不过值得注意的是public sex被成功赋值了,private address没有成功赋值,不过在1.2.22, 1.1.54.android之后,增加了一个SupportNonPublicField特性,如果使用了这个特性,那么private address就算没有setter、getter也能成功赋值,这个特性也与后面的一个漏洞有关。注意默认构造方法、setter方法调用顺序,默认构造器在前,此时属性值还没有被赋值,所以即使默认构造器中存在危险方法,但是危害值还没有被传入,所以默认构造器按理来说不会成为漏洞利用方法,不过对于内部类那种,外部类先初始化了自己的某些属性值,但是内部类默认构造器使用了父类的属性的某些值,依然可能造成危害。

可以看出,从最原始的版本就开始有autotype功能了,并且autotype默认开启。同时ParserConfig类中还没有黑名单。

JSON.parseObject(serializedStr)

JSON.parseObject(serializedStr):
call User default Constructor
call User setName
call User setAge
call User setFlag
call User getAge
call User isFlag
call User getName
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:{
   "flag":true,"sex":"boy","name":"lala","age":11}

在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName)以及对应的getter方法(getAge,getName),最终结果是一个字符串。这里还多调用了getter(注意bool类型的是is开头的)方法,是因为parseObject在没有其他参数时,调用了JSON.toJSON(obj),后续会通过gettter方法获取obj属性值:

在这里插入图片描述
在这里插入图片描述

JSON.parseObject(serializedStr, Object.class)

JSON.parseObject(serializedStr, Object.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
parseObject反序列化对象名称:com.longofo.test.User
parseObject反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

在指定了@type的情况下,这种写法和第一种JSON.parse(serializedStr)写法其实没有区别的,从结果也能看出。

JSON.parseObject(serializedStr, User.class)

JSON.parseObject(serializedStr, User.class):
call User default Constructor
call User setName
call User setAge
call User setFlag
parseObject反序列化对象名称:com.longofo.test.User
parseObject反序列化:User{
   name='lala', age=11, flag=true, sex='boy', address='null'}

在指定了@type的情况下,自动调用了User类默认构造器,User类对应的setter方法(setAge,setName),最终结果是User类的一个实例。这种写法明确指定了目标对象必须是User类型,如果@type对应的类型不是User类型或其子类&#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值