引言
fastjson 68版本前一阵子有些闹得沸沸扬扬的,我这边也一直忙着写字节码扫描器没太用心关注,今天看到fastjson代码已经跟进到71版本了,所以对68和69版本的代码做了一下比对,看了一下修复代码,这里对68版本fastjson的RCE漏洞做一下原理以及利用场景的分析。
注:其实最初在5月10号的时候我这边就已经看到了68版本的一种利用方式,基于Throwable子类的利用方式,不过这种方式只是68版本利用方式中的一种。
原理
fastjson的漏洞跟进的比较多的情况下,可以明白一个基本的套路就是,不管绕过怎么样的风骚,修复代码大部分都在ParserConfig类的checkAutoType里面,所以就话不多说,把68版本和69版本的ParserConfig类做个比较,看到改动还是很少的。
基本上一看可以看出是和expectClass有关,所以在函数的上上下下看了一下这个变量,可以归并一下发现的信息:
1. expectClass不为代码中的那些类的时候可以将expectClassFlag设置为true
2. 当expectClassFlag为true的时候,即使fastjson的autoype为false我们也能加载期望类的实例。
OK但从上诉两点,我们就可以看到借由expectClass我们是可以绕过fastjson的autotype安全限制的,这个安全通告相符合,代表我们走在正确的方向上。
但是在阅读checkAutoType函数中expectClass的代码的时候,也发现了其中一些会限制Gadget的点:
1. 黑名单检测逻辑是放在loadClass之前的,也就是说我们的Gadget将依旧受到黑名单的限制。(代码太长,我就不贴了)
2. Gadget必须实现了expectClass接口(或是expectClass的子类),才能成功生成Gadget的实例。
上面便是阅读代码得到的关键性信息了,接下来就要思考一下expectClass的问题了,第一个问题,expectClass是从哪里来的,看一下checkAutoType函数:
java public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features)
很清晰,expectClass就是直接传给checkAutoType函数的,那么接下来我们需要去寻找,究竟哪些地方会给checkAutoType函数传expectClass,全局搜索一下,可以看到场景非常少:
JavaBeanDeserializer类的deserialze函数
ThrowableDeserializer类的deserialze函数
关于第二种场景,我这边5月10号就到了别的大佬的分析,有兴趣大家可以去看一下,我就不再赘述,这里着重讲第一种。(当然其实就原理而言完全是一回事。)
DefaultJSONParser调用parseObject,用@type生成的clazz实例会紧接着传入JavaBeanDeserializer类的deserialze方法的type参数中,并继续解析json数据,如果json数据中还有@type,则把@type的值作为typeName,type参数作为expectClass传入checkAutoType函数中。
那根据我们刚刚分析代码得出的结论,如果typeName刚好为expectClass的子类,那么接下来就能生成typename的对象,从而达成绕过autotype的目的。
但是,这里一定要注意一个特别重要的问题,由于默认autotype是关着的,那么我们又怎么样去用@type来生成传给deserialze的clazz呢,下面我就先给出目前fastjson在autotype关闭的情况下能生成的clazz的范围:
1. cache mapping:48版本以前fastjson允许用户通过{"@type":"java.lang.Class","val":"com.evilClass"}的形式向mapping里面添加恶意类,这样不需要依赖autotype可以直接从cahce的mapping里获取clazz,但是48版本已经修复该问题。当然cache mapping在刚fastjson启动的时候就已经放了不少clazz,加载的时候不受autotype限制。
2. 白名单:不赘述,白名单的目的就是不受autotype的限制嘛。不过新版本白名单已经被加密,需要爆破一下。
所以我们需要的clazz就是要从上诉列举的三种情况里面寻找到,与此同时保证它不再黑名单里面。
OK,全部的分析已经到位,我们来汇总一下,看看poc究竟要怎么写:
1. 首先要用@type来生成一个clazz(不受autotype影响的)。从而把这个clazz传入deserialze函数的exceptClass中,要保证clazz不能为以下类。(这个是68版本的,69版本又添加了三个。)
2. 紧跟在生成在第一个@type后面,再跟一个@type,它的value不能是黑名单里面的类,而且必须是第一个clazz的子类。
之后便能生成一个不受autotype影响的clazz,以达到利用效果。
关于漏洞的复现,为了方便起见,我就直接去拿了69版本被拉黑而68版本没被拉黑的AutoCloseable接口来构造poc了。
因为想要利用成功必须保证第二个clazz实现AutoCloseable的接口,恶意类难找,我们只聊原理,我就随便写了一个利用类。
java
利用的poc如下:
java
成功利用的截图如下,可以看到在68版本autype默认为关的情况下,依旧成功调用了Person类的setAGe方法:
利用场景分析
可以从上面的分析看出,虽然在原理层面上,的确存在着代码执行的风险,且绕过了autotype机制,但是这个利用受到了多方面限制:
黑名单限制
漏洞利用类必须拥有一个在autotype关着的情况下可以生成实例的父类(目前在68版本我已知的是Throwable和AutoCloseable)。
所以因为以上两点的限制,就可以发现利用是非常局限的,当然也不排除黑客大佬们思路广阔而笔者才疏学浅的可能性,因此我也仅在这里给出自己的看法。