CVE-2026-59317

Denial of Service
Affects
Spring for Apache Kafka
in
Spring
No items found.
Versions
>=2.7.0 <=2.8.12, >=2.9.0 <=2.9.14, >=3.0.0 <=3.3.16, >=4.0.0 <=4.0.6, 4.1.0
Exclamation circle icon
Patch Available

This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.

Overview

Spring for Apache Kafka applies core Spring Framework concepts to Kafka-based messaging. It provides a KafkaTemplate as a high-level abstraction for publishing records, message-driven POJOs behind listener containers, and declarative non-blocking retry support in which failed records are forwarded to a series of retry topics and finally to a dead-letter topic. It is published to Maven Central as org.springframework.kafka:spring-kafka.

A Denial of Service (DoS) vulnerability (CVE-2026-59317) has been identified in the retry topic recovery path of DeadLetterPublishingRecovererFactory, which allows attackers who can write to a consumed topic to stall processing of a partition indefinitely by attaching a malformed retry topic timestamp header to a record.

Per CWE: The product receives input that is expected to specify a quantity (such as size or length), but it does not validate or incorrectly validates that the quantity has the required properties. Specified quantities include size, length, frequency, price, rate, number of operations, time, and others. Code may rely on specified quantities to allocate resources, perform calculations, control iteration, etc.

This issue affects the non-blocking retry topic and dead-letter publishing support of Spring for Apache Kafka.

Details

Module Info

Vulnerability Info

This Medium-severity vulnerability is found in the org.springframework.kafka:spring-kafka package in the non-blocking retry topic support of Spring for Apache Kafka.

When a listener annotated with RetryableTopic fails, DeadLetterPublishingRecovererFactory builds the headers for the retry or dead-letter record. It reads the retry_topic-original-timestamp header straight off the inbound ConsumerRecord and hands the raw bytes to the BigInteger byte-array constructor with no length or format validation:

@Nullable
private Header getOriginaTimeStampHeader(ConsumerRecord<?, ?> consumerRecord) {
    return consumerRecord.headers()
            .lastHeader(RetryTopicHeaders.DEFAULT_HEADER_ORIGINAL_TIMESTAMP);
}

private byte[] getOriginalTimestampHeaderBytes(ConsumerRecord<?, ?> consumerRecord) {
    Header currentOriginalTimestampHeader = getOriginaTimeStampHeader(consumerRecord);
    return currentOriginalTimestampHeader != null
            ? currentOriginalTimestampHeader.value()
            : BigInteger.valueOf(consumerRecord.timestamp()).toByteArray();
}

private long getOriginalTimestampHeaderLong(ConsumerRecord<?, ?> consumerRecord) {
    Header currentOriginalTimestampHeader = getOriginaTimeStampHeader(consumerRecord);
    return currentOriginalTimestampHeader != null
            ? new BigInteger(currentOriginalTimestampHeader.value()).longValue()
            : consumerRecord.timestamp();
}

The bytes returned by that accessor flow into the destination resolution step, where they are decoded to compute the next execution timestamp:

private long getNextExecutionTimestamp(String mainListenerId, ConsumerRecord<?, ?> consumerRecord, Exception e,
        byte[] originalTimestampHeader) {

    long originalTimestamp = new BigInteger(originalTimestampHeader).longValue();
    long failureTimestamp = getFailureTimestamp(e);
    long nextExecutionTimestamp = failureTimestamp + this.destinationTopicResolver
            .resolveDestinationTopic(mainListenerId, consumerRecord.topic(), getAttempts(consumerRecord), e,
                    originalTimestamp)
            .getDestinationDelay();
    ...
}

Kafka record headers are attacker-controlled data: any producer with write access to a topic consumed by a retryable listener can set retry_topic-original-timestamp to an arbitrary byte array, including a zero-length one. The BigInteger byte-array constructor rejects an empty array with a NumberFormatException, and an oversized array silently decodes to a value far outside the millisecond timestamp domain. Because the header is only ever read when the listener has already failed, the exception is thrown inside the recovery path itself: dead-letter publication is aborted, the error handler treats the recovery as failed and seeks back to the offending offset, and the container redelivers the same record. Nothing advances the offset, so the poison record is reprocessed in a tight loop and consumption of that partition stops for every well-formed record queued behind it.

The same unvalidated decoding is present on the back off side of the retry flow, where KafkaBackoffAwareMessageListenerAdapter reads the retry_topic-backoff-timestamp header of a record delivered from a retry topic:

private Optional<Long> maybeGetBackoffTimestamp(ConsumerRecord<K, V> data) {
    return Optional
            .ofNullable(data.headers().lastHeader(this.backoffTimestampHeader))
            .map(timestampHeader -> new BigInteger(timestampHeader.value()).longValue())
            .filter(ts -> ts - Instant.now(this.clock).toEpochMilli() <= MAX_BACKOFF_MS);
}

A record carrying a zero-length back off timestamp header raises the same NumberFormatException before the adapter can decide whether to pause the partition, so a single crafted record is enough to disrupt the retry machinery without any authentication beyond ordinary produce rights on the topic. Remediation is to treat a null, zero-length, or oversized header value as absent and fall back to the consumer record timestamp, which keeps recovery on the normal path.

Mitigation

Only recent versions of Spring for Apache Kafka receive community support. Older lines are End-of-Life and will not receive public updates to address this issue.

Users of the affected components should apply one of the following mitigations:

  • Upgrade to a currently supported version of Spring for Apache Kafka.
  • Restrict produce permissions on topics consumed by retryable listeners so that untrusted clients cannot attach arbitrary record headers.
  • Leverage a commercial support partner like HeroDevs for post-EOL security support.

Credits

  • No individual finder is publicly credited for this issue.
Vulnerability Details
Severity
Level
CVSS Assessment
Low
>=0 <4
Medium
>=4 <6
High
>=6 <8
Critical
>=8 <10
Medium
ID
CVE-2026-59317
PROJECT Affected
Spring for Apache Kafka
Versions Affected
>=2.7.0 <=2.8.12, >=2.9.0 <=2.9.14, >=3.0.0 <=3.3.16, >=4.0.0 <=4.0.6, 4.1.0
NES Versions Affected
Published date
August 25, 2026
≈ Fix date
August 27, 2026
Category
Denial of Service
Vex Document
Download VEXHow do I use it?
Sign up for the latest vulnerability alerts fixed in
NES for Spring
Rss feed icon
Subscribe via RSS
or

By submitting the form I acknowledge receipt of our Privacy Policy.

Thanks for signing up for our Newsletter! We look forward to connecting with you.
Oops! Something went wrong while submitting the form.