
Spring Kafka 反序列化漏洞 CVE-2023-34040 的 POC
首先启动 Dockerized Kafka 实例。这将启动 Kafka 并在端口 29092 上可用。
docker-compose up
启动消费者应用程序。
cd spring-kafka-consumer
mvn clean install
mvn spring-boot:run
这将构建并启动消费者应用程序。消费者将等待消息最长 10 分钟,然后关闭。
生产者
cd spring-kafka-producer
mvn clean install
mvn spring-boot:run
这将构建并启动生产者应用程序。生产者将向 Kafka 队列发送一条消息,然后关闭。
要使此漏洞成功,需要在消费者上启用以下一个或两个标志:
CheckDeserExWhenValueNull
CheckDeserExWhenKeyNull
这在消费者应用程序的 KafkaConsumerConfig.greetingKafkaListenerContainerFactory() 中完成。
有两个可以触发的 payload,默认使用 RCE payload。
要启用 DoS payload,请修改方法 KafkaApplication.sendGreetingMessage(),将作为 header 添加的 payload 改为 dosPayload,然后重新构建并运行生产者。
注意:由于 DoS 发生在消息读取时,且读取永远不会完成,因此消息仍保留在队列中,直到手动删除或消息保留时间到期。这增加了 DoS 的威力,因为它会使该队列无法使用,直到手动干预或保留时间到期。可能导致在 DoS 消息之后立即发送的消息丢失。
要启用 DoS payload,请修改方法 KafkaApplication.sendGreetingMessage(),将作为 header 添加的 payload 改为 rcePayload,然后重新构建并运行生产者。默认运行的命令是 touch /tmp/newfile,要查看攻击是否成功,请在 /tmp 目录下查找名为 newfile 的文件。如果在 Windows 上运行,可以将命令字符串修改为更合适的命令。这个 Gadget 只是一个 POC。要在现实世界中实现 RCE,需要在消费者的类路径上存在一个 gadget 类。
拒绝服务攻击不需要消费者的类路径上存在任何特定的 gadget 类。它依赖于生成一个包含 Object 的 org.springframework.kafka.support.serializer.DeserializationException 类的修改版本。这使得我们可以轻松地将任何我们想要的 payload 添加到序列化对象中。这个修改后的类名为 xrg.springframework.kafka.support.serializer.DeserializationException(注意包名开头的 x)。一旦 payload 被注入,在本例中使用 java.util.Set 和 java.lang.Object 的 billion laughs 风格的攻击,它就会被序列化。然后修改二进制数据,将 x 改为 o,以匹配消费者期望的类名。
这个序列化的异常类随后被添加到消息头部的 springDeserializerExceptionValue 和 springDeserializerExceptionKey 中。如果键或消息为空,消费者就会读取它们。之后只需确保键或消息为空,消费者就会读取它。
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。但注意只检查了顶层类,而且只检查了类名(攻击者可以修改)。所以任何该层以下的 payload 都会被反序列化。