引言
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函数,
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features)
很清晰,expectClass就是直接传给checkAutoType函数的,那么接下来我们需要去寻找,究竟哪些地方会给checkAutoType函数传expectClass,全局搜索一下,可以看到场景非常少,
1、JavaBeanDeserializer类的deserialze函数
2、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的限制嘛。不过新版本白名单已经被加密,需要爆破一下。
3、默认deserializers的buckets(在PaserConfig类的initDeserializers函数中初始化):可以把fastjson理解为一个编译器,它在进行初始化的时候也需要加载一些基础类型的对象,这些对象为了组件的正常工作是必须的。
所以我们需要的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的接口,恶意类难找,我们只聊原理,我就随便写了一个利用类。
package defaultpack;
import com.alibaba.fastjson.JSON;
import com.sun.rowset.JdbcRowSetImpl;
import org.h2.tools.SimpleResultSet;
import java.lang.reflect.Field;
public class Person implements AutoCloseable{
private String name;
private int age;
private String gender;
public Person() {
System.out.println("no-arg construct invoked!!!");
}
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
System.out.println("getAge invoked!!!");
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("setAge invoked!!!");
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void close() throws Exception {
}
}
利用的poc如下,
String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"defaultpack.Person\",\"age\":\"13\"}";
JSON.parseObject(payload);
成功利用的截图如下,可以看到在68版本autype默认为关的情况下,依旧成功调用了Person类的setAGe方法。:
利用场景分析
可以从上面的分析看出,虽然在原理层面上,的确存在着代码执行的风险,且绕过了autotype机制,但是这个利用受到了多方面限制:
1、黑名单限制
2、漏洞利用类必须拥有一个在autotype关着的情况下可以生成实例的父类(目前在68版本我已知的是Throwable和AutoCloseable)。
所以因为以上两点的限制,就可以发现利用是非常局限的,当然也不排除黑客大佬们思路广阔而笔者才疏学浅的可能性,因此我也仅在这里给出自己的看法。
*本文作者:平安银行应用安全团队@Glassy,转载请注明来自FreeBuf.COM