CVE-2026-47888

Denial of Service
Affects
Spring Framework
in
Spring
No items found.
Versions
>=5.2.0 <=5.2.25, >=5.3.0 <=5.3.49, >=6.0.0 <=6.0.30, >=6.1.0 <=6.1.28, >=6.2.0 <=6.2.19, >=7.0.0 <=7.0.8
Exclamation circle icon
Patch Available

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

Overview

Spring Framework is a widely used application framework for the Java platform that provides the core programming and configuration model for modern Java enterprise applications, including messaging support in the spring-messaging module. Among other protocols, spring-messaging integrates the RSocket binary protocol: RSocketMessageHandler lets an application expose @MessageMapping and @ConnectMapping handler methods as an RSocket server, and its responder() method produces the server-side acceptor that handles every new connection. Each RSocket connection begins with a SETUP frame sent by the client, whose metadata section is parsed by the configured MetadataExtractor to build the message headers that route the connection to a handler method.

A denial of service vulnerability (CVE-2026-47888) has been identified in RSocketMessageHandler, which allows attackers to exhaust the memory of an affected RSocket server by repeatedly opening connections whose SETUP frame carries malformed metadata. When parsing the metadata of a SETUP frame fails, the Netty buffer that backs the frame is never released, so every such connection attempt permanently strands one buffer and the server's pooled or direct memory grows without bound. Only applications that expose an RSocket server through RSocketMessageHandler.responder() are exposed; RSocket client usage through RSocketRequester is not affected.

MITRE's CWE-401 describes this weakness as one where the product does not sufficiently track and release allocated memory after it has been used, making the memory unavailable for reallocation and reuse.

The advisory rates this vulnerability Medium severity with the CVSS v3.1 vector AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L, which corresponds to a base score of 4.3. The attack vector is Network because the trigger is a remote RSocket connection attempt, attack complexity is Low because a single malformed SETUP frame is enough to strand a buffer and no special conditions are required, the advisory assesses the privileges required as Low, and the Availability impact is Low because each malformed connection leaks one buffer and memory is exhausted only through repeated attempts rather than by a single request.

This issue affects Spring Framework >=5.2.0 <=5.2.25, >=5.3.0 <=5.3.49, >=6.0.0 <=6.0.30, >=6.1.0 <=6.1.28, >=6.2.0 <=6.2.19, and >=7.0.0 <=7.0.8, including the End-of-Life 5.3.x, 6.1.x, and 6.2.x lines supported by NES for Spring Framework.

Details

Module Info

Vulnerability Info

This Medium-severity vulnerability is found in the spring-messaging package in multiple versions of Spring Framework. The server-side acceptor returned by RSocketMessageHandler.responder() wraps each new connection in a MessagingRSocket and hands it the client's SETUP frame through handleConnectionSetupPayload. RSocket frames are backed by reference-counted Netty buffers. For ordinary request frames the RSocket transport increments the reference count before delivering the payload and expects the handler to release it, but the connection SETUP payload is delivered without that extra reference, so MessagingRSocket takes one itself to keep the two paths uniform:

public Mono<Void> handleConnectionSetupPayload(ConnectionSetupPayload payload) {
    // frameDecoder does not apply to connectionSetupPayload
    // so retain here since handle expects it.
    payload.retain();
    return handle(payload, FrameType.SETUP);
}

handle then builds the message headers from the frame's metadata before it releases the payload:

private Mono<Void> handle(Payload payload, FrameType frameType) {
    MessageHeaders headers = createHeaders(payload, frameType, null);
    DataBuffer dataBuffer = retainDataAndReleasePayload(payload);
    int refCount = refCount(dataBuffer);
    Message<?> message = MessageBuilder.createMessage(dataBuffer, headers);
    return Mono.defer(() -> this.messageHandler.handleMessage(message))
            .doFinally(s -> {
                if (refCount(dataBuffer) == refCount) {
                    DataBufferUtils.release(dataBuffer);
                }
            });
}

private MessageHeaders createHeaders(
        Payload payload, FrameType frameType, @Nullable AtomicReference<Flux<Payload>> responseRef) {

    MessageHeaderAccessor headers = new MessageHeaderAccessor();
    headers.setLeaveMutable(true);

    Map<String, Object> metadataValues = this.metadataExtractor.extract(payload, this.metadataMimeType);

    metadataValues.putIfAbsent(MetadataExtractor.ROUTE_KEY, "");
    for (Map.Entry<String, Object> entry : metadataValues.entrySet()) {
        if (entry.getKey().equals(MetadataExtractor.ROUTE_KEY)) {
            RouteMatcher.Route route = this.routeMatcher.parseRoute((String) entry.getValue());
            headers.setHeader(DestinationPatternsMessageCondition.LOOKUP_DESTINATION_HEADER, route);
        }
        else {
            headers.setHeader(entry.getKey(), entry.getValue());
        }
    }
    // ... remaining headers ...
    return headers.getMessageHeaders();
}

The only place the payload's reference is given back is PayloadUtils.retainDataAndReleasePayload, which slices out the data portion and then releases the payload in a finally block:

public static DataBuffer retainDataAndReleasePayload(Payload payload, DataBufferFactory bufferFactory) {
    try {
        if (bufferFactory instanceof NettyDataBufferFactory nettyBufferFactory) {
            ByteBuf byteBuf = payload.sliceData().retain();
            return nettyBufferFactory.wrap(byteBuf);
        }
        else {
            return bufferFactory.wrap(payload.getData());
        }
    }
    finally {
        if (payload.refCnt() > 0) {
            payload.release();
        }
    }
}

createHeaders runs first and is not guarded. It calls MetadataExtractor.extract, which iterates the composite metadata entries of the SETUP frame and runs the registered decoder for each one, and then RouteMatcher.parseRoute on the extracted route. Any exception raised there, for example composite metadata framing that does not match the declared metadata MIME type, an entry the registered decoder rejects, or a route the matcher rejects, propagates out of handle before retainDataAndReleasePayload is ever reached. The connection fails as expected, but the reference taken by payload.retain() in handleConnectionSetupPayload is never released. The RSocket transport releases only its own reference, leaving the SETUP frame's ByteBuf with a reference count of one for the lifetime of the process.

A remote client can trigger this on every connection attempt simply by sending a SETUP frame with metadata that the server's MetadataExtractor cannot parse. No handler method is invoked and no application-level authentication runs before the headers are built, so nothing in the application code path sees the attempt. Each rejected connection strands one pooled buffer, and an attacker who repeats the attempt drives the server's direct memory to exhaustion, at which point Netty allocation fails and the server can no longer accept or serve connections.

This vulnerability was introduced in 2019 with Spring Framework 5.2.0.RELEASE.

Mitigation

Only recent versions of Spring Framework receive community support. The 5.3.x, 6.1.x, and 6.2.x lines are End-of-Life and will not receive public updates to address this issue, so there is no publicly available fix for those lines other than through a commercial support partner.

Applications that do not expose an RSocket server through RSocketMessageHandler.responder(), for example those that only use RSocketRequester as a client, are not exposed. For servers that cannot upgrade immediately, restricting which clients are able to open RSocket connections at the network or transport level limits who can trigger the leak, but there is no configuration option within Spring Framework that prevents it.

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

  • Upgrade to a currently supported version of Spring Framework. The open-source fix ships in Spring Framework 7.0.9 on the 7.0.x line.
  • Leverage a commercial support partner like HeroDevs for post-EOL security support, which provides the fix for the 5.3.x, 6.1.x, and 6.2.x lines in nes-v5.3.54, nes-v6.1.30, and nes-v6.2.21.

Credits

  • No public finder credit is listed in the advisory sources checked for this entry.
Vulnerability Details
Severity
Level
CVSS Assessment
Low
>=0 <4
Medium
>=4 <6
High
>=6 <8
Critical
>=8 <10
Medium
ID
CVE-2026-47888
PROJECT Affected
Spring Framework
Versions Affected
>=5.2.0 <=5.2.25, >=5.3.0 <=5.3.49, >=6.0.0 <=6.0.30, >=6.1.0 <=6.1.28, >=6.2.0 <=6.2.19, >=7.0.0 <=7.0.8
NES Versions Affected
Published date
August 29, 2026
≈ Fix date
August 25, 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.