CVE-2019-17564:Apache dubbo HTTP协议反序列化漏洞分析

Apache dubbo是一个是基于Java的高性能开源RPC框架。它支持dubbo,http,rmi,hessian等协议。本次问题出现在dubbo开启http协议后,会将消费者提交的 request 请求,在无安全校验的情况下直接交给了 spring-web.jar 进行处理,最终 request.getInputStream() 被反序列化,故存在反序列化漏洞。下面我们来调试分析代码。

0x02 影响范围

  • 2.7.0 <= Apache Dubbo <= 2.7.4
  • 2.6.0 <= Apache Dubbo <= 2.6.7
  • Apache Dubbo = 2.5.x

0x03 环境搭建

  • OS: Mac OSX
  • JDK: 1.8.0_191
  • Dubbo: 2.7.3

环境搭建这里我选择官方的 samples 中的 dubbo-samples-http

https://github.com/apache/dubbo-samples/

源码下载后将 pom.xml 中指定的dubbo版本修改为 2.7.3 ,同时加入 commons-collections4-4.0.jar 方便测试。

<properties>
    <source.level>1.8</source.level>
    <target.level>1.8</target.level>
    <!-- 修改dubbo版本为2.7.3 -->
    <dubbo.version>2.7.3</dubbo.version>
    <spring.version>4.3.16.RELEASE</spring.version>
    <junit.version>4.12</junit.version>
    ...
    <!-- 添加一个可用的Gadget到classpath -->
    <dependencies>
        ...
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>

同时dubbo依赖zookeeper,请自行安装!

0x04 漏洞分析

dubbo启用http协议后,所有的请求都会通过 org.apache.dubbo.rpc.protocol.http.HttpProtocol$InternalHandler 类的 handle 方法进行处理。我们在这打断点,并发送poc开始跟踪分析。

首先 handle 方法会获取请求路径,然后通过这个路径去 skeletonMap 里获取到该接口对应的处理对象,来处理当前 request 请求。

//org.apache.dubbo.rpc.protocol.http.HttpProtocol$InternalHandler
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    // 1. 获取请求的路径(/org.apache.dubbo.samples.http.api.DemoService)
    String uri = request.getRequestURI();
    // 2. 通过请求的接口路径获取对应的处理对象
    HttpInvokerServiceExporter skeleton = (HttpInvokerServiceExporter)HttpProtocol.this.skeletonMap.get(uri);
    if (!request.getMethod().equalsIgnoreCase("POST")) {
        response.setStatus(500);
    } else {
        RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
        try {
            // 3. 使用获取到的处理对象进行处理请求
            skeleton.handleRequest(request, response);
        } catch (Throwable var6) {
            throw new ServletException(var6);
        }
    }
}

第二步中存储接口地址和处理对象的 skeletonMap

处理对象是 HttpInvokerServiceExporter 类对象,它负责获取远程调用对象,并执行获取结果返回给客户端。跟进它的 handleRequest 方法, request 对象被传入 readRemoteInvocation 方法中来获取 RemoteInvocation 远程调用对象

//org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
    	// 1. 从request对象获取远程调用对象
        RemoteInvocation invocation = this.readRemoteInvocation(request);
        RemoteInvocationResult result = this.invokeAndCreateResult(invocation, this.getProxy());
        this.writeRemoteInvocationResult(request, response, result);
    } catch (ClassNotFoundException var5) {
        throw new NestedServletException("Class not found during deserialization", var5);
    }
}

readRemoteInvocation 方法将 request.getInputStream() (我们提交的序列化内容)传入 createObjectInputStream 方法,封装为一个 ObjectInputStream 。该对象又被传入 doReadRemoteInvocation 方法中,进行最终的获取操作。

//org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
protected RemoteInvocation readRemoteInvocation(HttpServletRequest request) throws IOException, ClassNotFoundException {
    // 1. 传入request对象和request.getInputStream()
    return this.readRemoteInvocation(request, request.getInputStream());
}

protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is) throws IOException, ClassNotFoundException {
    // 2. 将request.getInputStream()封装为ObjectInputStream
    ObjectInputStream ois = this.createObjectInputStream(this.decorateInputStream(request, is));

    RemoteInvocation var4;
    try {
        // 3. 获取RemoteInvocation远程调用对象
        var4 = this.doReadRemoteInvocation(ois);
    } finally {
        ois.close();
    }
    return var4;
}

doReadRemoteInvocation 方法中, ObjectInputStream 类对象 ois 直接被反序列化了。这个过程中没有进行任何过滤,导致我们传入的恶意序列化对象可以被反序列化创建,漏洞触发!

//org.springframework.remoting.rmi.org.springframework.remoting.rmi.RemoteInvocationSerializingExporter
protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    // 1. 恶意对象在此被反序列化,漏洞触发
    Object obj = ois.readObject();
    if (!(obj instanceof RemoteInvocation)) {
        throw new RemoteException("Deserialized object needs to be assignable to type [" + RemoteInvocation.class.getName() + "]: " + ClassUtils.getDescriptiveType(obj));
    } else {
        return (RemoteInvocation)obj;
    }
}

0x05 漏洞修复

漏洞出现的原因 dubbo HTTP接口将携带有恶意反序列化数据的 request ,在无安全校验的情况下直接交给了 spring-web.jarHttpInvokerServiceExporter 进行处理,导致存在反序列化漏洞。按理说这个漏洞不仅仅只是 dubbo 自身的问题,还是 spring 的问题。

在2.7.4.1版本开始,dubbo处理HTTP接口的调用请求交给了 jsonrpc4j.jarJsonRpcServer 去处理了。

跟踪分析 JsonRpcServer 类的 handle 方法后, request.getInputStream() 没有再被反序列化了。所以原来的利用方法失效了。

0x06 漏洞总结

该漏洞利用虽然简单粗暴,但在黑盒情况下利用难点有两个,一是我们无法得知web服务是否是dubbo http接口。二是如何获取接口路径,该路径可以在服务器上的 zookeeperhttp-provider 配置文件中找到,如果不配合其他漏洞是不容易获取的。

0x07 参考文章

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章