Contrast-Security-OSS/Spring-Kafka-POC-CVE-2023-34040
GitHub: Contrast-Security-OSS/Spring-Kafka-POC-CVE-2023-34040
一个用于复现 Spring-Kafka 反序列化漏洞 CVE-2023-34040 的概念验证工具,支持 RCE 和 DoS 两种攻击载荷。
Stars: 46 | Forks: 6
# Spring Kafka 反序列化 POC
## 环境要求
* Java 11
* Maven
* Docker(或 Kafka)
## 使用方法
首先启动 Docker 化的 Kafka 实例。这将启动 Kafka 并使其在 29092 端口可用。
```
docker-compose up
```
启动 Consumer 应用程序。
```
cd spring-kafka-consumer
mvn clean install
mvn spring-boot:run
```
这将构建并启动 Consumer 应用程序。Consumer 最多会等待 10 分钟以接收消息,超时后关闭。
Producer
```
cd spring-kafka-producer
mvn clean install
mvn spring-boot:run
```
这将构建并启动 Producer 应用程序。Producer 会向 Kafka 队列发送一条消息后关闭。
### Spring-Kafka-Consumer 配置
要使此漏洞利用成功,需要在 Consumer 上启用以下一个或两个标志:
`CheckDeserExWhenValueNull`
`CheckDeserExWhenKeyNull`
在 Consumer 应用程序中的 `KafkaConsumerConfig.greetingKafkaListenerContainerFactory()` 方法中进行配置。
### 载荷
有两种可触发的载荷,默认使用 RCE 载荷。
#### 拒绝服务(Denial of Service)
要启用 DOS 载荷,修改 `KafkaApplication.sendGreetingMessage()` 方法。
将作为 header 添加的载荷改为 `dosPayload`,然后重新构建并运行 Producer。
注意:由于 DOS 在消息读取时发生,且读取永远不会完成,消息会一直保留在队列中,直到手动删除或消息保留时间到期。这增加了 DOS 的效力,因为在手动干预或保留时间到期之前,该队列将不可用。
这可能导致紧随 DOS 消息之后发送的消息丢失。
#### RCE
要启用 RCE 载荷,修改 `KafkaApplication.sendGreetingMessage()` 方法。
将作为 header 添加的载荷改为 `rcePayload`,然后重新构建并运行 Producer。默认执行的命令是
`touch /tmp/newfile`,要验证攻击是否成功,请检查 /tmp 下是否存在名为 newfile 的文件。
如果在 Windows 上运行,可以将命令字符串修改为更适合的命令。
这个 Gadget 本质上只是一个 POC。要在真实世界中实现 RCE,Consumer 的类路径上需要有可用的 gadget 类。
#### 工作原理
拒绝服务不需要 Consumer 的类路径上有任何特定的 gadget 类。它依赖于生成一个修改过的类版本:
`org.springframework.kafka.support.serializer.DeserializationException`,其中包含一个 Object。
这使得我们可以轻松地将任意载荷添加到序列化对象中。
这个修改后的类名为 `xrg.springframework.kafka.support.serializer.DeserializationException`(注意包名开头的 x)。
注入载荷后,在本例中是一个使用 `java.util.Set` 和 `java.lang.Object` 的 billion laughs 风格攻击,然后进行序列化。
接着修改二进制数据,将 x 改为 o,以匹配 Consumer 所期望的类名。
这个序列化后的异常类随后被添加到消息 header 中,分别放入 `springDeserializerExceptionValue` header 和 `springDeserializerExceptionKey` header。
如果 key 或 message 为 null,Consumer 会读取这些 header。只需确保 key 或 message 为 null,Consumer 就会读取它。
Spring-Kafka 内部存在一些反序列化保护机制,位于 ListenerUtils 中。
```
public static DeserializationException byteArrayToDeserializationException(LogAccessor logger, byte[] value) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(value)) {
boolean first = true;
@Override
protected Class> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (this.first) {
this.first = false;
Assert.state(desc.getName().equals(DeserializationException.class.getName()),
"Header does not contain a DeserializationException");
}
return super.resolveClass(desc);
}
};
return (DeserializationException) ois.readObject();
}
catch (IOException | ClassNotFoundException | ClassCastException e) {
logger.error(e, "Failed to deserialize a deserialization exception");
return null;
}
}
```
可以看到有一个检查,确保顶层类是 `org.springframework.kafka.support.serializer.DeserializationException`。但注意只检查了顶层类,而且只检查了类名(攻击者有能力修改该名称)。因此该层级以下的任何载荷都会被反序列化。
标签:CVE-2023-34040, Docker, DoS, JS文件枚举, Kafka消费者, Maven, Maven, meg, POC, Proof of Concept, RCE, Spring Kafka, Spring框架, 信息安全, 反序列化漏洞, 域名枚举, 安全检查绕过, 安全防御评估, 序列化攻击, 拒绝服务, 攻击技术, 消息队列, 漏洞验证, 漏洞验证, 编程工具, 网络安全, 请求拦截, 远程代码执行, 隐私保护