又是fastjson!又是这家伙!至少经历了2次+这样的场景。我不知道这家伙又得罪了哪位大仙,频繁被“黑”。fastjson到底做错了什么?为什么会被频繁爆出漏洞?但是作为一个技术人(兴趣爱好者),我更关注的是它为什么会频繁被爆漏洞?而其他的Gson却没有。通过对fastjson的releaseNote以及部分源代码进行查阅,发现此现象跟fastjson中的一个AutoType特性有关联。
Java处理Json数据有三个比较流行的类库,Gson(google维护)、Jackson、以及今天的
主角Fastjson。
fastjson是阿里巴巴一个开源的json相关的java library,地址:https://github.com/alibaba/fastjson,Fastjson可以将java的对象转换成json的形式,也可以用来将json转换成Java对象,效率较高,被广泛的用在web服务以及android上,它的JSONString()方法可以将Java的对象转换成json格式,同样通过parseObject方法可以将json数据转换成java的对象。这个阿里巴巴的开源一个牛逼的Json解析库,通常被用于将Java Bean和Json字符串之间进行转换。
这次被工信部通报公司系统存在漏洞,导致了公司各业务线升级fastjson版本,以防止系统安全问题。对部分业务包进行查看,发现当前使用的fastjson版本不一,不过都是在1.2.68之前版本。2020年真是不平凡的一年,fastjson组件库频繁暴露安全漏洞,此漏洞可以绕过autoType开关来实现反序列化远程代码执行并获取服务器访问权限。
从2019年7月份发布的v1.2.59一直到2020年6月份发布的 v1.2.71 ,每个版本的升级中都有关于AutoType的升级,涉及13个正式版本。fastjson中与AutoType相关的版本历史可参考如下:
1.2.59发布,增强AutoType打开时的安全性 fastjson 1.2.60发布,增加了AutoType黑名单,修复拒绝服务安全问题 fastjson 1.2.61发布,增加AutoType安全黑名单 fastjson 1.2.62发布,增加AutoType黑名单、增强日期反序列化和JSONPath fastjson 1.2.66发布,Bug修复安全加固,并且做安全加固,补充了AutoType黑名单 fastjson 1.2.67发布,Bug修复安全加固,补充了AutoType黑名单 fastjson 1.2.68发布,支持GEOJSON,补充了AutoType黑名单 1.2.69发布,修复新发现高危AutoType开关绕过安全漏洞,补充了AutoType黑名单 1.2.70发布,提升兼容性,补充了AutoType黑名单 1.2.71发布,补充安全黑名单,无新增利用,预防性补充
关于1.2.68版本的漏洞中,其主要利用方式是利用异常进行攻击,并且重写getMessage方法。其实本质最广泛的是使用AutoCloseable这个接口绕过checkAutoType,网上大多也以这种方式展开分析。因为AutoCloseable构成的payload所需要的条件可能没有那么复杂。之前有文章中说1.2.69已经修复了异常攻击漏洞,但是经过测试似乎并没有。
然而,截止到2020年11月份发布的v1.2.75版本,依然没有对异常类进行处理,所以仍然存在漏洞,该漏洞的利用条件如下:
1、危险类必须继承“系统白名单”中任意一个异常类
2、危险类中的危险方法必须为构造方法或者setter方法,参数可控是最好的。
以fastjson1.2.68为例,当初产生的主要原因在于,fastjson为了再次避免用户用其AutoType机制进行反序列化而产生漏洞,加入了checkAutoType方法,AutoType机制的标志就是“@type”这个标签。checkAutoType这个方法本意是不想让用户使用AutoType,除非用户自己开启AutoType。然而在checkAutoType方法实现过程中还是没忍住偷偷用了几下(标志是解析了"@type"标志),但加强了限制。在代码审计过程中,我发现,在com.alibaba.fastjson.parser.ParserConfig第1326行
clazz = TypeUtils.getClassFromMapping(typeName);
其中typeName变量是用户输入的序列化之后的JSON格式数据中,含有类名的字符串,比如{"@type","com.demo.test"}这个json数据,其typeName就是com.demo.test。这行代码的意思就是从TypeUtils这个类中尝试获取json中提到的类。在TypeUtils的静态方法中,执行了addBaseClassMappings函数,其函数代码如下(注意其中含有AutoCloseable接口):
private static void addBaseClassMappings(){ mappings.put("byte", byte.class); mappings.put("short", short.class); mappings.put("int", int.class); mappings.put("long", long.class); mappings.put("float", float.class); mappings.put("double", double.class); mappings.put("boolean", boolean.class); mappings.put("char", char.class); mappings.put("[byte", byte[].class); mappings.put("[short", short[].class); mappings.put("[int", int[].class); mappings.put("[long", long[].class); mappings.put("[float", float[].class); mappings.put("[double", double[].class); mappings.put("[boolean", boolean[].class); mappings.put("[char", char[].class); mappings.put("[B", byte[].class); mappings.put("[S", short[].class); mappings.put("[I", int[].class); mappings.put("[J", long[].class); mappings.put("[F", float[].class); mappings.put("[D", double[].class); mappings.put("[C", char[].class); mappings.put("[Z", boolean[].class); Class<?>[] classes = new Class[]{ Object.class, java.lang.Cloneable.class, loadClass("java.lang.AutoCloseable"), java.lang.Exception.class, java.lang.RuntimeException.class, java.lang.IllegalAccessError.class, java.lang.IllegalAccessException.class, java.lang.IllegalArgumentException.class, java.lang.IllegalMonitorStateException.class, java.lang.IllegalStateException.class, java.lang.IllegalThreadStateException.class, java.lang.IndexOutOfBoundsException.class, java.lang.InstantiationError.class, java.lang.InstantiationException.class, java.lang.InternalError.class, java.lang.InterruptedException.class, java.lang.LinkageError.class, java.lang.NegativeArraySizeException.class, java.lang.NoClassDefFoundError.class, java.lang.NoSuchFieldError.class, java.lang.NoSuchFieldException.class, java.lang.NoSuchMethodError.class, java.lang.NoSuchMethodException.class, java.lang.NullPointerException.class, java.lang.NumberFormatException.class, java.lang.OutOfMemoryError.class, java.lang.SecurityException.class, java.lang.StackOverflowError.class, java.lang.StringIndexOutOfBoundsException.class, java.lang.TypeNotPresentException.class, java.lang.VerifyError.class, java.lang.StackTraceElement.class, java.util.HashMap.class, java.util.Hashtable.class, java.util.TreeMap.class, java.util.IdentityHashMap.class, java.util.WeakHashMap.class, java.util.LinkedHashMap.class, java.util.HashSet.class, java.util.LinkedHashSet.class, java.util.TreeSet.class, java.util.ArrayList.class, java.util.concurrent.TimeUnit.class, java.util.concurrent.ConcurrentHashMap.class, java.util.concurrent.atomic.AtomicInteger.class, java.util.concurrent.atomic.AtomicLong.class, java.util.Collections.EMPTY_MAP.getClass(), java.lang.Boolean.class, java.lang.Character.class, java.lang.Byte.class, java.lang.Short.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Float.class, java.lang.Double.class, java.lang.Number.class, java.lang.String.class, java.math.BigDecimal.class, java.math.BigInteger.class, java.util.BitSet.class, java.util.Calendar.class, java.util.Date.class, java.util.Locale.class, java.util.UUID.class, java.sql.Time.class, java.sql.Date.class, java.sql.Timestamp.class, java.text.SimpleDateFormat.class, com.alibaba.fastjson.JSONObject.class, com.alibaba.fastjson.JSONPObject.class, com.alibaba.fastjson.JSONArray.class, }; for(Class clazz : classes){ if(clazz == null){ continue; } mappings.put(clazz.getName(), clazz); } }
可以发现是系统将这些类加入了mappings中,之前提到的方法显而易见了,即尝试在TypeUtils类中获取typeName值,如果没有获取到,那么clazz为null,若expectClass此时也为null,就会触发异常,告诉用户AutoType不可用。正常情况下AutoType就是被禁止了。然而还有另一种情况,若clazz不为空,则就存在绕过限制的可能,绕过的关键就是TypeUtils.mappings中所包含的类,它们就相当于是“系统的白名单”。
更详细的:
1、fastjson会首先从左至右寻找JSON格式中带“@type”的键,若存在,则将其键对应的值(即typeName)通过checkAutoType方法检查是否在"系统的白名单中"和用户自定义的白名单中,当然系统还有自带的黑名单,typeName还不能在黑名单中。
以下为1.2.68系统设定的黑名单:
if (expectClass == null) { expectClassFlag = false; } else { if (expectClass == Object.class || expectClass == Serializable.class || expectClass == Cloneable.class || expectClass == Closeable.class || expectClass == EventListener.class || expectClass == Iterable.class || expectClass == Collection.class ) { expectClassFlag = false; } else { expectClassFlag = true; } }
2、在确定typeName不在黑名单而又在白名单后,会将typeName赋值为expectClass,expectClass可是个好东西啊,相当于一个强大的通行证,他会允许expectClass的儿子们(实现类或子类)不需要通过系统白名单检查直接通过。1.2.68漏洞也因这个“通行证”而产生。
关于3者Json框架的特性的相关情况,简要如下:
Fastjson
1、速度快 fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能一直高于其他类型库 2、使用简单 fastjson的API十分简洁。 3、使用广泛 fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。
Jackson
1、容易使用 - jackson API提供了一个高层次外观,以简化常用的用例。 2、无需创建映射 - API提供了默认的映射大部分对象序列化。 3、性能高 - 快速,低内存占用,适合大型对象图表或系统。 4、干净的JSON - jackson创建一个干净和紧凑的JSON结果,这是让人很容易阅读。 5、不依赖 - 库不需要任何其他的库,除了JDK。
Gson
1、输出轻量易读的JSON。 2、支持任意复杂的对象。 3、允许自定义对象的表现形式。 4、允许预先存在的不可变的对象转换为JSON或与之相反。 5、提供一种机制,使得将Java对象转换为JSON或相反如使用toString()以及构造器(工厂方法)一样简单。
相比之下,其他的json框架,如Gson和Jackson,漏洞数量少很多,高危漏洞也比较少。并且,其他2种框架也在努力改进中,各自有各自的优势,从中远期来看,若fastjson漏洞一直无法修复或者避免,Gson未尝不是一个性价比更高的选择呢?