本文为网络转载文章。文章内观点不代表本博客立场。本转载仅用于技术学习与交流。
去年FastJson的严重漏洞
这要从去年6月份的一个高级漏洞说起,阿里云监测到FastJson存在0day漏洞,攻击者可以利用该漏洞绕过黑名单策略进行远程代码执行。虽然具体来复现这个漏洞笔者没有进行深入研究,不过看上去就很严重的样子,可以远程让服务端执行指定的代码,就意味着服务端都不再安全了,更别提我们处心积虑地各种加密的客户端了,什么加密混淆加固这一切努力都将白费。就像一个没穿衣服的人,你戴再多帽子那也是徒劳。
具体漏洞如下,相信大家肯定也看到过类似的帖子,或者被公司的同事提醒过:
关于这次漏洞,开发者需要做什么
这一次漏洞覆盖面太广了,但是阿里官方当然不会坐视不管,自己的各种项目里都用着FastJson,任由病毒肆意蔓延,最后的损失将不可估量。于是在发现这个漏洞的第一时间FastJson官方就发出了解决方案,强烈建议大家升级FastJson到1.2.58版本以上。笔者也被公司的运维同学通知到了该消息,于是第一时间对该问题进行了处理,当时还小嘚瑟了一下,感觉没有比单纯升级一个sdk,改改版本号更简单的需求了。我当时上Github上看了下FastJson的最新版本是1.2.60,于是就修改了项目中build.gradle中的FastJson版本号为1.2.60。
解决这次漏洞,仅仅是改版本号这么简单吗?
升级以后,准备上传代码的时候我多留了一个心眼。按理说不会有什么问题,但是小心驶得万年船,心里想着既然是官方紧急发布的版本,是不是在修复了这个漏洞的同时,会同时带来其他的问题呢?我首先随便点了几个使用了FastJson进行解析的页面,并没有发现问题。但是问题经常就出现在特殊情况下,特别是第三方SDK对于自己就像个黑盒的时候。于是我习惯性地进行了一些健壮性测试,这是其中的一个测试案例,具体测试代码如下:
String jsonStr = "";
JSONObject.parseObject(jsonStr,JavaBean.class);
果不其然,当需要解析的字符串是空字符串时,将会发生crash,具体的错误堆栈信息如下:
Caused by: com.alibaba.fastjson.JSONException: syntax error,except start with { or [,but actually start with EOF
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:688)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:383)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:287)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:560)
从报错信息来看,像是传空字符串时,FastJson并没有进行健壮性处理。我第一反应是看一下升级以前的版本是不是也有相同的问题,于是我回退代码再跑了一遍,发现并没有问题。这只能说明被我不幸言中了,正是1.2.60这版本新出现的问题。因为笔者项目里有很多地方都是通过传参的方式传入json字符串,然后用FastJson进行解析的,难保有些地方由于网络不好,没有请求到Json数据。或者是页面间传参丢失等,出现解析空字符串的情况,而且具体哪些地方可能出现也不好排查。幸好在上线前发现了这一问题,既然之前版本存在重大漏洞,然后最新版本又不能用。所以项目里最终是用Gson来代替了FastJson上线,避免了可能存在的crash问题。
从源码角度分析Crash来源
那1.2.60版本为什么会报这个crash呢,我们从源码的角度来看看吧。这部分代码很简单,首先从使用的地方看起吧,我们使用FastJson对Json字符串解析成Bean的时候大概都是这样子的:
JSONObject.parseObject(jsonStr,JavaBean.class);复制代码
继续点下去的话大概会经过几个重载方法,然后最后一个parseObject()方法大概是这样的:
public static <T> T parseObject(...){ //省略无用代码
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
T value = (T) parser.parseObject(clazz, null);
//省略无用代码
}
我们可以看到,FastJson是这样做的,首先初始化了一个DefaultJSONParser类,然后通过这个类的parseObject()方法来实现真正的逻辑,最后该方法的返回值就是最终我们需要的Bean对象。接下来再看看DefaultJSONParser类中的parseObject()方法吧,方法具体实现也很简单,我这里也贴出来,大致如下:
public static<T>T parseObject(...){
//省略无用代码
if (deserializer.getClass() == JavaBeanDeserializer.class) {
if (lexer.token()!= JSONToken.LBRACE && lexer.token()!=JSONToken.LBRACKET) {
throw new JSONException("syntax error,except start with { or [,but actually start with "+ lexer.tokenName());
}
return (T) ((JavaBeanDeserializer) deserializer).deserialze(this, type, fieldName, 0);
} else {
return (T) deserializer.deserialze(this, type, fieldName);
}
//省略无用代码}
看到这里报错的位置就已经出来了,很明显如果lexer.token()不是JSONToken.LBRACE或者JSONToken.LBRACKET的时候,就会抛出前面遇到的异常,那么两个枚举是什么意思呢?
我们点到枚举类里去看一下,注释已经很清楚了,这两个枚举分别代表左大括号和左中括号。也就是说想要解析必须以左大括号和左中括号开始,否则就抛出我们看到的那个异常。那么答案就水落石出了,当json为空字符串的时候,明显不是以左大括号和左中括号开始的,所以就crash了。
之前版本是并没有这个问题的,那么在1.2.55版本这一块的代码是怎么处理的呢?同样点进去,我们发现在1.2.55里面并没有主动抛出这个异常:
而是在接着往下面解析的时候,将结果置为Null进行返回了。
所以使用1.2.55版本的FastJson的话,结果是返回空的对象。如果我们有在代码里对解析以后的对象进行判空的话,至少不会发生crash了。
截止到今天,FastJson是否已修复该问题
那截止到目前2020/6/11,FastJson是否修复了该问题呢?答案是肯定的,我们也来看下在最新的1.2.70版本里面FastJson源码是怎么处理的吧,代码非常简单:
我们可以看到,相比1.2.55,在1.2.70中是将json字符串的判空逻辑前置了,所以也不会有问题了。
总结
本文分享了一个项目中实际发生的FastJson踩坑实例,由一个例子也引申出了FastJson的部分简单源码。当然大家并不需要担心,因为在1.2.70里面已经修复了这个问题。代码里有准备使用1.2.60版本的FastJson的同学,或者是现在正在使用1.2.60版本的同学需要注意了,要尽量升级到1.2.70,防患于未然。
通过这次事件,有一个值得深思的事件似乎冒了出来,那就是我们不能完全相信SDK会做好健壮性处理。虽然阿里的开发工程师和测试工程师都很强大,但是也不可避免地有可能会出现一些披露。所以我们在升级SDK的时候,一定不能大意了,不要觉得改一个版本号就没事了。就算是改版本号也要跟项目组里的测试同学反映一下,做一下简单的回归测试。其次,自己也可以做一些极限条件下的模拟测试,来尽量减少上线以后可能出现的问题。
有句话说得很对,那就是上线前测出一百个问题,实际严重程度比不上线上出的一个问题。上线前出问题最多只是延长发版的时间,这是完全可以接受的,但是线上出问题了,作为非Hybrid开发的功能来说,又要经过漫长的bug修复、代码Review、回归测试、申请应用市场、用户重新下载安装才能算是成功修复了,而且出问题期间的损失又有多少?
在这个任何行业竞品App满天飞的时代,有多少用户会因为这个crash问题从而卸载App,转而投向竞品App的怀抱呢?
关注我获取更多知识或者投稿
作者:苹果味的少年
链接:https://juejin.im/post/5ee1f026f265da770e1bdb33
本网站文章均为原创内容,并可随意转载,但请标明本文链接
如有任何疑问可在文章底部留言。为了防止恶意评论,本博客现已开启留言审核功能。但是博主会在后台第一时间看到您的留言,并会在第一时间对您的留言进行回复!欢迎交流!
本文链接: https://leetcode.jp/android编程-fastjson是如何导致app-crash的/