长亭百川云 - 文章详情

fastjson 反序列化漏洞 POC 分析

闻道解惑

78

2024-07-13

fastjson 是阿里巴巴开源的,使用Java语言编写的 JSON 解析库,项目地址是 https://github.com/alibaba/fastjson,以速度快、性能高著称,使用范围非常广。

在2017年3月15日,Fastjson官方主动爆出Fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。本文对这个漏洞的POC进行分析。

零、Fastjson反序列化的特点

先创建一个实体类User,其中包括:

  • public元素name

  • private元素age和它的setter函数

  • private元素prop和它的getter函数

  • private元素grade和它的getter函数

接下来使用JSON.parseObject(),用指定类型的方式将这个类反序列化出来。

运行结果是:

从结果上看,我们可以得出以下结论:

  • User 对象的无参构造函数被调用

  • public String name 被成功的反序列化

  • private int age 被成功的反序列化,它的 setter 函数被调用

  • private Properties prop 被成功的反序列化,它的 getter 函数被调用

  • private String grade 没有被反序列化,仍然是默认值 null,getter 函数也没有被调用

前三点都自然,奇怪的是后两点。prop 和 grade 同样是 private 类型,同样提供了 getter 函数没有提供 setter 函数,为什么 prop 可以通过 getter 函数来反序列化,而 grade 却没有?

这涉及到 fastjson 的一个特殊处理。对于只有 getter 函数,没有 setter 函数的 private 元素,fastjson会按如下条件判断反序列化的时候是否调用其 getter 函数。

  • 函数名称大于等于 4

  • 非静态函数

  • 函数名称以get起始,且第四个字符为大写字母

  • 函数没有入参

  • 函数的返回类型满足如下之一

  • 继承自Collection

  • 继承自Map

  • 是AtomicBoolean

  • 是AtomicInteger

  • 是AtomicLong

回到前面的问题。prop 的 getter 函数 getProp() 满足上面的条件,它的返回类型 Properties 继承自 Map ,因此可以成功的被调用。而 grade 的 getter 函数 getGrade() 的返回类型 String 不满足返回类型的那个条件,因此没有被调用,grade 也无法被赋值。

那么,在反序列化的时候,向 grade 这样无法通过 setter 或 getter 函数进行赋值的 private 元素,有什么方法可以赋值呢?有。就是使用 FEATURE.SupportNonPublicField

在 JSON.parseObject() 中使用 fastjson 的标签 FEATURE.SupportNonPublicField:

执行结果是:

grade已经成功被反序列化,此时 grade 的 getter 函数 getGrade() 并没有被调用。

FEATURE.SupportNonPublicField 从 fastjson 的 1.2.22 版本开始引入。

还有个疑问:上面的 App.java 中,json string 中使用 @type 标签指定了反序列化的目标类型为com.xiang.fastjson.poc.User,在调用 JSON.parseObject() 时也指定了目标类型是 User.class。那如果这两个类型不一致,会发生什么?

需要说明的是,只要 JSON.parseObject() 的第二个参数中指定的类,与 json string 中 @type 指定的类之间存在继承或转换关系,那么这个反序列化就会成功执行。比如把 JSON.parseObject() 的第二个参数设为 Object.class(也就是所有类的基类),那么反序列化就不会因为类型不匹配而失败。如果把第二个参数设为 String.class,那么所有支持 toString() 方法的类同样可以序列化成功。

但是如果两个类之间没有关联关系,那么反序列化的时候,是会直接返回错误拒绝反序列化,还是将对象反序列化完成再进行类型转换呢?

这个答案是:不确定。比如,我们修改App.java,将 JSON.parseObject() 的第二个参数换成 Integer.class,json string中仍然保持 com.xiang.fastjson.poc.User。

执行结果是:

从结果看,fastjson 先按照 json string 中的 @type 将对象反序列化出来,然后再转换为 JSON.parseObject() 中指定的目标类型。即便两个类型不一致,json string 指定的对象对应的 getter 和 setter 函数也一样会被调用。

再换一下,把 JSON.parseObject() 的第二个参数换成 ASMUtils.class。

执行结果是:

这一次的结果上,fastjson 却首先检查了两个类型是否匹配,不匹配直接抛出异常,没有调用各个字段的方法进行反序列化操作。

fastjson 内置了一些常用类的反序列化处理类,这些常用类的列表在 ParseConfig 中可以看到。

其中有一些处理类中(比如Integer、BigInteger等)没有检查类型是否匹配,就直接进行反序列化处理;还有一些没有进行检查,但是反序列化过程会因为语法错误而失败。而对于大多数不属于这些常用类的目标类型,fastjson 是会进行检查不同类型之间的关联关系的(如上面的ASMUtils)。

好吧,这一部分太晕了。有没有更简单一些的用法,不用考虑这些匹配原则?有,就是 JSON.parse()。

执行结果是:

同样也支持 Feature.SupportNonPublicField 设置。

执行结果是:

可以看到,JSON.parse(),完全是按照 json string 中指定的类进行反序列化,不用考虑指定目标类的情况,因此可能是更广泛的用法。不过少了一次类型检查,也会引入更多的安全风险。

一、TemplatesImpl POC分析

首先是 TemplatesImpl 的 POC,来自于 廖神(http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/)。

这个 POC 的入口点在 TemplatesImpl 类中 getOutputPerpeties() 函数。由于 TemplatesImpl 的 private 元素 _outputProperties 只有 getter 没有 setter ,同时其 getter 函数的返回类型 Properties 又继承自 Map,因此这个 getter 函数 getOutputProperties() 满足前面说的调用条件,可以在 fastjson 反序列化 TemplatesImpl 时被调用到 。POC 的调用链是:

getOutputProperties() -> newTransformer() -> getTransletInstance() -> defineTransletClasses() 

POC 代码如下:

执行之后成功弹出计算器:

上面的POC中,Exploit类继承自 AbstractTranslet。如果不想继承也可以,只要在 json string 中,_outputProperties 之前增加 _transletIndex 和 _auxClasses 的设置就好了。

执行之后同样弹出计算器:

TemplatesImpl 的 POC,在利用的时候有几个限制:

  1. 由于 POC 中关键元素 _bytecode 没有对应的 public 的 getter 和 setter 函数,因此需要服务器上解析 json 的时候,不管是使用 JSON.parse() 还是使用 JSON.parseObject(),都需要设置 Feature.SupportNonPublicField 。

  2. Feature.SupportNonPublicField 是在 1.2.22 版本引入,而这个漏洞在 1.2.25 版本就被封堵。这就要求目的服务器上的 fastjson 版本必须在 1.2.22 到 1.2.24 之间。

  3. 如果目的服务器使用的是 JSON.parseObject(),那么第二个参数必须是和TemplatesImpl不冲突的类,比如 Object.class,String.class,Integer.class 等。

二、JdbcRowSetImpl POC分析

JdbcRowSetImpl 的 POC 同样来自 廖神(http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/)。

JdbcRowSetImpl 的调用入口在 setAutoCommit() ,调用链很短: 

setAutoCommit() -> connect() -> InitialContext.lookup()

需要设置的属性只有 dataSourceName 和 autoCommit 两个。由于它们的 setter 函数 setDataSourceName() 和 setAutoCommit() 都是 public 类型,因此这里 不需要设置 SupportNonPublicField 属性 ,可以直接触发。

POC代码为

搭建好 RMI 的环境之后,执行 POC ,成功弹出计算器。

JdbcRowSetImpl 的 POC,使用限制为:

  1. fastjson 的版本范围为 1.2.24 及以下的所有版本。

  2. 如果目的服务器使用的是 JSON.parseObject(),那么第二个参数必须是和 JdbcRowSetImpl 不冲突的类,比如 Object.class,String.class,Integer.class 等。

三、fastjson的修复

反序列化漏洞的修补,通常都是通过白名单或黑名单的形式,禁止具有恶意功能的类进行反序列化。fastjson 使用的是黑名单的方式,具体而言就是从 1.2.25 版本开始,fastjson 增加了两个处理:

  1. autotype 功能默认关闭,同时提供手动 enable_autotype 的设置。

  2. 默认开启了黑名单,危险类所在的包不允许进行反序列化。

我们将 fastjson 升级到最新的 1.2.41 版本,再执行上面 JdbcRowSetImpl 的 POC,结果是直接抛了异常:

查看抛异常的地方,是被denyList拦截了。

查看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

  • org.springframework

com.sum.rowset.JdbcRowSetImpl 正好符合其中的 com.sun. 这个黑名单前缀,因此被屏蔽。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2