CVE-2026-87022
Patch Available.
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Apache Tomcat is an open-source implementation of the Jakarta Servlet, Jakarta Pages, Jakarta Expression Language, Jakarta WebSocket, Jakarta Annotations, and Jakarta Authentication specifications. It serves Java web applications either as a standalone servlet container and HTTP server or embedded inside an application through the tomcat-embed-core artifact. Its Catalina servlet container (org.apache.tomcat:tomcat-catalina) supplies the request-processing pipeline, including Valves such as RewriteValve, along with connectors, realms, and session management.
An HTTP request smuggling vulnerability (CVE-2026-87022) has been identified in the Apache Tomcat WebSocket per-message-deflate transformation, which allows a peer to hide part of a compressed WebSocket message from the receiving endpoint. The hidden tail is silently dropped from the message the application is handed and can be reinterpreted as separate message content, so the endpoint and anything that inspected the wire bytes disagree about where one message ends and the next begins.
Per OWASP: HTTP Request Smuggling is a class of vulnerabilities caused by inconsistencies in how HTTP requests are parsed by frontend and backend components. When intermediaries such as reverse proxies, load balancers, or API gateways interpret request boundaries differently from backend servers, attackers may inject or "smuggle" hidden requests that are processed out of sequence.
This issue affects multiple versions of Apache Tomcat below 11.0.26.
Details
Module Info
- Product: Apache Tomcat
- Affected packages: tomcat-websocket, tomcat-embed-websocket
- Affected versions: >=7.0.56 <=7.0.109, >=8.5.0 <=8.5.100, >=9.0.0.M1 <9.0.122, >=10.1.0-M1 <10.1.60, >=11.0.0-M1 <11.0.26
- GitHub repository: https://github.com/apache/tomcat
- Published packages: https://central.sonatype.com/artifact/org.apache.tomcat/tomcat-websocket, https://central.sonatype.com/artifact/org.apache.tomcat.embed/tomcat-embed-websocket
- Package manager: Maven
- Fixed in:
- NES for Apache Tomcat 8.5.100-tomcat-8.5.112
- Apache Tomcat 11.0.26, 10.1.60, 9.0.122 (OSS)
Vulnerability Info
This Low-severity vulnerability is found in the org.apache.tomcat:tomcat-websocket package in the 8.5.x line of Apache Tomcat, in the org.apache.tomcat.websocket.PerMessageDeflate transformation that implements the WebSocket per-message-deflate extension negotiated during the opening handshake.
When per-message-deflate is in use, an incoming message is decompressed by feeding the frame payload to a java.util.zip.Inflater. RFC 7692 section 7.2.1 explicitly permits a sender to compress a single message as several DEFLATE blocks with any mix of BFINAL values, including a block that sets BFINAL=1 while further blocks of the same message follow. A java.util.zip.Inflater treats BFINAL=1 as the end of the compressed stream: finished() becomes true, the bytes after that block stay unconsumed and are reported by getRemaining(), and every subsequent call to inflate() returns 0 without consuming anything.
The decompression loop never asked whether input was left over. Once inflate() returned 0 and the inflater no longer needed input, the transformation simply declared the frame complete:
int written;
boolean usedEomBytes = false;
while (dest.remaining() > 0 || usedEomBytes) {
// Space available in destination. Try and fill it.
try {
written = inflater.inflate(dest.array(), dest.arrayOffset() + dest.position(), dest.remaining());
} catch (DataFormatException e) {
throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e);
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}
dest.position(dest.position() + written);
if (inflater.needsInput() && !usedEomBytes) {
readBuffer.clear();
TransformationResult nextResult = next.getMoreData(opCode, fin, (rsv ^ RSV_BITMASK), readBuffer);
inflater.setInput(readBuffer.array(), readBuffer.arrayOffset(), readBuffer.position());
// ... handling of UNDERFLOW, OVERFLOW and end-of-message bytes
} else if (written == 0) {
if (fin && (isServer && !clientContextTakeover || !isServer && !serverContextTakeover)) {
try {
inflater.reset();
} catch (NullPointerException e) {
throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e);
}
}
return TransformationResult.END_OF_FRAME;
}
}There is no test of inflater.finished() against inflater.getRemaining() anywhere on this path, and the offset and length of the bytes last handed to setInput() are not tracked, so the transformation has no way to locate or re-feed the unconsumed tail. A client that compresses one logical message into two blocks and sets BFINAL=1 on the first block therefore gets the first block delivered to the endpoint as a complete message while the second block, which can carry arbitrary attacker-chosen content, is discarded or picked up as the start of separate message content. Any proxy, gateway, logging filter, or WebSocket subprotocol validator that decompressed the same frames correctly sees a different message than the application endpoint does, which is the message-boundary desynchronization that makes this a smuggling issue rather than a parsing bug. The affected code path is reachable by any peer that is allowed to negotiate the per-message-deflate extension, including anonymous clients of a WebSocket endpoint such as those shipped in the examples web application.
This vulnerability was introduced in 2014 with Apache Tomcat 8.0.11.
Mitigation
Only recent versions of Apache Tomcat are community-supported. The community support version will not receive any updates to address this issue. For more information, see here.
Users of the affected components should apply one of the following mitigations:
- Upgrade to a patched version of Apache Tomcat.
- Remove the examples web application, which upstream lists as a mitigation because it exposes WebSocket endpoints that negotiate per-message-deflate.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- krsecurity(kongr) (finder)