fastjson 序列化 不包括转义字符_fastjson远程代码执行漏洞问题分析

背景

fastjson远程代码执行安全漏洞(以下简称RCE漏洞),最早是官方在2017年3月份发出的声明,

security_update_20170315(如果链接不生效,可以点击下面 了解更多 看原文)

没错,强如阿里这样的公司也会有漏洞。代码是人写的,有漏洞是难免的。关键是及时的修复。

a354bab0b38ca5534ca86f969fc4d3e9.png

声明中,官方指出:

最近发现fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,为了保证系统安全,请升级到1.2.28/1.2.29/1.2.30/1.2.31或者更新版本。

你会注意到这份声明中并没有对漏洞的细节有详细的描述,原因是官方担心透漏了细节会扩散漏洞。这个是可以理解的,警方公布案件的消息一般都不会透露犯罪细节,就是防止有人模仿犯罪。

公布漏洞之后,阿里进行了升级修复,并给出了防漏洞的建议。然后针对这一版的修复方案,黑客或者安全测试人又找到绕过防御的方案。于是在很长一段时间里,阿里的大佬们不断的修复漏洞,优化升级,黑客们不断的尝试新的攻击方法。整个就是一部fastjson RCE漏洞版的谍战剧。

19f083789d5da29d4941b1bd6a18d772.png

由于篇幅限制,本文并不打算详细介绍Fastjson RCE漏洞的进化史,而是只关注第一次漏洞分析。

漏洞分析

为了能重现漏洞,本文示例代码都是基于fastjson 1.2.23版本。

如果你是maven的工程,可以直接引入下面这个依赖。

            com.alibaba            fastjson            1.2.23        

fastjson基本使用

为了照顾有读者可能没有使用过fastjson,我们先来看看fastjson的基本用法。

首先我们定义一个实体类,

public class Student {    private String name;//姓名    private String number;//学号    private int age;  //年龄    private String secret;    public String getName() {        System.out.println("getName");        return name;    }    public void setName(String name) {        System.out.println("setName");        this.name = name;    }    public String getNumber() {        System.out.println("getNumber");        return number;    }    public void setNumber(String number) {        System.out.println("setNumber");        this.number = number;    }    public int getAge() {        System.out.println("getAge");        return age;    }    public void setAge(int age) {        System.out.println("setAge");        this.age = age;    }    public String getSecret() {        System.out.println("getSecret");        return secret;    }    public void setSecret(String secret) {        System.out.println("setSecret");        this.secret = secret;    }}

为了便于分析问题,我在gettter和setter方法中都加了打印日志。

然后我们看使用的示例,

eec970570bef6fc851761c7a4a394bb8.png

运行输出结果是,

jsonStr1:{"age":25,"name":"lucas","number":"001"}jsonStr2{"@type":"com.app.fastjson.Student","age":25,"name":"lucas","number":"001"}lucas25001

我们把上面的代码改下,使用jsonStr2进行反序列化的,

JSONObject jsonObject = JSON.parseObject(jsonStr2);

此时输出的结果是,

...jsonStr2{"@type":"com.app.fastjson.Student","age":25,"name":"lucas","number":"001"}setAgesetNamesetNumbergetAgegetNamegetNumbergetSecretlucas25001

序列化的结果一个带了类信息(使用SerializerFeature.WriteClassName可以输出类信息),一个没有带。这两个字符串都可以进行反序列成JsonObject对象,但是你应该已经注意到了,使用前者序列化的时候,调用了类对象的getter和setter方法。

因为fastjson在处理以@type形式传入的类的时候,会默认调用该类的共有setgetis函数。

请你记住这个结论,因为它正是漏洞的关键所在。

攻击

既然反序列化的时候会执行getter和setter方法,那我们是不是可以在这些方法里加入攻击的代码,然后fastjson返序列化的时候调用,不就达到了攻击的目的了吗。

我们先来构造一次攻击,等下再解释原理。

首先构造一段恶意代码,很简单就是在构造函数里打开本地的一个计算器应用。

a44e8d9c6e703d3d45d7505c6d2e2a7c.png

运行的效果如下图所示,

d1b12fb4dc6e45339499159ec967b5d5.png

我们把编译好的EvilCode.class文件留着备用。接着我们写一段poc证明漏洞的存在,

a5514dccc17dbe3601656493757457d4.png

运行这段代码,你会发现计算器的引用也会弹出来。这就相当于在反序列的时候执行了我们的恶意代码。

攻击原理分析

我们来分析下上面那段poc代码,首先是读入我们的EvilCodeclass文件,这里有一个小细节就是读入的class文件内容会被base64编码。为什么要这么处理呢?

答案其实也很简单,因为FastJson提取byte[]数组字段值时会进行Base64解码,这个大家看下源码就知道了,不是本文的重点这里不展开了。

接下来就是执行反序列了,我们的EvilCode的class文件是赋值给了TemplatesImpl的_bytecodes属性,_bytecodes却是私有属性,_name也是私有域,所以在parseObject的时候需要设置Feature.SupportNonPublicField,这样_bytecodes字段才会被反序列化。

前面提到过,反序列化的时候会调用成员变量的getter和setter方法,这其中就包括_outputProperties的getter方法,

1f0c19b4afd5fa9d28a51c7a80204e56.png
ae9b7e0a92d4bd5689379cefaed0232f.png
533ce612de8042b483b105755eee0b45.png
c1af7def9b0e56a9119475bc661b7b8b.png

不知道你看明白了没有。

在getTransletInstance调用defineTransletClasses,在defineTransletClasses方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance方法用到生成一个实例,也也就到了最终的执行命令的位置Runtime.getRuntime.exec()

总结起来,调用的链路是这样:

391a3a2c64dcafee0970f4cc443e24c7.png

解决方案

好了,到这里你已经知道了关于漏洞的细节。那么官方是如何解决这个漏洞的呢?

Fastjson官方在1.2.24版本后默认关闭autotype功能,并且启用黑名单功能。请用户确保该功能关闭。我们简单了解下解决方案的细节。

解决方案的关键是新增了一个checkAutoType方法,早期版本的方法实现如下,

public Class> checkAutoType(String typeName, Class> expectClass, int features) {        if (this.autoTypeSupport || expectClass != null) {            for(mask = 0; mask 

这个denyList你可以理解成黑名单,也就是这个名单里的类都不允许反序列化。如果上面我们用来攻击的TemplatesImpl被加入到这个黑名单自然就不能被恶意代码攻击了。早期的黑名单如下:

this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.apache.xalan,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");

后面可以不断的更新这个黑名单来抵御攻击(当然只是抵御针对黑名的攻击,其它攻击手段无法通过更新黑名单解决)。

其实机智如你应该能想到这里好像有个坑,就是黑名单公布出来,本来大家想不到的一些payload就可以被拿来攻击低版本的fastjson了。阿里应该是也意识到这个问题了,新版本的源码黑名单都是以hashCode的方式存放在源码里,如下所示,

ab5fe9cb28008a256f90f43c21e8262e.png

后来黑客界,安全界的大佬们还想出其它各种攻击手段,也不断的促进者fastjson原来越好,这里不表。

不过如果你使用场景中包括了这个功能,请参考:

enable_autotype (如果链接不生效,可以点击下面 了解更多 看原文)

这里如何添加白名单或者打开autotype功能。

总结

fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞。

开发中应严格控制AutoType开关,保持fastjson为最新版本。保持最新的版本这个通常要注意生产上使用稳定版本而不是beta版。


参考

  • http://xxlegend.com/2017/04/29/title-%20fastjson%20远程反序列化poc的构造和分析/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值