小白都能看懂的JSON反序列化远程命令执行

前言

Fastjson是一个由阿里巴巴维护的一个json库。它采用一种“假定有序快速匹配”的算法,是号称Java中最快的json库。Fastjson接口简单易用,已经被广泛使用在缓存序列化、协议交互、Web输出、Android客户端等多种应用场景。今天我们就以最详细的姿势,一步步分析一下FastJson的远程命令执行!

0x01序列化

先熟悉一下FastJson的用法,毕竟连用法都不会怎么分析漏洞。下面用在最简单的示例快速入门一下FastJson

简单创建了一个实体bean,并set了两个属性值。进行简单的序列化,看看序列化后是不是变成了我们想要的东西。至于WriteClassName的作用,序列化时写入类型信息,默认为false。反序列化是需用到。

此时,已经非常完美的序列化成了我们常见的json数据。而加了WriteClassName属性的序列化,多了一个@type,也就是我们当时创建的那个实体对象。

0x02反序列化

反序列化的用法也比较简单,也就是将toJSONString换成parseObject即可。第一个参数是json字符串,第二个参数就是前面说到的@type实体对象。

成功的将字符反序列化位了实体对象。

0x03静态分析

分析漏洞最好的方式就是看看他到底做了什么防御,从他的补丁入手。

从更新的补丁来看,官方增加了一个checkAutoType方法,看到check这个词大概就能想到这块估计是是做了一个黑名单。跟进这个方法

发现checkAutoType方法对denyList列表进行了遍历。跟进checkAutoType看一看。

public Class<?> checkAutoType(StringtypeName, Class<?> expectClass) {       if (typeName == null) {           return null;       }        final String className = typeName.replace('$', '.');        if (autoTypeSupport || expectClass != null) {           for (int i = 0; i < acceptList.length; ++i) {                String accept = acceptList[i];                if (className.startsWith(accept)){                    returnTypeUtils.loadClass(typeName, defaultClassLoader);                }           }            for (int i = 0; i < denyList.length; ++i) {                String deny = denyList[i];                if (className.startsWith(deny)){                    throw newJSONException("autoType is not support. " + typeName);                }           }       }        Class<?> clazz = TypeUtils.getClassFromMapping(typeName);       if (clazz == null) {           clazz = deserializers.findClass(typeName);       }        if (clazz != null) {           if (expectClass != null && !expectClass.isAssignableFrom(clazz)){                throw newJSONException("type not match. " + typeName + " -> " +expectClass.getName());           }            return clazz;       }        if (!autoTypeSupport) {           for (int i = 0; i < denyList.length; ++i) {               String deny = denyList[i];                if (className.startsWith(deny)){                    throw newJSONException("autoType is not support. " + typeName);                }           }           for (int i = 0; i < acceptList.length; ++i) {                String accept = acceptList[i];                if(className.startsWith(accept)) {                    clazz =TypeUtils.loadClass(typeName, defaultClassLoader);                     if (expectClass != null&& expectClass.isAssignableFrom(clazz)) {                        throw newJSONException("type not match. " + typeName + " -> " +expectClass.getName());                    }                    return clazz;                }           }       }        if (autoTypeSupport || expectClass != null) {           clazz = TypeUtils.loadClass(typeName, defaultClassLoader);       }        if (clazz != null) {            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger                    ||DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver                    ) {                throw newJSONException("autoType is not support. " + typeName);           }            if (expectClass != null) {                if(expectClass.isAssignableFrom(clazz)) {                    return clazz;                } else {                    throw newJSONException("type not match. " + typeName + " -> " +expectClass.getName());                }           }       }        if (!autoTypeSupport) {           throw new JSONException("autoType is not support. " +typeName);       }        return clazz;    }}

首先他会先判断expectClass是否为空如果为空的化,就会去检查denyList这个列表。看名字就知道是一个黑名单列表。看看这个列表里都有什么东西。

当我们引入的库是以列表中任何一个字段开头时就报throw newJSONException(“autoType is not support. “ + typeName);的异常。再看看网上的poc引入的库com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。看来当循环到com.sun时就抛出了异常。阻止了对恶意对象反序列化的执行。当然这是知道了网上流传的poc,利用前辈们的poc分析起来就轻松多了。

0x05构造poc

当然引入poc以前,再熟悉json和java的应用。

依旧是新建一个实体bean,但是现在要注意两个地方,一个是我设置了两个属性。二是我往无参构造器里写入了一条弹计算器的命令。接下来我们看看会发生什么。

神奇的地方发生了,当json反序列化时会自动调用无参构造器里的方法,导致计算器弹出。但是还有一点大家有没有注意到,我上面的json字符串明明有password=123456为什么没有反序列化出来。答案是因为我的PassWord字段设置的是私有属性,所以FastJson无权直接去反序列化私有字段。只是我们构造poc的一点java基础知识。

这已经能执行系统命令了,是不是把我们的实体bean直接传给服务器,服务器就可以让我们为所欲为了呢?当然不是的,因为这个只是我们自己构造的实体bean,只有在自己环境才能认识,除非将实体bean直接上传到服务器。那就有点扯淡了………

言归正传,现在的第一步就是学习java反序列化的思想,想尽办法在jdk和fastjson中,服务器肯定存在的代码中找我们想要的东西。这时就该引出前辈们的poc中的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类,那就跟进去看一看

getTransletInstance方法生成一个translet类的实例

_bytecodes字节数组又是translet类的实际类定义。这样我们是不是就以其他的方式代替了将恶意类上传到服务器这个不可取的方法了呢。

private void defineTransletClasses()       throws TransformerConfigurationException {        if (_bytecodes == null) {           ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);           throw new TransformerConfigurationException(err.toString());       }        TransletClassLoader loader = (TransletClassLoader)           AccessController.doPrivileged(new PrivilegedAction() {                public Object run() {                    return newTransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());                }           });       try {           final int classCount = _bytecodes.length;           _class = new Class[classCount];            if (classCount > 1) {                _auxClasses = new Hashtable();           }           for (int i = 0; i < classCount; i++) {                _class[i] =loader.defineClass(_bytecodes[i]);                final Class superClass =_class[i].getSuperclass();                 // Check if this is the mainclass                if(superClass.getName().equals(ABSTRACT_TRANSLET)) {                    _transletIndex = i;                }                else {                   _auxClasses.put(_class[i].getName(), _class[i]);                }           }            if (_transletIndex < 0) {                ErrorMsg err= newErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);                throw newTransformerConfigurationException(err.toString());           }       }       catch (ClassFormatError e) {           ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);           throw new TransformerConfigurationException(err.toString());       }       catch (LinkageError e) {           ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);           throw new TransformerConfigurationException(err.toString());       }    }

首先_bytecodes会传入getTransletInstance方法中的defineTransletClasses方法,defineTransletClasses方法会根据_bytecodes字节数组new一个_class,_bytecodes加载到_class中,最后根据_class,用newInstance生成一个java实例。

此时可以看到已经成功生成了我们的恶意代码com.screw.test.Demo.但是还要注意另一个标注点。强制类型转化为AbstractTranslet类,这是就知道为什么构造的恶意代码一定要继承AbstractTranslet类了。

0x04障碍解决

现在还有一个问题,怎么去触发getTransletInstance接下来会将大家带入一个好玩的调用链。感觉这个巧妙程度和当时的java反序列化有得一拼。

在newTransformer中触发了getTransletInstance方法,那问题又来了怎么触发newTransformer?

getOutputProperties()方法出场了,在return处调用了newTransformer方法。继续寻找getOutputProperties方法。

很快找到了这个outputProperties属性,只要调用他的get方法是不是就出发了getOutputProperties方法了呢?但是非常遗憾的是_outputPropertie属性前面有一个下划线,调用get方法是触发的是get OutputProperties方法,而且这个属性还是一个私有属性,不知道大家还记不记得我前面的实验,FastJson不能直接使用实体bean中的私有方法,没有达到我们的目标怎么办?

JavaBeanDeserializer中的smartMatch方法会将传入的key的_替换为空。

这张是动态调试的结果,很明显key2已经从_OutputProperties变成了OutputProperties。至此所有的阻碍已经去除。

0x05调用链

0x06最终POC

恶意类

poc

这块放出了完整的poc和恶意类可以结合调用链再回顾一下整个的反序列化过程!

总结:

FastJson虽然被广泛利用但是不知道大家有没有看到,Feature.SupportNonPublicField这个属性就是最后一个坑。他是在1.2.22版本才引入的,在1.2.25版本就被修复了。导致这个漏洞特别的稀少。虽然漏洞没有什么太大的利用价值,但是最重要的是我们学到了大佬的挖洞思路,我想这才是最有价值的东西。

原文发布于微信公众号 - FreeBuf(freebuf)

原文发表时间:2018-03-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿DD

Spring框架中的设计模式(二)

在 上一篇 中我们在Spring中所谈到的设计模式涉及到了创建模式三剑客和1个行为模式(解释器模式)。这次我们会将眼光更多地关注在具有结构性和行为性的设计模式上...

4098
来自专栏mini188

java中的锁

java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够。于是再次翻看了一下书里的内容,突然有点打开脑门的感觉...

3769
来自专栏黄Java的地盘

[翻译]WebSocket协议第二章——Conformance Requirements

本文为WebSocket协议的第二章,本文翻译的主要内容为WebSocket协议中相关术语的介绍。

871
来自专栏逍遥剑客的游戏开发

MPQ文件系统优化(续)

1705
来自专栏coolblog.xyz技术专栏

自己动手实现一个简单的JSON解析器

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。相对于另一种数据交换格式 XML,JSON 有着诸多优点。比如易读...

66419
来自专栏FreeBuf

浅析ReDoS的原理与实践

*本文原创作者:MyKings,本文属FreeBuf原创奖励计划,未经许可禁止转载 ReDoS(Regular expression Denial of Ser...

6425
来自专栏喔家ArchiSelf

从构造函数看线程安全

线程是编程中常用而且强大的手段,在使用过程中,我们经常面对的就是线程安全问题了。对于Java中常见的数据结构而言,一般的,ArrayList是非线程安全的,Ve...

1082
来自专栏程序员维他命

iOS 代码规范

花了一个月的时间结合几篇博客和书籍写了这套 iOS 代码规范(具体参考底部的参考文献部分)。这套代码规范除了有仅适用于 iOS 开发的部分,还有其他的比较通用性...

1912
来自专栏JAVA高级架构开发

Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码

美国时间 09 月 25 日,Oralce 正式发布了 Java 11,这是据 Java 8 以后支持的首个长期版本。

2130
来自专栏我杨某人的青春满是悔恨

走进 RxSwift 之观察者模式

RxSwift 是 ReactiveX 系列的 Swift 版本,如果你之前用过 ReactiveCocoa(RAC) 的话,想必对 Functional Re...

1852

扫码关注云+社区

领取腾讯云代金券