0x00 Hessian协议解析
Hessian是一个轻量级的Java反序列化框架,和Java原生的序列化对比,hessian更加高效并且非常适合二进制数据传输。
既然是一个序列化/反序列化框架,hessian也有反序列化命令执行的问题,这个在marshalsec工具中有所体现。这里有个有趣的点,在hessian框架中,ysoserial框架提供的gadget无法使用,你会发现在hessian生成序列化数据,即writeObject的时候就直接弹窗报错了,这是为什么呢?
以Groovy1这个gadget为例,我们看一下ysoserial的gadgets在hessian中的表现:
这段代码中生成了Groovy1这个对象,然后丢进hessian序列化和反序列化过程中,其实这里在writeObject的部分就触发了RCE,成了一个自己日自己的漏洞,为什么会这样呢?其实这和hessian的序列化实现有关系,看一下源码:
在hessian将对象进行序列化操作的时候,会提取对象的字段然后执行序列化操作,注意到这里直接调用了memberValues的get方法,即提前触发了ConvertedClosure的invoke方法,这导致了序列化的过程直接失败了。而ysoserial的这些gadgets基本都是基于原生Java反序列化过程进行构造的,hessian是一种不同于原生序列化的库,因此ysoserial的gadgets在这里就用不上。
0x01 Gadget原理分析
针对hessian反序列化过程进行攻击,就需要使用特殊的gadget,在marshalsec这个工具里,已经有了5个可用的Gadgets。分别是:
这里以Rome这个Gadget分析,代码如下:
(1)首先创建一个ToStringBean的对象item,设置其beanClass和obj属性为JdbcRowSetImpl
(2)然后创建了一个EqualsBean的对象root,将这个item放进去
(3)最后把root放在一个HashMap中,这里的JDKUtil.makeMap方法使用了反射动态创建数组,防止在构造gadget的时候触发put方法导致RCE。
ToStringBean对象中有个toString方法,会调用其beanClass字段的全部getter方法,这里设置为了JdbcRowSetImpl,这个类的一个getter叫getDatabaseMetaData会调用connect方法,即通过lookup方式触发RCE。
而EqualBean里有个hashCode方法,该方法通过hash(obj)进行触发,这个方法就调用了对象的toString()方法。
下面我们来看看是如何触发命令执行的,过程很有意思。hessian在处理map反序列化的时候,调用了MapDeserializer的readMap方法,这里会调用HashMap的put方法
put方法中会调用hash(key)计算对象hash,这里实际上就是调用的EqualBean的hashCode,进而调用了ToStringBean的toString方法,在ToStringBean.toString方法中调用了JdbcRowSetImpl的所有getter方法,其中当调用getDatabaseMetaData这个方法的时候触发lookup,从而完成JNDI触发RCE。
0x02 Dubbo Exploit
Dubbo是一个SOA框架,用于服务管理、注册、发现、调用等功能。下图就是dubbo的各个模块关系图:
可以理解为consumer通过RPC远程调用的方法调用provider上面的方法。既然是远程调用,就免不了使用对象序列化和反序列化,dubbo也不例外。在配置provider的时候可以选择几个不同的协议,对应着不同的序列化方式,默认情况支持dubbo协议,配置如下:
历史上,这个地方也出过漏洞,即从consumer端指定serialization=java的方法修改provider端的反序列化方式为java原生反序列化,这样就可以使用ysoserial里的gadgets去攻击dubbo了,但是这个漏洞在2.5.9版本被修复了。不过今天,我们是直接攻击hessian反序列化的过程。
攻击场景如下,provider和registry对外开放,恶意consumer通过本攻击方法可以在provider机器上执行任意命令。
复现代码如下:
直接手动构造好payload,通过调用远程方法的方式将对象传递过去即可触发了。
0x03 总结
marshal和unmarshal实际上是一种比传统序列化更加广泛的攻击面,因为它涉及了诸多对象保存和还原的方式,比如json、YAML、hessian等。并且每个序列化方式实现都不同,所以gadgets也比较灵活好找,可以预见2018年会出现更多的gadgets来拓宽该领域的攻击面。