Apache-RocketMQ远程代码执行漏洞(CVE-2023-33246)原理分析
2023-06-09 12:50:00

0x00、前言

Apache-RocketMQ远程代码执行漏洞(CVE-2023-33246)原理分析

0x01、漏洞描述

RocketMQ 5.1.0及以下版本,在一定条件下,存在远程命令执行风险。RocketMQ的NameServer、Broker、Controller等多个组件暴露在外网且缺乏权限验证,攻击者可以利用该漏洞利用更新配置功能以RocketMQ运行的系统用户身份执行命令。此外,攻击者可以通过伪造 RocketMQ 协议内容来达到同样的效果。

0x02、漏洞范围

Apache RocketMQ <= 5.1.0
Apache RocketMQ <= 4.9.5

0x03、环境搭建

这里直接docker起的p牛的vulhub环境
项目地址:https://github.com/vulhub/vulhub/tree/master/rocketmq/CVE-2023-33246

可以编辑docker-compose.yml文件添加debug的监听端口,方便后面的调试

docker直接启动:

docker-compose up -d

idea调试:
进入docker容器,修改启动配置文件(runbroker.sh、runserver.sh)

添加idea debug jvm远程调试的代码

然后重启docker容器即可

docker restart <ContainerId>

idea开启debug即可调试

(如果debug连接出现握手失败,换个调试端口)

0x04、漏洞复现

目前POC已公开,可以直接利用
POC:
https://github.com/I5N0rth/CVE-2023-33246
https://github.com/vulhub/vulhub/tree/master/rocketmq/CVE-2023-33246

漏洞复现:


0x05、原理分析

简述漏洞原理:
由于RocketMq未对不同角色访问做严格的身份验证,攻击者可伪造成RocketMq管理端向服务器的broker(代理服务器)发送更新Broker配置更新请求,请求中包含了恶意代码,broker server接收到更新请求会调用callshell导致恶意代码执行。
这个过程主要就涉及用户发送更新配置请求、broker server接受并处理更新请求。

伪造管理端发送代理服务器配置更新

下断点跟进

攻击者用户端起了nameServer服务,发送更新配置请求,请求方法updateBrokerConfig,传递参数为远程broker地址和配置信息(其中配置信息包括rocketmqHome字段和filterServerNums字段)

继续跟进,接着调用defaultMQAdminExtImpl.updateBrokerConfig方法


接着调用MQClientAPIlmpl.updateBrokerConfig方法,通过RemotingCommand.createRequestCommand方法创建请求对象

这也是对应数据包中发送请求的部分数据

然后从配置中读取信息拼接成字符串(即payload传入的filterServerNumsrocketmqHome属性信息),后面再转换成字节放入请求体中

接着调用invokeSync发送

通过getAndCreateChannel向远程地址获取sock通信

然后执行doBeforeRpcHooks钩子方法,为执行前通过读取环境配置添加部分数据


然后调用invokeSyncImpl方法执行rocketmq请求

最后执行的数据包

Broker server处理配置更新请求

broker代理服务器通过BrokerStartup.start启动(broker代理服务器会生命周期每30秒向nameserver发送心跳)

接着调用BrokerController.startBasicService

startBasicService方法会判断broker功能即服务是否存在开启对应的功能,这里开启filterServerManager.start()

然后调用createFilterServer方法

这里遇到的第一个条件即通过配置表中获取FilterServerNums属性值,去减去filterServerTable的大小(由于默认未创建对应的filterServer,因此表为null,大小为0),只有得到的差值>=1,才能调用下面的callShell方法去执行命令,因此需要配置文件FilterServerNums属性值设置为>=1

props.setProperty("filterServerNums", "1");

接着通过buildStartCommand方法获取执行命令的字符串语句

这里的第二个条件即只有通过获取getRocketmqHome方法才能达到执行命令的目的,而getNamesrvAddr获取后执行条件不满足执行恶意代码,因此需要通过配置环境RocketmqHome值为恶意代码,通过判断服务器运行系统环境的来执行windows命令还是linux命令。

props.setProperty("rocketmqHome", "-c $@|sh . echo touch /tmp/success");

用上述payload在linux上的执行效果则为:

sh -c $@|sh . echo touch /tmp/success;/bin/startfsv.sh

获取到cmd后,调用callShell

然后调用splitShellString方法把cmd语句通过空格分割成字符串数组


最后命令执行触发漏洞


最后的问题就是命令执行形式为什么是-c $@|sh . echo <cmd>,在linux主机环境上也无法正常运行此代码,这思考了很久没想出答案,直到ChatGPT的回复以及这个命令的来源文章

-c $@|sh . echo <cmd>命令来源文章:
https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html

很清晰的解释了java代码中在linux环境下用-c $@|sh . echo <cmd>来构造命令执行的原因

0x06、漏洞修复

目前官方已发布安全修复更新,升级到Apache RocketMQ 5.1.1或者4.9.6。
https://rocketmq.apache.org/download/

修复方式:
简单粗暴,直接移除漏洞代码

0x07、参考链接

https://github.com/vulhub/vulhub/tree/master/rocketmq/CVE-2023-33246
https://github.com/I5N0rth/CVE-2023-33246
https://blog.csdn.net/weixin_39841589/article/details/118037271
https://blog.csdn.net/qq_26400953/article/details/103129856
https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html