前言
还是先来了解一介系什么
Apache Dubbo 是一款 RPC(Remote Procedure Call Protocol)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。 服务开发框架,用于解决微服务架构下的服务治理与通信问题。漏洞存在于 Apache Dubbo默认使用的反序列 化工具 hessian 中,攻击者可能会通过发送恶意 RPC 请求来触发漏洞,这类 RPC 请求中通常 会带有无法识别的服务名或方法名,以及一些恶意的参数负载。当恶意参数被反序列化时,达 到代码执行的目的。
在Java语言体系下dubbo通常为spring boot,提供从项目创建、开发测试,到部署、可视化监测、流量治理,再到生态集成的全套服务。
可以从官网看到,dubbo支持很多种通讯协议
了解 Dubbo 核心概念和架构 | Apache Dubbo
dubbo RPC 默认采用 hessian2 序列化。 但 hessian 是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对 java 进行 优化的。而 dubbo RPC 实际上完全是一种 Java to Java 的远程调用,其实没有必要采用跨语 言的序列化方式(当然肯定也不排斥跨语言的序列化)。
环境搭建
可以实现新功能的东西还是自己先动手试试吧
要配置并且启动zookeeper
ZooKeeper 是一个集中式服务,用于维护配置信息、命名、提供分布式同步和提供组服务。
dubbo是一个分布式服务框架,主要用于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。dubbo的服务提供者会在zookeeper上面创建一个临时节点,表明自己的IP和端口。当消费者需要使用服务时,会先在zookeeper上面查询,找到服务提供者,做一些负载的选择(比如随机、轮流),然后按照这些信息,访问服务提供者。因此,dubbo必须使用zookeeper
下载完后解压进入conf目录后新建一个zoo.cfg文件,然后内容为
1 | tickTime=2000 |
记得修改为自己的路径
然后进入bin目录运行
1 | ./zkServer.cmd |
看到这个表示成功了
项目搭建就根据官网的教程来
3 - 基于 Spring Boot Starter 开发微服务应用 | Apache Dubbo
好吧,后面发现跟据官网搭建是远远不够的,最多只是熟悉一下如何使用的。
CVE-2020-1948
Apache dubbo Hession协议反序列化漏洞
漏洞介绍 Dubbo 2.7.6或更低版本采用hessian2实现反序列化,其中存在反序列化远程代码执行漏洞。攻击者可以发送未经验证的服务名或方法名的RPC请求,同时配合附加恶意的参数负载。当服务端存在可以被利用的第三方库时,恶意参数被反序列化后形成可被利用的攻击链,直接对 Dubbo服务端进行恶意代码执行。
影响范围
- Apache Dubbo 2.7.0 ~ 2.7.6
- Apache Dubbo 2.6.0 ~ 2.6.7
- Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)。
diff一下补丁
DecodeableRpcInvocation增加入参类型校验 by aquariuspj · Pull Request #6374 · apache/dubbo (github.com)
这里判断了rpc的方法名,要求为指定的方法名,要求参数为String,String[],Object[]
但是这样根本无法拦截恶意代码,通过设置$invoke
、$invokeAsync
、$echo
等特殊方法的变量值,依然可以造成RCE
1 | String[] types = new String[]{"com.xxx.rome"}; |
环境搭建
需要根据官网的搭建教程进行修改
properties标签的maven.compiler.source和maven.compiler.target都设置值为1.8
然后修改三个模块dubbo依赖
1 | <!-- dubbo --> |
在provider的application.yml文件里面新加,来配置扫描注解的包。
1 | server: |
以及添加接口的实现方法
1 | package org.apache.dubbo.springboot.demo.provider; |
maven依赖添加
1 | <dependency> |
interface那个项目,下的DemoService新添加两个
1 | package org.apache.dubbo.springboot.demo; |
consumer项目修改application.yml来运行web服务
1 | server: |
添加SpringBoot路由
1 | package org.apache.dubbo.springboot.demo.consumer; |
添加poc
1 | package org.apache.dubbo.springboot.demo.consumer; |
然后访问localhost:9991/calc来通过消费者来操作提供者。
漏洞分析
打个断点来看调用栈
1 | lookup:417, InitialContext (javax.naming) |
就是那Rome链打的。
CVE-2021-43297
Apache Dubbo Hessian2 异常处理时反序列化
漏洞描述
Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297) (seebug.org)
就是在catch未知情况时会导致远程命令执行
补丁
Remove toString calling · apache/dubbo-hessian-lite@a35a4e5 (github.com)
这些改动都是删除了括号符与obj拼接的输出。这里存在字符串拼接的隐式.toString
调用。
- toString()方法在Object类里定义的,其返回值类型为String类型,返回类名和它的引用地址。
- 在进行String类与其他类型的连接操作时,自动调用toString()方法。
toString调用了hashMap的hashCode方法,hashMap又有readObject方法,可以作为构造恶意反序列化的入口。但是Hessian2在恢复map类型的对象时,硬编码成了HashMap或者TreeMap,LazeMap不能使用。
利用链
思路是触发 ContextUtil.ReadOnlyBinding 的 toString 方法(实际继承 javax.naming.Binding),toString 方法调用 getObject 方法获取对象。
经过了上面对于补丁的分析,这个时候我们就需要去寻找如何到达obj.toString方法,在com.alibaba.com.caucho.hessian.io.Hessian2Input
发现obj拼接在except方法中
obj是执行反序列化之后得到的,如果这里反序列化出来的是恶意ReadOnlyBinding
对象,就可以RCE了
接着来找找excpet方法的用法,看到在当前这个类是有很多用法的。
而且很多使用是switch语句,default情况,当取default上面没有条件的case就可以进入default里面。
readString当 tag 大于 31 的时候,就会进入到 this.expect 函数
1 | public int readString(char[] buffer, int offset, int length) throws IOException { |
然后又在com.caucho.hessian.io.Hessian2Input#readObjectDefinition找到了对于readString的调用。
在com.caucho.hessian.io.Hessian2Input#readObject()
反序列化的入口的case64下调用了readObjectDefinition方法。
1 | public Object readObject() throws IOException { |
注意,在com.alibaba.com.caucho.hessian.io.SerializerFactory#getDefaultSerializer会检测有没有序列化没有继承Serializable的类。默认是不允许序列化没有继承Serializable的类,但是神奇的是这只是本地的校验,关闭即可,服务端根本没有校验类需要继承Serializable。
添加一个依赖,dubbo的com.alibaba.com.caucho.hessian.io不太好用来控制_defaultSerializer
的值
1 | <dependency> |
也可以通过重写的方式来实现,直接修改值
1 | protected Serializer getDefaultSerializer(Class cl) { |
或者我想通过jvm代理来重写,QAQ
害,直接修改vm参数就可以了,服辣。
1 | -Ddubbo.hessian.allowNonSerializable=true |
测试类
1 | import com.alibaba.com.caucho.hessian.io.Hessian2Input; |
后续利用就是找一条toString链
CVE-2022-39198
官方的通告
hessian-lite导致的hessian反序列化
diff一下补丁
Merge pull request #61 from apache/3.2.13-release · apache/dubbo-hessian-lite@5727b36 (github.com)
就是ban掉了一些包
1 | org.apache.commons.codec. |
唯一一个JDK包名的是sun.print.
frohoff/jdk8u-jdk (github.com)
注意到一个sun.print.UnixPrintServiceLookup
类,只在unix的JDK中存在。他自带了getter方法getDefaultPrintService
,虽然没有实现Serializeable接口,但是可以在hessian反序列化中得到利用。但是在高版本的JDK中被移除了。
现在的利用思路就是寻找可以触发getter方法的(我能想到的)。
- FastJason可以触发
- Rome可以触发
fastjson库是dubbo库的依赖,所以选择fastjson
XString#equals
方法调用JSONObject#toString
方法的调用栈
1 | toString:1071, JSON (com.alibaba.fastjson) |
类的构造函数没有使用public与static修饰,所以只能通过使用反射去实例化该类
1 | import sun.print.UnixPrintService; |
CVE-2023-23638
泛化调用可以使我们不依赖具体的接口 API, 就可以调用对应 Service 的某个方法
先看到 org.apache.dubbo.common.utils.PojoUtils#realize0
方法
这一块代码首先判断是否为Map类型,通过getInstance来获取对象,防止多次调用构造方法
看到这个INSTANCE属性的定义,这个getInstance方法是通过双重校验锁法来实现的一个单列模式
单列模式的实现这篇文章有讲解Java单例模式(Singleton)以及实现 - CieloSun - 博客园 (cnblogs.com)
接着看到validateClass 方法,用来给类名进行过滤
1 | public boolean validateClass(String name, boolean failOnError) { |
首先是验证CLASS_ALLOW_LFU_CACHE
白名单,再验证CLASS_BLOCK_LFU_CACHE
黑名单
再看到realize0方法,遍历Map的key然后获取setter方法并且给field赋值。
通过object.field.set进行利用
如果调用 JdbcRowSetImpl 的 setAutoCommit 方法造成 jndi 注入。
与此同时呢,获取可能的 setter 和 Field, 然后赋值,这样可以控制任何类的任何属性。那么通过field赋值机制可以控制SerializeClassChecker的INSTANCE属性值来绕过黑白名单。
poc
1 | package org.apache.dubbo.samples; |
迭代的HashMap的顺序并不是放置元素的顺序,这个时候就需要LinkedHashMap。LinkedHashMap通过维护一个运行于所有条目的双向链表保证了元素迭代的顺序。
复现代码X1r0z/Dubbo-RCE: PoC of Apache Dubbo CVE-2023-23638 (github.com)
通过object.set+METHOD_NAME进行利用
Dubbo的configuration也是可以通过java.lang.System类的props对象进行传入的。那么就可以直接调用System.setProperties方法,传入修改后的dubbo配置,接着直接进行JDK原生反序列化。
1 | private static Map getProperties() throws IOException { |
后记
使用JNDI注入,暑假回去自己整了一个JNDI注入的工具吧。
1 | java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "Calc" -A "127.0.0.1" |
效率有点低,想改CVE-2023-23638的payload,通过访问路由来调用方法,但配置文件改了很久没改成功,也没找到教程就放弃了,耽误了很多时间。以后学习别人漏洞利用文章的时候先看思路,感觉大把时间都用在搭建环境上面了/(ToT)/~~
这4个洞的触发思路各有不同,CVE-2020-1948 是反序列化直接就造成的漏洞,CVE-2021-43297是在反序列化过程中可以触发tostring方法,CVE-2022-39198是利用了dubbo自带的fastjson反序列化触发任意getter方法进而调用到一些无需实现实现Serializeable接口类的get方法,在hessian反序列化中使用,最后的CVE-2023-23638是通过恶意利用泛化调用中的方法来绕过黑名单限制。
还没学习过fastjson只能对触发点有浅显的了解,后面学习fastjson多多注意吧。
通过CVE-2021-43297漏洞在Apache Dubbo<=2.7.13下实现RCE - bitterz - 博客园 (cnblogs.com)
Dubbo的反序列化安全问题-Hessian2 - bitterz - 博客园 (cnblogs.com)
Apache Dubbo CVE-2023-23638 分析 - X1r0z Blog (exp10it.io)
Dubbo的反序列化安全问题-Hessian2 - bitterz - 博客园 (cnblogs.com)
Apache Dubbo 2.7.6 反序列化漏洞复现及分析 - 先知社区 (aliyun.com)
CVE-2022-39198 Apache Dubbo Hession Deserialization Vulnerability Gadgets Bypass - 先知社区 (aliyun.com)
Java安全学习——Hessian反序列化漏洞 - 枫のBlog (goodapple.top)
与 CVE-2021-43297 相关的两道题目 (harmless.blue)
https://aecous.github.io/2023/10/01/%E5%88%9D%E6%8E%A2UnixPrintService/