漏洞分析 | Dubbo2.7.7反序列化漏洞绕过分析
北京时间2020-6-22日Apache官方发布了Dubbo 2.7.7版本,其中修复了一个严重的远程代码执行漏洞(CVE-2020-1948),这个漏洞是由腾讯安全玄武实验室的ruilin提交,该漏洞允许攻击者使用任意的服务名和方法名发送RPC请求,同时将恶意序列化参数作为有效载荷,当恶意序列化的参数被反序列化时将执行恶意代码。该漏洞与 CVE-2017-3241 RMI反序列化漏洞有点类似,都是在远程调用过程中通过方法参数传入恶意序列化对象,服务端在解析参数进行反序列化时触发。Dubbo Github Star数量32.8k,知名度不亚于fastjson,被大量企业使用,包括一些知名互联公司,漏洞影响十分广泛。
补丁分析
从补丁对比文件来看,在DecodeableRpcInvocation.java文件133-135行增加了对Method方法进行验证,如果验证不通过则抛出非法参数异常终止程序运行,核心代码代码如下:
if (!RpcUtils.isGenericCall(path, this.getMethodName()) && !RpcUtils.isEcho(path, this.getMethodName())){throw new IllegalArgumentException("Service not found:" + path + ", " + this.getMethodName());}
跟进isGenericCall和isEcho方法,发现验证逻辑十分简单,如果method等于$invoke、$invokeAsync或者$echo则返回true。不得不说此处站在开发角度思考是没问题的,非Dubbo自带service中的$invoke、$invokeAsync、$echo方法以外,其他函数名全部抛出异常,但是万万没想到RPC调用过程中方法名是用户可控的,所以攻击者可轻易的将method设置为其中任意一个方法来绕过此处限制。
public static boolean isGenericCall(String path, String method) {return "$invoke".equals(method) || "$invokeAsync".equals(method);}
public static boolean isEcho(String path, String method) {return "$echo".equals(method);}
通过对历史版本的回溯,发现在2019.10.31日的一次提交中DubboProtocol类的getInvoker函数的RemotingException代码块中增加了getInvocationWithoutData方法,对inv对象的arguments参数进行置空操作,用来缓解后反序列化攻击,此处正是CVE-2020-1948漏洞后反序列化利用的触发点。
如下getInvocationWithoutData函数,可能是为了方便开发者排查问题,如果系统配置log4j debug级别或者不配置任何其他级别,则不会将inv对象的arguments参数设置为null,会直接返回invocation对象,所以还是存在被后反序列化漏洞攻击的风险。所谓后反序列化简单理解就是漏洞是在对象被正常反序列化之后触发,比如在异常处理中对成功反序列化的对象进行间接或直接的函数调用,从而导致的代码执行。
/*** only log body in debugger mode for size & security consideration. * * @param invocation * @return */private Invocation getInvocationWithoutData(Invocation invocation) {if (logger.isDebugEnabled()) {return invocation; }if (invocation instanceof RpcInvocation){RpcInvocation rpcInvocation = (RpcInvocation) invocation;rpcInvocation.setArguments(null);return rpcInvocation;}return invocation; }
由上可知,DecodeableRpcInvocation#decode请求体解码函数的验证逻辑存在绕过DubboProtocol#getInvocationWithoutData函数的后反序列缓解存在无效情况。
构造POC
知道了method的验证逻辑,修改CVE-2020-1948 Poc中的的service_name和method_name参数的值,分别为:org.apache.dubbo.rpc.service.GenericService和$invoke。
在DecodeableRpcInvocation类中的decode函数方法起始处设置断点进行Debug。
代码123-124行首先通过path(对应客户端的service_name)参数来获取服务描述对象repository,该对象包含了服务名、接口类型和方法信息等。
继续跟进,由于params是我们构造Gadget, 最终repository对象获取到函数描述对象为null。
继续跟进,由于pts变量没有被赋值,所以pts== DubboCodec.EMPTY_CLASS_ARRAY表达式成立, 接着进入isGenericCall函数,由于rpc调用设置的method的值为$invoke,此处可以验证通过。
最后进入hession反序列化流程,成功执行了代码。
可以看到调用栈如下:
另外,Dubbo暴露的端口如果开启了Telnet协议,攻击者可以连接到服务通过ls命令查看service、method等信息,甚至可以执行shutdown危险操作直接关停服务。白帽子@CF_HB在Dubbo 2.6.8版本通过Telnet服务中InvokeTelnetHandler.java类在处理invoke命令时的漏洞结合Fastjson反序列化漏洞成功进行了利用。随着越来越多的安全研究人员对Dubbo安全问题的关注,相信后面会有更多的漏洞被挖掘出来。
漏洞的修复
2.漏洞发现者ruilin建议删除RpcInvocation类的toString方法中输出的arguments参数,防范后反序列化攻击。同时对Hessian进行黑白名单加固来防范Hessian反序列化攻击。
目前官方和社区给出的修复方法都是单点防御,很容易被攻击者绕过,短期防护可参考玄武实验室给出的方案:
• 出网限制
经研究当前存在的反序列化利用链大多需要远程加载恶意类,如果没有特殊需求,建议在不影响业务的情况下将服务器配置出外网限制。
• IP白名单
建议用户将能够连接至Dubbo服务端的消费端IP加入到可信IP白名单里,并在服务端配置可信IP白名单,以防止攻击者在外部直接发起连接请求。
• 更换默认的反序列化方式
Dubbo协议默认采用Hessian作为序列化反序列化方式,而Hessian存在危险的反序列化漏洞。用户可以在考虑不影响业务的情况下更换协议以及反序列化方式,如:rest,grpc,thrift等。
• 关闭公网端口
不要将Dubbo服务端的开放端口暴露在公网,但需要注意这种场景若攻击者在内网环境仍然可以进行攻击。
参考
Apache Dubbo Provider 远程代码执行漏洞 (CVE-2020-1948)
https://xlab.tencent.com/cn/2020/06/23/xlab-20-001/
Java“后反序列化漏洞”利用思路
[CVE-2020-1948] Apache Dubbo Provider default deserialization cause RCE
https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html
作者:freddychi(迟长峰)@腾讯安全云鼎实验室