Apache-RocketMQ远程代码执行漏洞(CVE-2023-37582)原理分析
2023-07-25 11:00:00

0x00、前言

Apache-RocketMQ远程代码执行漏洞(CVE-2023-37582)原理分析,该漏洞为历史漏洞(CVE-2023-33246)补丁未修复完全,导致可以向nameserver发送更新配置信息从而写入文件。

回顾上个月的历史漏洞(CVE-2023-33246)原理:
由于RocketMq未对不同角色访问做严格的身份验证,攻击者可伪造成RocketMq管理端向服务器的broker(代理服务器)发送更新Broker配置更新请求,请求中包含了恶意代码,broker server接收到更新请求会调用callshell导致恶意代码执行。

0x01、漏洞描述

Apache RocketMQ是一款开源的分布式消息和流处理平台,提供了高效、可靠、可扩展的低延迟消息和流数据处理能力,广泛用于异步通信、应用解耦、系统集成以及大数据、实时计算等场景。

Apache RocketMQ远程代码执行漏洞(CVE-2023-37582)细节及POC已公开,由于历史漏洞(CVE-2023-33246)未修复完全,最终可以RocketMQ运行的系统用户身份执行任意命令。

0x02、漏洞范围

Apache RocketMQ <= 5.1.1

0x03、环境搭建

这里直接拉取docker镜像

docker pull apache/rocketmq:5.1.1

启动nameserver

docker run -it --net=host apache/rocketmq:5.1.1 ./mqnamesrv

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

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

然后重启docker容器即可

docker restart <ContainerId>

idea开启debug即可调试

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

0x04、漏洞复现

已公开对应的检测exp:



0x05、原理分析

RocketMQ-NameServer源码可参考文章,文章中对nameserver的启动、功能、执行流程有很清晰的描述

引用一张上文中nameserver的时序流程图

可以看到nameserver通过NamesrvStartup类进行启动,启动的源码很好跟进,不多叙述,重点在接收请求的处理类DefaultRequestProcessor也就是图中的步骤10

通过建立连接,获取请求的code值,通过code值的的不同进入不同的处理分支。

code值为UPDATE_NAMESRV_CONFIG,也就是318时,会调用updateConfig方法用于更新配置


updateConfig方法会获取请求体的内容,将请求体内容转化为字符串

将请求体配置内容存放进环境配置变量properties中,随后判断如果配置内容key中存在kvConfigPathconfigStorePathName,则直接返回,无法更新配置

该黑名单匹配为历史漏洞(CVE-2023-33246)补丁修复添加的,为了杜绝可能存在风险的更新配置功能点,在一些控制管理和角色组件中添加了黑名单,与此同时还添加了其它的黑名单匹配来防止更新配置文件


在nameserver角色组件中,过滤了上图中的kvConfigPathconfigStorePathName字段,但是没有过滤configStorePath字段,导致仍可使用configStorePath字段进行更新配置文件,导致的补丁绕过,接着往下跟进

在获取到配置内容后,调用update更新

通过mergeIfExist方法将properties信息添加到allConfigs中,前提是properties中的key值必须在allConfigshash表中存在才能覆盖表中的原有值

    private void mergeIfExist(Properties from, Properties to) {
for (Entry<Object, Object> next : from.entrySet()) {
if (!to.containsKey(next.getKey())) {
continue;
}

Object fromObj = next.getValue(), toObj = to.get(next.getKey());
if (toObj != null && !toObj.equals(fromObj)) {
log.info("Replace, key: {}, value: {} -> {}", next.getKey(), toObj, fromObj);
}
to.put(next.getKey(), fromObj);
}
}

}

添加过后,allconfigs包含了所有的配置信息包括我们请求体中的配置信息

接下来需要找到allconfigs配置信息中可控的元素,来实现对文件内容的可控,这里找到了配置变量productEnvName,通过productEnvName变量的可控,实现对写入内容的可控,由于已经运行过payload,因此value值为写入的测试数据。

原理上只要是allconfigshash表中的字符串类型元素,且不影响下面反射赋值后对写入产生影响的元素均可以作为文件内容的可控元素。

但此时,并未在系统中更改对应configStorePath配置属性的值,只是将请求体信息添加更新到allconfigs中,作为写入的内容而已

接着调用properties2Object方法,通过反射调用存在set前缀的属性方法,这里通过调用setconfigStorePath方法对configStorePath字段进行赋值,实现对文件写入路径的可控



接着调用persist方法

获取到allconfigs信息后,调用MixAll.string2File方法allconfigs信息将写入指定的configStorePath目录文件中,深入利用还可以将写入路径改为linux计划任务中,实现自启动执行命令

路径通过反射获取configStorePath的值

最后将allconfigs配置信息写入configStorePath路径文件中


payload的数据包,configStorePath路径和productEnvName内容可控,可实现任意文件的写入

0x06、漏洞修复

目前官方已发布安全版本,建议受影响用户升级至:
RocketMQ 5.x >= 5.1.2
RocketMQ 4.x >= 4.9.7

官方补丁下载地址:
https://rocketmq.apache.org/download/

修复方式:

  • 黑名单匹配添加configStorePath字段,用户请求中包括configStorePath字段将无法进行配置更新请求

0x07、参考链接

https://github.com/apache/rocketmq/commit/9d411cf04a695e7a3f41036e8377b0aa544d754d
https://blog.51cto.com/search/user?uid=14281117&q=rocketmq
https://github.com/apache/rocketmq/commit/c1fdf1d62c627d6cfbae06d0e15f1c23c7be654b