ApacheDubbo反序列化漏洞复现分析
前言
学习下Apache Dubbo系列的经典漏洞
CVE-2019-17564
影响版本
- Dubbo 2.7.0 to 2.7.4
- Dubbo 2.6.0 to 2.6.7
- Dubbo all 2.5.x versions
环境准备
从https://github.com/apache/dubbo-samples/tree/master/dubbo-samples-http下载源代码,在pom中切换dubbo版本,发现无法找到存在该漏洞的版本,手动下载jar包添加,并添加可利用依赖common-collections3.2
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/12a6c23f-1044-40f5-bfb8-5e81d83cd94c.png)
漏洞分析
在dubbo开启http协议后通过http协议的请求都会经过org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle方法,所以在此处打断点
利用ysoserial生成payload
1 | java -jar ysoserial.jar CommonsCollections6 "/System/Applications/Calculator.app/Contents/MacOS/Calculator">cc6 |
然后发送
1 | curl http://127.0.0.1:8081/org.apache.dubbo.samples.http.api.DemoService --data-binary @cc6 |
会发现断在我们的断点处,向下跟踪
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/30eef9b5-a4b8-4389-841a-f48c1908b3e5.png)
170行会根据我们传入的路径寻找处理器,我们看看this.skeletonMap的值
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/f329a528-80d3-4742-93b6-a4011cf63f58.png)
key对应着请求路径,value是一个HttpInvokerServiceExporter类的实例;接着在177行使用获取到的处理器处理请求,调用的是org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter#handleRequest
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/1f078ad5-c0a9-4eae-a74c-05a049b29f47.png)
接着会将请求发送到org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter#readRemoteInvocation(javax.servlet.http.HttpServletRequest)方法中
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/7ab76992-613d-4b8a-a603-c92899c77d34.png)
该方法中将请求对象和我们发送的序列化内容传入org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter#readRemoteInvocation(javax.servlet.http.HttpServletRequest, java.io.InputStream)方法中
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/a43467fa-f72c-4fb9-8551-7ced7ea9334f.png)
在该方法中首先会对我们传入的序列化内容转换成ObjectInputStream类型,然后传入org.springframework.remoting.rmi.RemoteInvocationSerializingExporter#doReadRemoteInvocation方法中
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/9036e34f-32ad-492f-8ce1-ade2862e08e1.png)
在该方法中直接对传入的ObjectInputStream对象进行反序列化操作,导致漏洞利用链的触发
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/28aea383-a033-4ba1-8885-891dd72f905d.png)
漏洞修复
将dubbo切换到高版本进行调试,发现在org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle中将请求处理器HttpInvokerServiceExporter换为了JsonRpcServer,在后续的处理中,没有进行反序列化操作
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/30d3f51b-ac42-434d-bbb5-d838b268b5f7.png)
CVE-2020-1948
影响版本
- Dubbo 2.7.0 to 2.7.6
- Dubbo 2.6.0 to 2.6.7
- Dubbo all 2.5.x versions
漏洞分析
可以参考https://www.cnblogs.com/zhengjim/p/13204194.html 中的环境搭建部分进行测试
参考网上的利用脚本
1 | from dubbo.codec.hessian2 import Decoder,new_object |
由于最后是利用rome组件的链条完成利用,所以直接在rome调用链中的com.rometools.rome.feed.impl.ToStringBean#toString()处下断点,发送poc,断点断下后向上查看调用栈,可以看到是由org.apache.dubbo.rpc.RpcInvocation#toString方法进入到ToStringBean的,其中的arguments包含着ToStringBean的实例
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/24806daf-704d-4e2f-a103-0f04bd88adf3.png)
接着向上寻找会发现org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker方法中的代码引发了后面的利用过程,关键点在一处异常处理中
1 | //关键代码 |
其中inv是一个DecodeableRpcInvocation 类的实例,里面存储着上文提到的arguments
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/1b8349e5-ad8b-4b18-b615-55c6d39aec09.png)
在处理异常的过程中,会隐式调用toString方法,进而触发后面的利用链;那么我们来分析下何时会抛出异常
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/c859a983-c180-48a9-8168-a37b0e9f0e4f.png)
当exporter是null的时候,会进行异常处理,也就是在this.exporterMap中找不到键值为servicekey的值,其中servicekey是用户请求的servicename,this.exporterMap是provider定义的service,也就是说当用户请求的service在provider中找不到时,会触发该漏洞
2.7.7补丁分析及绕过
将dubbo版本切换到2.7.7,然后发送payload
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/eaac59dc-84c9-4100-a858-e88de3344617.png)
提示Service not found:,搜索下异常
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/416fc20a-d015-4c68-bfba-ec823cf1ae5e.png)
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/f70326fb-81bd-483e-8582-d29cace88083.png)
可以看到在抛出异常之前有一个判断,跟进去if中的两个方法
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/e801d5bf-1b21-4f3a-a9d1-477abea7d087.png)
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/9ee55c76-8adb-4b2a-ac48-03b8cda74ea6.png)
当请求的方法名是$invoke , $invokeAsync ,$echo其中之一时,不会抛出异常,那么我们可以修改payload如下
1 | from dubbo.codec.hessian2 import Decoder,new_object |
用https://github.com/HyCXSS/JNDIScan 项目检测下jndi注入请求
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/dcf1fa6a-f2f2-4d21-8811-d73e2a10ec0d.png)
可以看道成功收到了provider的ldap请求
2.7.8补丁修复
将版本切换到2.7.8,可以看到对调用方法的参数类型做了校验,
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/d705c589-c5b4-47cb-9220-25f78cef65d4.png)
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/740af94c-586a-4609-a15e-5c5837d0f96e.png)
我们传入的参数类型是Lcom/rometools/rome/feed/impl/ToStringBean; ,自然是不符合的,也就会抛出异常
CVE-2021-43297
影响版本
- Apache Dubbo 2.6.x versions prior to 2.6.12
- Apache Dubbo 2.7.x versions prior to 2.7.15
- Apache Dubbo 3.0.x versions prior to 3.0.5
漏洞分析
首先看一下版本diff
https://github.com/apache/dubbo-hessian-lite/commit/a35a4e59ebc76721d936df3c01e1943e871729bd#
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/bde93243-4023-4639-9df2-1cfe4145a6cc.png)
都是隐式触发toString的点被修复了,其中值得关注的是com.alibaba.com.caucho.hessian.io.Hessian2Input#expect 在反序列化过程中多处存在,那么我们可以分析下如何触发异常来让我们到达toString触发点,首先分析下反序列化的过程
首先会使用com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectDefinition来恢复类的类型和字段名
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/c35a6370-bf7f-4beb-8f6b-286d207b8b81.png)
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/26dfc432-6fcf-48ce-a4ef-b5027fed9285.png)
然后进入com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject(java.util.List<java.lang.Class<?>>)来恢复类的实例
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/d678d8a7-3048-4533-822c-ce2bb2b11d34.png)
跟进去
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/6f0fe2f6-d67f-4fd3-a34c-0e295198a72f.png)
接着跟进
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/b0a1a114-08f1-476a-8003-550d9d6340b4.png)
获取到反序列化器为JavaDeserializer,然后进行反序列化,接着跟进com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject(com.alibaba.com.caucho.hessian.io.AbstractHessianInput, java.lang.String[])
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/4644c654-f5d6-4618-8d13-8fd63ae82369.png)
首先恢复类的实例,然后接着调用com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject(com.alibaba.com.caucho.hessian.io.AbstractHessianInput, java.lang.Object, java.lang.String[])来恢复属性的值
首先遍历属性,获取属性对应的反序列化器进行反序列化
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/8eb5dbe5-1265-45ed-87e1-420ce697605e.png)
当字段的类型是object时,会再次调用com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectDefinition来获取对象的信息
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/43ef6e1f-9be5-41df-871c-ecfb40017d92.png)
首先会进行readstring操作,其中这个readString方法中就存在着对expect的调用
如果在该处readString时使其异常进入expect,我们就可以触发后面的toString链,这里我们可以将属性的值更改掉,如图
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/75f10520-bddc-4f76-af77-ad1b8893362a.png)
正常在readString中首先会执行read操作,读到F,我们可以控制换掉这个F,也就是更改tag的值
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/b142da54-8abe-42b3-a8fc-6765f137fec1.png)
在这里我们选择0x43,也就是67,然后进入expect
![](/2022/04/20/ApacheDubbo%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%88%86%E6%9E%90/1294f6c5-09d1-4a8c-b9ab-bcccfe8a076f.png)
在这里会readobject,也就是会读0x43 后面的值,然后触发toString,我们在0x43后拼接我们构造的恶意对象即可
参考链接
https://gv7.me/articles/2020/cve-2019-17564-dubbo-http-deserialization-vulnerability/
https://l3yx.github.io/2020/08/25/Apache-Dubbo-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E7%AC%94%E8%AE%B0/
https://www.cnblogs.com/zhengjim/p/13204194.html
http://rui0.cn/archives/1338