CVE-2026-47891
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 the reactive Spring WebFlux web stack. WebFlux decodes XML request bodies through pluggable codecs in the spring-web module. One of them, Jaxb2XmlDecoder, unmarshals an XML stream into JAXB-annotated objects, and when the optional com.fasterxml:aalto-xml async parser is on the classpath it drives XmlEventDecoder to turn each incoming data buffer into XML events as they arrive rather than waiting for the whole body. Both decoders expose a maxInMemorySize limit that is meant to cap how much of a request is buffered in memory.
A denial of service vulnerability (CVE-2026-47891) has been identified in Jaxb2XmlDecoder, which allows attackers to exhaust the memory of an affected server by sending a single XML document whose matched element aggregates an unbounded number of XML events, because the maxInMemorySize limit is enforced in the wrong component and is silently bypassed during Aalto async parsing. An application is only exposed when it decodes XML request bodies into a Flux of JAXB-annotated objects and has the optional com.fasterxml:aalto-xml parser on its classpath. The upstream advisory attributes the issue to spring-webflux because WebFlux is the web stack that registers and drives the decoder, but the vulnerable code itself lives in the spring-web artifact.
The relevant weakness class is CWE-770, defined as a product that allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.
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 an ordinary XML HTTP request, attack complexity is Low because a single crafted document reaches the flaw once the preconditions hold, privileges are Low because XML-consuming endpoints are typically available to authenticated users, and the Availability impact is Low because the oversized event list is buffered until the request drives the process toward an OutOfMemoryError while other impacts are absent.
This issue affects Spring Framework <=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
- Product: Spring Framework
- Affected packages:
org.springframework:spring-web,org.springframework:spring-webflux - Affected versions: <=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
- GitHub repository: https://github.com/spring-projects/spring-framework
- Published packages: https://central.sonatype.com/artifact/org.springframework/spring-web
- Package manager: Maven
- Fixed in:
- NES for Spring Framework nes-v5.3.54, nes-v6.1.30, and nes-v6.2.21
- Spring Framework 7.0.9 (OSS)
Vulnerability Info
This Medium-severity vulnerability is found in the spring-web package in multiple versions of Spring Framework. The upstream advisory attributes it to spring-webflux because that is the web stack whose default codec configuration registers Jaxb2XmlDecoder when JAXB is present, but the vulnerable classes are in the spring-web artifact, under org/springframework/http/codec/xml. When a WebFlux endpoint decodes an XML request body into a Flux of JAXB-annotated objects and the com.fasterxml:aalto-xml async parser is on the classpath, Jaxb2XmlDecoder feeds the request buffers to XmlEventDecoder, which emits a stream of XMLEvents, and then groups those events by element so each matched element can be unmarshalled into one object.
Two different components share the work, and the maxInMemorySize limit is enforced in the one that does not actually hold the data. XmlEventDecoder counts the bytes it has received and, on every start and end of a depth-1 element, resets that counter to zero:
private void increaseByteCount(DataBuffer dataBuffer) {
if (this.maxInMemorySize > 0) {
if (dataBuffer.readableByteCount() > Integer.MAX_VALUE - this.byteCount) {
raiseLimitException();
}
else {
this.byteCount += dataBuffer.readableByteCount();
}
}
}
private void checkDepthAndResetByteCount(XMLEvent event) {
if (this.maxInMemorySize > 0) {
if (event.isStartElement()) {
this.byteCount = this.elementDepth == 1 ? 0 : this.byteCount;
this.elementDepth++;
}
else if (event.isEndElement()) {
this.elementDepth--;
this.byteCount = this.elementDepth == 1 ? 0 : this.byteCount;
}
}
}
The component that actually accumulates memory is the event splitter that Jaxb2XmlDecoder uses. It starts a list when a start element whose name matches the target JAXB type is seen at any depth, and keeps appending every event until the matching end element, with no size check of its own:
@Override
public void accept(XMLEvent event, SynchronousSink<List<XMLEvent>> sink) {
if (event.isStartElement()) {
if (this.barrier == Integer.MAX_VALUE) {
QName startElementName = event.asStartElement().getName();
if (this.names.contains(startElementName)) {
this.events = new ArrayList<>();
this.barrier = this.elementDepth;
}
}
this.elementDepth++;
}
if (this.elementDepth > this.barrier) {
Assert.state(this.events != null, "No XMLEvent List");
this.events.add(event);
}
if (event.isEndElement()) {
this.elementDepth--;
if (this.elementDepth == this.barrier) {
this.barrier = Integer.MAX_VALUE;
Assert.state(this.events != null, "No XMLEvent List");
sink.next(this.events);
}
}
}
The two views of the document do not line up. XmlEventDecoder measures bytes per top-level (depth-1) node and clears its counter at each depth-1 boundary, so the counter never grows large across a document with many top-level children. The splitter, meanwhile, holds every event of whichever element matches the target type, and that element can be the document root, spanning the entire body. When a client posts a single root element that matches the target JAXB type, every depth-1 child inside it resets XmlEventDecoder's counter, so byteCount never exceeds maxInMemorySize and no limit is ever raised, while the splitter's list grows with the whole document. The configured limit is silently bypassed and the server keeps allocating XMLEvent objects for as long as the client keeps sending data for that element.
An attacker who can reach such an endpoint sends one XML request whose matched element is arbitrarily large. The server buffers the growing event list in heap memory before the application sees a result, so a sufficiently large body, or a few concurrent requests, drives the JVM toward an OutOfMemoryError and makes the service unavailable. Only the streaming Flux decode path is affected: the single-object path routes through DataBufferUtils.join(input, this.maxInMemorySize), which enforces the limit as expected, and applications without the optional Aalto parser fall back to the same joining path, so they are not exposed.
Versions older than 5.1.11.RELEASE enforced no in-memory limit on this decode path at all, so they are exposed to the same unbounded buffering; this is why the advisory's oldest affected row carries no lower bound. This vulnerability was introduced in 2019 with Spring Framework 5.1.11.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 cannot upgrade immediately can reduce their exposure by removing the optional com.fasterxml:aalto-xml dependency where async XML parsing is not required, since without it XmlEventDecoder falls back to the joining path that honors maxInMemorySize, or by decoding XML bodies as a single object rather than a Flux, since the single-object path enforces the limit as well. Upgrading remains the only complete fix.
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.