补个漏洞不容易—Dubbo远程命令执行漏洞修复始末
“纵观中国开源历史,你真的没法找到第二个像Dubbo一样自带争议和讨论热度的开源项目。”Dubbo的开发人员如是评论自己的“孩子”:Dubbo是一个优秀的RPC框架,是Java项目中卓越的框架之一。同所有的优秀服务提供框架一样,Dubbo也面临的安全问题。从本文从Dubbo今年6月遭遇的远程代码执行漏洞的入手,介绍Dubbo发现漏洞、修复漏洞、被绕过、继续修复的过程,期间涵盖了Dubbo漏洞原理和漏洞利用方式,希望可以借助Dubbo修复漏洞事件为例,来提供企业对重用应用框架安全的关注和重视程度。
01
技术背景
02
遭遇漏洞
1.Dubbo远程命令执行漏洞
2020年6月22日Apache官方发布了Dubbo 2.7.7版本,其中修复了一个严重的远程代码执行漏洞(CVE-2020-1948),该漏洞允许攻击者使用任意的服务名和方法名发送RPC请求,同时将恶意序列化参数作为Payload,当恶意序列化的参数被反序列化时将执行恶意代码。该漏洞与CVE-2017-3241RMI反序列化漏洞有点类似,都是在远程调用过程中通过方法参数传入恶意序列化对象,服务端在解析参数进反序列化时触发。
2.漏洞原理
CVE-2020-1948远程代码执行漏洞原理是远程方法被动态调用导致的代码执行。从程序报错异常中分析中找到奔溃点,然后跟进到源码中可以发现atorg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:79)[dubbo-2.7.6.jar:2.7.6]开始分析,找到 readobject,再定位到com\alibaba\com\caucho\hessian\io\Hessian2Input.java,经过后续的多次循环,com\alibaba\com\caucho\hessian\io\ClassDeserializer.java与com\alibaba\com\caucho\hessian\io\ClassDeserial。得到是hessian问题造成漏洞原因。
基于此,Apache官方在2020年6月22日Apache官方发布了Dubbo 2.7.7版本,在此版本中,对invoke的method的进行了限制,在DecodeableRpcInvocation.java文件133-135行增加了对Method方法进行验证,如果验证不通过则抛出非法参数异常终止程序运行,用来修复此漏洞。
03
亡羊补牢
1.惨遭绕过
2020年7月1日,腾讯安全云鼎实验室就给出了Dubbo2.7.7反序列化漏洞绕过的方法。从官方发布的补丁对比文件来看,Dubbo在DecodeableRpcInvocation.java文件133-135行增加了对Method方法进行验证,如果验证不通过则抛出非法参数异常终止程序运行。通过对历史版本的回溯,发现在2019.10.31日的一次提交中DubboProtocol类的getInvoker函数的RemotingException代码块中增加了getInvocationWithoutData方法,对inv对象的arguments参数进行置空操作,用来缓解后反序列化攻击,此处正是CVE-2020-1948漏洞后反序列化利用的触发点。千里之堤溃于蚁穴,为了应对反序列化的漏洞而推出的Dubbo2.7.7,还是再一次走上了因为反序列化而引发远程命令执行的老路。
其中MITRE Caldera执行的动作由计划系统结合预配置的ATT&CK模型生成。这样的好处在于能够更好更灵活地对攻击者的操作进行模拟,而不是遵循规定的工作序列。自动模拟攻击者进行攻击演练,安全地重现发生过的攻击行为,不会对资产造成损害,并且能够重复执行以对防御能力和检测能力进行测试和验证。
2.绕过原理
绕过选用的反射链是com.sun.rowset.JdbcRowSetImpl。此次dubbo的利用方式虽然也是在connect()方法中被使用jndi的方式加载恶意类,但是dubbo是动态调用了JdbcRowSetImpl的getDatabaseMetaData方法,造成了connect方法被执行。对提交修复的commit代码分析发现在DecodeableRpcInvocation中增加了输入参数类型的校验。点开查看代码,在原有的基础上增加了参数类型的校验,补丁代码如下图所示:
这里的parameterTypes是限制Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,如下图所示:
绕过2.7.7版本更新的传入的参数类型为Class<?>类型。类路径为Class<?>class com.rometools.rome.feed.impl.ToStringBean类,如果对参数检查是可以有效阻止漏洞的利用,然而修复错了地方。从git的commit记录来看,官方的修复思路推测是想在异常抛出前利用参数类型的校验并拦截,防止引用的toString()方法造成代码执行。但是拦截错了地方,通过对源码的单步调试发现,此处抛出的异常并不在漏洞触发的调用链上。
DecodeableRpcInvocation.java文件133-135行增加了对Method方法进行验证,如果验证不通过则抛出非法参数异常终止程序运行,核心代码代码如下:
跟进isGenericCall和isEcho方法,发现验证逻辑十分简单,如果method等于$invoke、$invokeAsync或者$echo则返回true。不得不说此处站在开发角度思考是没问题的,非Dubbo自带service中的$invoke、$invokeAsync、$echo方法以外,其他函数名全部抛出异常,但是万万没想到RPC调用过程中方法名是用户可控的,所以攻击者可轻易的将method设置为其中任意一个方法来绕过此处限制。
知道了method的验证逻辑,修改CVE-2020-1948 Poc中的的service_name和method_name参数的值,分别为:org.apache.dubbo.rpc.service.GenericService和$invoke。就可以验证通过,最后进入hession反序列化流程,成功执行了代码。
3.姗姗来迟
7月28日,ApacheDubbo终于发布了2.7.8的版本用来解决绕过问题。
时隔1个月发布的Dubbo2.7.8版本堵上了2.7.7解决反序列化的问题,在2.7.8版本发布的后的1个多月,还没有被爆出利用绕过的案例,至此,Dubbo的反序列漏洞的修补终于告一段落。
04
后记
梅须逊雪三分白,雪却输梅一段香。关于Dubbo反序列化漏洞的攻与防之争从来没有停止过,Dubbo如此,其他的服务框架亦如此。没有绝对的安全,只有动态的攻与防的平衡。作为安全从业者,我们要敬畏攻击,更要感谢攻击,他山之石,可以攻玉。以攻促防、以攻带防,了解对手、了解敌人,攻防一体、攻防兼备才可以推动企业的安全的新发展。