作者:平安科技银河实验室
公众号: https://mp.weixin.qq.com/s/wU1iacELbhspWMftRIQdeA
Dubbo是阿里巴巴一种开源的RPC服务框架,常被用来做分布式服务远程对象的调用,日前Dubbo被发现有CVE-2020-1948的远程代码执行漏洞,官方针对这一漏洞发布了新版本Dubbo2.7.7,对源码分析发现修复方式并不能阻止CVE-2020-1948漏洞利用。
Dubbo的安全威胁即使升级到了新版本依然存在威胁。不是绕过是官方修复错了位置,并没有产生新的0day。
针对爆发漏洞时间节点的commit记录发现,官方对传入参数做了类型的校验,针对修复的commit记录,我们做了详细的分析。
Dubbo漏洞爆发的时间线如下图所示:
对提交修复的commit代码分析发现在DecodeableRpcInvocation中增加了输入参数类型的校验。Commits记录截图如下图所示:
点开查看代码,在原有的基础上增加了参数类型的校验,补丁代码如下图所示:
这里的parameterTypes是限制为 Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
,如下图所示;
本次poc传入的参数类型为Class<?>类型。类路径为 Class<?>class com.rometools.rome.feed.impl.ToStringBean
类,如果对参数检查是可以有效阻止漏洞的利用,然而修复错了地方。
从git的commit记录来看,官方的修复思路推测是想在异常抛出前利用参数类型的校验并拦截,防止引用的toString()方法造成代码执行。
但是拦截错了地方,通过对源码的单步调试发现,此处抛出的异常并不在漏洞触发的调用链上,具体的漏洞分析可见下文。
CVE-2020-1948远程代码执行漏洞原理是远程方法被动态调用导致的代码执行。
下文将会对本次漏洞利用方式和触发原理进行分析,因为本次代码执行漏洞触发原理与Java的反射机制相关,下文将简单阐述Java的反射机制。
Java的反射机制是指在程序的运行状态中,程序对于任意一个类都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个属性和方法;这种动态的信息获取和调用机制被称为Java的反射机制。
Dubbo本次的漏洞触发原理就是toStringBean类内部动态调用外部传入对象的方法导致的代码执行。在研究漏洞的触发前,需要先简单了解为什么toStringBean类会在代码中动态调用外部对象的方法。
ToStringBean类是一个可以被序列化的公共类,可以将对象的类型和方法转成字符串供调用。
在ToStringBean实现的toString方法中,会遍历传入对象的所有方法(Method对象),并且通过java实现的invoke方法动态调用传入对象的所有Method对象。
toString实现方法如下图所示:
漏洞触发点在 com.rometools.rome.feed.impl.ToStringBean
类的toString方法中,toString方法中的 getter.invoke(obj, NO_PARAMS);
语句,如下图所示:
上图是动态运行中的调试截图,可以从图中看出obj参数的值为JdbcRowSetImpl类的实例化对象,当此处for循环执行到JdbcRowSetImpl类中getDatabaseMetData函数时候,会调用函数内connect方法,导致执行JdbcRowSetImpl的执行链,导致代码执行。
一个正常的dubbo的服务调用,当找不到注册的service的时候会抛出异常,抛出异常的截图如下图所示:
这个inv是DecodeableRpcInvocation的实例化对象。上述红箭头中的+ inv语法会默认调用inv实例化对象的toString()方法,DecodeableRpcInvocation的toString()方法实现在父类Rpcinvocation中,跟进查看 Rpcinvocation.java
中的toString方法,截图如下:
上图中的argements方法根据右边的变量调试信息显示,实际上就是ToStringBean类的实例化对象,其中该对象有两个参数,beanClass是Class<?>类型,obj是Object类型,此时的调用栈显示obj是JdbcRowSetImpl实例化对象,beanClass是 Class.ForName(“com.sun.rowset.JdbcRowSetImpl”)
对象。 Array.toString(Object[] obj)
方法,将会在底层调用 String.valueOf(Object obj)
,此时这个obj对象是带有恶意负载的argements对象,继续往下跟踪Array.toString将argements向下执行, Array.toString(Object[] obj)
方法如下图所示:
上图中可以发现传入的对象最后会经过String.valueOf转变为字符串储存在b变量里,继续往下跟踪,在String.valueOf方法中,当传入对象为toStringBean的时候,会调用toStringBean对象的toString()方法,String.valueOf方法如下图所示:
当进入ToStringBean的toString()方法的时候,如之前漏洞触发点所陈述,携带有恶意负载的对象,将会被执行恶意代码。由此我们清楚的看到了调用链,如下:
throw new RemotingException ->RpcInvocation.toString()->Arrays.toString()->String.valueOf()->toStringBean.toString()->getter.invoke(obj, NO_PARAMS) 最终在ToStringBean的toString()方法中Invoke动态调用对象造成代码执行。
1、服务器防火墙添加内网IP白名单策略。 2、限制服务器的公网权限。
1、漏洞被利用的类在 rome-{version}.jar
包中,公网服务可以考虑自查是否引用了 rome-{version}.jar
包,如果引用了可以考虑重写toString()方法,重新编译并加载到生产环境。 2、服务器防火墙添加IP白名单策略。
1、在流量检测设备上可以增加检测规则,通过分析发现流量中可能会包含以下危险类的关键字
(如:org.apache.xbean.naming.context.ContextUtil.ReadOnlyBinding, org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor, com.rometools.rome.feed, com.caucho.naming.QName, com.rometools.rome.feed.impl.ToStringBean等)。
下图是复现过程中的流量抓包:
用最新版本dubbo-2.7.7版本做漏洞复现,显示最新版本仍存在CVE-2020-1948漏洞。 引用dubbo-samples的http的例子,git地址为https://github.com/apache/dubbo-samples,加载之后需要修改配置,将只支持http协议的方式修改为dubbo-rpc协议,maven相关配置如下图所示:
启用服务之后,利用传统的jndi的利用方式,在公网启用ldap服务和http服务,http服务目录下放入我们需要加载的远程执行代码的class文件。
本文中漏洞复现选择在windows本机上弹出计算器。
截至目前官方给出的最新版本依然存在该风险,利用如下图所示:
本次漏洞复现的利用方式采用了JNDI远程加载恶意类的方式。
本次选用的反射链是com.sun.rowset.JdbcRowSetImpl,jdk对于该链在较新的版本有限制,在做漏洞复现的过程中尽量使用低版本的jdk版本,如环境有限制需要配置特定的绕过策略。
本次dubbo的漏洞触发点与fastjson有所不同,fastjson使用该链造成代码执行是因为在setAutoCommit方法的时候,该方法中会使用this.connect()方法,connect()方法中可以使用jndi的方式加载恶意类造成代码执行。
本次dubbo的利用方式虽然也是在connect()方法中被使用jndi的方式加载恶意类,但是dubbo是动态调用了JdbcRowSetImpl的getDatabaseMetaData方法,造成了connect方法被执行。
getDatabaseMetaData方法源码如下图所示:
从上图可以看出getDatabaseMetaData对象中也调用了connect方法,connect方法中会加载dataSource指向的地址,被加载远程Reference对象,造成JNDI方式的远程代码执行。