跳转至

CVE-2024-32030 — Kafka UI 未授权 JRMP 反序列化远程代码执行

发布于 2026-06-06

未授权远程代码执行

未经身份验证的攻击者可通过注册一个将 JMX 指标端点指向攻击者控制的 JRMP 监听器的集群,在 Kafka UI JVM 内执行任意命令。无需凭据或任何预先访问权限。

属性
Project Kafka UI
受影响组件 kafka-ui-api — JMX 指标收集器(JmxMetricsRetriever
Severity HIGH
CVSS 8.1 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWE CWE-502, CWE-94
受影响版本 所有版本 ≤ v0.7.1
修复版本 v0.7.2(补丁提交 83b5a60,PR #4427,发布于 2024-04-10)
安全公告 GHSL-2023-230

1. 漏洞概述

Kafka UI(provectus/kafka-ui)是一个用于管理 Apache Kafka 集群的开源 Spring Boot Web 应用程序。当动态配置功能开启时(官方 README 推荐添加 -Ddynamic.config.enabled=true 参数),HTTP API 接受未经身份验证的 PUT /api/config 请求来注册新的 Kafka 集群,请求中包含一个 metrics 字段,用于指定 JMX 主机和端口。Kafka UI 的 JMX 指标收集器随后通过 Java RMI 消息协议(JRMP)使用标准的 javax.management.remote.JMXConnector 连接到该主机和端口。

JRMP 在完成身份验证之前就对服务端响应进行了反序列化。由于 commons-collections:3.2.2scala-library:2.13.9 作为传递性依赖同时存在于类路径中,攻击者可以构造一个两阶段反序列化攻击,在 Kafka UI JVM 内实现完整的远程代码执行。

根本原因

文件: kafka-ui-api/pom.xml

kafka-json-schema-serializer 依赖将 commons-collections:3.2.2 作为传递性依赖引入。Commons Collections 3.2.2 通过系统属性 org.apache.commons.collections.enableUnsafeSerialization 控制的开关来防御不安全的函数反序列化。然而,同样作为传递性依赖的 scala-library:2.13.9 提供了可以在反序列化过程中设置该系统属性的构建块。

第一阶段 — 通过 Scala 利用链绕过 CC 3.2.2 防护:

ConcurrentSkipListMap.readObject() 在反序列化键时会调用 comparator.compare()。通过将比较器设置为 scala.math.Ordering$IterableOrdering,将键设置为包含指向 scala.sys.SystemProperties.$anonfun$addOne$1SerializedLambdascala.collection.View$Fill 实例,反序列化链会调用 System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true"),从而禁用防护。

第二阶段 — 通过 CommonsCollections7 执行任意命令:

防护被禁用后,第二个 JRMP 连接投递标准的 CommonsCollections7 利用链:HashtableLazyMapChainedTransformerInvokerTransformerRuntime.exec(...)

修复方案(提交 83b5a60)从 kafka-json-schema-serializer 的依赖树中排除了 commons-collections:3.x,并用 commons-collections4:4.4 替换它。后者的函数类完全不实现 Serializable 接口,使整个 CC 利用链在结构上无法被组装:

--- a/kafka-ui-api/pom.xml
+++ b/kafka-ui-api/pom.xml
@@ -81,6 +81,12 @@
         <groupId>io.confluent</groupId>
         <artifactId>kafka-json-schema-serializer</artifactId>
         <version>${confluent.version}</version>
+        <exclusions>
+            <exclusion>
+                <groupId>commons-collections</groupId>
+                <artifactId>commons-collections</artifactId>
+            </exclusion>
+        </exclusions>
     </dependency>
     ...
+    <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-collections4</artifactId>
+        <version>4.4</version>
+    </dependency>

2. 漏洞复现环境

复现环境是一个运行官方 provectuslabs/kafka-ui:v0.7.1 镜像的单个 Docker 容器。该镜像内置了完整的漏洞类路径,commons-collections-3.2.2.jarscala-library-2.13.9.jar 均被打包在 JAR 的嵌套库目录中。动态配置 API 通过环境变量启用,未授权的集群注册入口随即可用。

启动时不需要真实的 Kafka 代理。漏洞利用脚本会单独启动一个攻击者控制的代理;在 JMX 收集运行之前,Kafka UI API 接受任意 bootstrapServers 值而不会主动连接验证。

下载环境文件: env.zip — 或直接访问单个文件:env/docker-compose.yml

启动环境

docker compose -f env/docker-compose.yml up -d

服务暴露在 http://127.0.0.1:8080。容器名称为 cve-2024-32030-kafka-ui,JVM 以服务用户 kafkaui(pid 1)运行。

确认漏洞底座

启动后运行以下检查,验证当前运行的是未修补版本:

# 1. API 正常响应
curl -s http://127.0.0.1:8080/actuator/health
# 预期输出: {"status":"UP",...}

# 2. 认证已禁用,动态配置已激活
curl -s http://127.0.0.1:8080/api/config | grep -o '"type":"DISABLED"'
# 预期输出: "type":"DISABLED"

# 3. JVM 服务用户及漏洞类路径
docker exec cve-2024-32030-kafka-ui ps -o user,args -p 1
docker exec cve-2024-32030-kafka-ui sh -c 'unzip -l /kafka-ui-api.jar | grep -E "commons-collections-3.2.2|scala-library-2.13"'
# 预期输出: 用户为 kafkaui;列表中同时包含 commons-collections-3.2.2.jar 和 scala-library-2.13.x.jar

容器通过 extra_hosts: host.docker.internal:host-gateway 配置了 host.docker.internal 别名,使 Kafka UI JVM 可以向宿主机上运行的监听器发起出站 JRMP 连接。


3. 漏洞利用

下载利用文件: exploit.zip — 或浏览单个文件:exploit/run.shexploit/JrmpExploit.java

攻击由编排脚本 exploit/run.sh 驱动,该脚本负责编译 JRMP 监听器、启动攻击者 Kafka 代理、注册恶意集群配置,并在退出时自动清理。

前置条件

  • 漏洞 Kafka UI 容器正在运行(参见第 2 节)。
  • 宿主机上 Docker 可用(脚本使用 Docker 编译并运行监听器)。
  • 宿主机 TCP 端口 1718(JRMP 监听器)和 9092(攻击者 Kafka 代理)空闲可用。

执行利用

生成一个本次运行专用的随机 nonce,再调用脚本:

NONCE="poc$(date +%s)$RANDOM"
bash exploit/run.sh http://127.0.0.1:8080 1718 /tmp/pwned "$NONCE"
参数 说明
target_url http://127.0.0.1:8080 Kafka UI HTTP API(未授权)
jrmp_port 1718 JRMP 监听器的宿主机端口
marker_path /tmp/pwned 标记文件路径前缀;利用程序写入 /tmp/pwned.<nonce>
nonce poc<epoch><rand> 本次运行专用 nonce——防止预先植入或过期文件通过检查

脚本执行步骤

  1. eclipse-temurin:17-jdk 容器中,使用目标程序自带的 scala-library-2.13.9.jarcommons-collections-3.2.2.jar(存放在 exploit/libs/)编译 exploit/JrmpExploit.java
  2. 启动攻击者 Kafka 代理(apache/kafka:3.7.0,容器名 cve-2024-32030-poc-broker),广播地址为 host.docker.internal:9092,使 Kafka UI 的管理客户端能够发现代理节点,以便在触发 JMX 收集之前建立连接。
  3. 在宿主机端口 1718 上启动 JRMP 监听器。
  4. 发送 PUT /api/config——一个未授权请求,注册一个集群,其 metrics 字段(type: JMXhost: host.docker.internalport: 1718)将 JVM 的 JMX 客户端定向至攻击者监听器。
  5. 等待 Kafka UI 30 秒一次的定时指标收集周期。第一个 JMX 连接接收第一阶段载荷(Scala 利用链设置绕过属性);第二个连接接收第二阶段载荷(CC7 利用链调用 Runtime.exec("touch /tmp/pwned.<NONCE>"))。
  6. 退出时通过 shell trap 清理监听器和代理容器。

两个调度周期完成大约需要两分钟,请耐心等待。

确认执行成功——独立验证

验证通过完全独立于利用脚本输出的渠道进行:直接对 Kafka UI 容器执行 shell 命令,检查标记文件是否由 Kafka UI JVM 创建:

docker exec cve-2024-32030-kafka-ui stat -c '%U %n' /tmp/pwned.<NONCE>
# 预期输出: kafkaui /tmp/pwned.<NONCE>

在已验证的复现运行中:

docker exec cve-2024-32030-kafka-ui ls -l /tmp/pwned.poc178039439726378
  -> -rw-r--r-- 1 kafkaui kafkaui 0 Jun 2 10:01 /tmp/pwned.poc178039439726378
docker exec cve-2024-32030-kafka-ui stat -c '%U %n' /tmp/pwned.poc178039439726378
  -> kafkaui /tmp/pwned.poc178039439726378

这两个属性共同确认任意代码在漏洞进程内部得到执行:

  • nonce 是新鲜的 — 标记文件携带仅为本次运行生成的 nonce;任何预先植入或过期的文件都无法匹配它。
  • 所有者是 kafkaui — 文件由 JVM 的 Runtime.exec 创建,而 Runtime.exec 在 JVM 自身身份(pid 1,用户 kafkaui)下运行。利用脚本从未直接写入容器文件系统,也没有与容器共享任何卷。

辅助证据(非必要):监听器日志记录了 call #1 -> delivering STAGE 1 (set property),随后是 call #2 -> delivering STAGE 2 (CC7 exec)。Kafka UI 自身日志显示,在利用链触发的瞬间,JmxMetricsRetriever.withJmxConnector 处抛出了 java.io.StreamCorruptedException——利用链在漏洞 JMX 帧的反序列化过程中执行后,该异常随即抛出。修复补丁移除的正是这条异常路径。

环境清理

docker compose -f env/docker-compose.yml down -v

该命令停止并移除 Kafka UI 容器,同时清理所有卷(包括标记文件)。


4. 安全建议

修复方案

升级至 Kafka UI v0.7.2 或更高版本。修复补丁于 2024-04-10 发布(提交 83b5a60,PR #4427)。该修复从传递性依赖树中移除了 commons-collections:3.2.2,并用 commons-collections4:4.4 替换。后者的函数类完全不实现 Serializable 接口,无论系统属性状态如何,Scala 绕过利用链和 CC7 远程代码执行利用链均无法从类路径中组装。

如果无法立即升级,可采取以下缓解措施:

  • 禁用动态集群配置。 移除或省略 -Ddynamic.config.enabled=true 参数。没有该参数,新集群无法通过未授权的 HTTP API 注册,从而阻断主要攻击入口。集群只能通过应用程序配置文件静态定义,而只有具有文件系统访问权限的管理员才能添加。
  • 启用身份验证。 Kafka UI 支持多种身份验证机制(OAuth2、LDAP、基本认证)。启用其中任何一种均可阻止未授权的 PUT /api/config 请求,将攻击门槛从仅需网络访问提升至需要有效凭据。
  • 限制网络访问。 如果 Kafka UI 实例仅供内部使用,可通过防火墙或服务网格规则限制能够访问 8080 端口的主机,即使没有应用层控制也能降低攻击面。

禁用动态配置或添加身份验证只能移除未授权注册入口,并不会从类路径中删除利用链类。若攻击者对目标 Kafka UI 已配置监控的 Kafka 代理拥有网络层访问权限,仍可直接操控 JMX 响应触发漏洞。升级至 v0.7.2 是唯一完整的修复方案。

参考资料