
POC для уязвимости десериализации Spring Kafka CVE-2023-34040
Сначала запустите 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, после чего завершит работу.
Для успешной эксплуатации этой уязвимости на consumer должны быть включены один или оба из следующих флагов:
CheckDeserExWhenValueNull
CheckDeserExWhenKeyNull
Это делается в приложении Consumer в методе KafkaConsumerConfig.greetingKafkaListenerContainerFactory()
Существует две полезные нагрузки, которые можно активировать; по умолчанию используется RCE-полезная нагрузка.
Чтобы включить DoS-полезную нагрузку, измените метод KafkaApplication.sendGreetingMessage()
Измените полезную нагрузку, добавляемую как заголовок, на dosPayload, затем пересоберите и запустите Producer.
ПРИМЕЧАНИЕ: Поскольку DoS происходит при чтении сообщения, и чтение никогда не завершается, сообщение остаётся в очереди до тех пор, пока не будет удалено вручную или не истечёт время хранения сообщения. Это увеличивает эффективность DoS, так как делает эту очередь бесполезной до момента ручного вмешательства или истечения времени хранения. Потенциально это может привести к потере данных для сообщений, отправленных сразу после DoS-сообщения.
Чтобы включить DoS-полезную нагрузку, измените метод KafkaApplication.sendGreetingMessage()
Измените полезную нагрузку, добавляемую как заголовок, на rcePayload, затем пересоберите и запустите Producer. По умолчанию выполняется команда touch /tmp/newfile. Чтобы проверить успешность атаки, найдите файл с именем newfile в /tmp.
Если вы работаете в Windows, вы можете изменить строку команды на более подходящую.
Этот Gadget — это всего лишь POC. Для реального RCE необходимо, чтобы в classpath Consumer был доступен класс-гаджет.
Отказ в обслуживании не требует наличия каких-либо специфических классов-гаджетов в classpath Consumer. Он основан на создании модифицированной версии класса org.springframework.kafka.support.serializer.DeserializationException, содержащей Object.
Это упрощает добавление любой полезной нагрузки в сериализованный объект.
Этот модифицированный класс называется xrg.springframework.kafka.support.serializer.DeserializationException (обратите внимание на x в начале имени пакета).
После внедрения полезной нагрузки (в данном случае атака в стиле «миллиард смеха» с использованием java.util.Set и java.lang.Object) она сериализуется.
Затем двоичные данные изменяются так, чтобы заменить x на o, чтобы соответствовать ожиданиям consumer.
Затем этот сериализованный класс исключения добавляется как заголовок сообщения как в заголовок springDeserializerExceptionValue, так и в springDeserializerExceptionKey.
Затем consumer считывает их, если ключ или сообщение равны null. После этого просто убедитесь, что ключ или сообщение равны 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. Но обратите внимание: проверяется только класс верхнего уровня и только его имя (которое атакующий может изменить). Таким образом, любая полезная нагрузка ниже этого уровня десериализуется.