Apache Tomcat: AJP response header mix-up
Description
A regression in the fix for bug 66512 in Apache Tomcat 11.0.0-M5, 10.1.8, 9.0.74 and 8.5.88 meant that, if a response did not include any HTTP headers no AJP SEND_HEADERS messare woudl be sent for the response which in turn meant that at least one AJP proxy (mod_proxy_ajp) would use the response headers from the previous request leading to an information leak.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A regression in Apache Tomcat's AJP SEND_HEADERS handling causes information leakage when a response has no HTTP headers.
Vulnerability
Overview
CVE-2023-34981 is a regression in the fix for Apache Tomcat bug 66512 affecting versions 11.0.0-M5, 10.1.8, 9.0.74, and 8.5.88 [1][2][3][4]. The root cause is that when a response contains no HTTP headers, the affected Tomcat versions do not send an AJP SEND_HEADERS message to the AJP connector. This omission causes at least one AJP proxy, mod_proxy_ajp, to reuse the response headers from the previous request, leading to an information leak [1][2].
Attack
Vector and Prerequisites
The vulnerability is triggered when an AJP proxy (specifically mod_proxy_ajp) is used in front of a vulnerable Tomcat instance [1]. An attacker can craft a request that results in a response with no HTTP headers; subsequent requests handled by the same proxy connection will then receive the previous response headers, potentially exposing sensitive data from other users or sessions [1][2]. No authentication is required to initiate the attack, though the attacker must be able to send requests through the AJP proxy.
Impact
Successful exploitation allows an attacker to leak HTTP response headers from a prior request processed by the same AJP proxy connection. This can include sensitive information such as session cookies, authentication tokens, or custom headers that may contain internal state or user-specific data [1][2]. The attack can lead to session hijacking, privilege escalation, or further compromise of the application.
Mitigation
Apache has released fixed versions: Tomcat 11.0.0-M6, 10.1.9, 9.0.75, and 8.5.89 [1][2][3][4]. Users running affected versions should upgrade immediately. No workaround is provided other than applying the patch. Both Tomcat 8.5.x and 10.0.x have reached end-of-life status; users on those branches should migrate to supported versions [1][2].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 11.0.0-M5, < 11.0.0-M6 | 11.0.0-M6 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 10.1.8, < 10.1.9 | 10.1.9 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 9.0.74, < 9.0.75 | 9.0.75 |
org.apache.tomcat:tomcat-coyoteMaven | >= 8.5.88, < 8.5.89 | 8.5.89 |
Affected products
4- osv-coords3 versionspkg:bitnami/tomcatpkg:maven/org.apache.tomcat.embed/tomcat-embed-corepkg:maven/org.apache.tomcat/tomcat-coyote
>= 8.5.88, < 8.5.89+ 2 more
- (no CPE)range: >= 8.5.88, < 8.5.89
- (no CPE)range: >= 11.0.0-M5, < 11.0.0-M6
- (no CPE)range: >= 8.5.88, < 8.5.89
- Apache Software Foundation/Apache Tomcatv5Range: 11.0.0-M5
Patches
4f0742f47b98aFix 66591 - Make sure every response includes a Send Headers packet
3 files changed · +99 −36
java/org/apache/coyote/ajp/AjpProcessor.java+39 −35 modified@@ -956,43 +956,47 @@ protected final void prepareResponse() throws IOException { responseMsgPos = -1; int numHeaders = headers.size(); - for (int i = 0; i < numHeaders; i++) { - if (i == 0) { - // Write AJP message header - responseMessage.reset(); - responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // Write HTTP response line - responseMessage.appendInt(statusCode); - // Reason phrase is optional but mod_jk + httpd 2.x fails with a null - // reason phrase - bug 45026 - tmpMB.setString(Integer.toString(response.getStatus())); - responseMessage.appendBytes(tmpMB); - - // Start headers - responseMessage.appendInt(numHeaders); - } + boolean needAjpMessageHeader = true; + while (needAjpMessageHeader) { + // Write AJP message header + responseMessage.reset(); + responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - try { - // Write headers - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseMessage.appendInt(hC); - } else { - responseMessage.appendBytes(hN); + // Write HTTP response line + responseMessage.appendInt(statusCode); + // Reason phrase is optional but mod_jk + httpd 2.x fails with a null + // reason phrase - bug 45026 + tmpMB.setString(Integer.toString(response.getStatus())); + responseMessage.appendBytes(tmpMB); + + // Start headers + responseMessage.appendInt(numHeaders); + + needAjpMessageHeader = false; + + for (int i = 0; i < numHeaders; i++) { + try { + // Write headers + MessageBytes hN = headers.getName(i); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responseMessage.appendInt(hC); + } else { + responseMessage.appendBytes(hN); + } + MessageBytes hV = headers.getValue(i); + responseMessage.appendBytes(hV); + } catch (IllegalArgumentException iae) { + // Log the problematic header + log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), + iae); + // Remove the problematic header + headers.removeHeader(i); + numHeaders--; + // Restart writing of AJP message + needAjpMessageHeader = true; + break; } - MessageBytes hV = headers.getValue(i); - responseMessage.appendBytes(hV); - } catch (IllegalArgumentException iae) { - // Log the problematic header - log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), - iae); - // Remove the problematic header - headers.removeHeader(i); - numHeaders--; - // Reset loop and start again - i = -1; } }
test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java+55 −1 modified@@ -883,6 +883,60 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=66591 + */ + @Test + public void testNoHeaders() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "bug66591", new NoHeadersServlet()); + ctx.addServletMappingDecoded("/", "bug66591"); + + tomcat.start(); + + SimpleAjpClient ajpClient = new SimpleAjpClient(); + ajpClient.setPort(getPort()); + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = ajpClient.createForwardMessage(); + forwardMessage.end(); + + TesterAjpMessage responseHeaderMessage = ajpClient.sendMessage(forwardMessage, null); + + // Expect 3 messages: headers, body chunk, end + Map<String, List<String>> responseHeaders = validateResponseHeaders(responseHeaderMessage, 200, "200"); + Assert.assertTrue(responseHeaders.isEmpty()); + + String body = extractResponseBody(ajpClient.readMessage()); + Assert.assertTrue(body.isEmpty()); + + validateResponseEnd(ajpClient.readMessage(), true); + + // Double check the connection is still open + validateCpong(ajpClient.cping()); + + ajpClient.disconnect(); + } + + + private static class NoHeadersServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.flushBuffer(); + } + } + + /** * Process response header packet and checks the status. Any other data is ignored. */ @@ -943,7 +997,7 @@ private String extractResponseBody(TesterAjpMessage message) throws Exception { Assert.assertEquals(0x03, message.readByte()); int len = message.readInt(); - Assert.assertTrue(len > 0); + Assert.assertTrue(len >= 0); return message.readString(len); }
webapps/docs/changelog.xml+5 −0 modified@@ -151,6 +151,11 @@ and <code>allowHostHeaderMismatch</code> as they have been removed in Tomcat 11 onwards. (markt) </update> + <fix> + <bug>66591</bug>: Fix a regression introduced in the fix for + <bug>66512</bug> that meant that an AJP Send Headers was not sent for + responses where no HTTP headers were set. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
2214c8030522Fix 66591 - Make sure every response includes a Send Headers packet
3 files changed · +113 −50
java/org/apache/coyote/ajp/AjpProcessor.java+53 −49 modified@@ -1058,59 +1058,63 @@ protected final void prepareResponse() throws IOException { responseMsgPos = -1; int numHeaders = headers.size(); - for (int i = 0; i < numHeaders; i++) { - if (i == 0) { - // Write AJP message header - responseMessage.reset(); - responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // Write HTTP response line - responseMessage.appendInt(statusCode); - if (sendReasonPhrase) { - String message = null; - if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER && - HttpMessages.isSafeInHttpHeader(response.getMessage())) { - message = response.getMessage(); - } - if (message == null) { - message = HttpMessages.getInstance(response.getLocale()).getMessage(response.getStatus()); - } - if (message == null) { - // mod_jk + httpd 2.x fails with a null status message - bug 45026 - message = Integer.toString(response.getStatus()); - } - tmpMB.setString(message); - } else { - // Reason phrase is optional but mod_jk + httpd 2.x fails with a null - // reason phrase - bug 45026 - tmpMB.setString(Integer.toString(response.getStatus())); + boolean needAjpMessageHeader = true; + while (needAjpMessageHeader) { + // Write AJP message header + responseMessage.reset(); + responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); + + // Write HTTP response line + responseMessage.appendInt(statusCode); + if (sendReasonPhrase) { + String message = null; + if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER && + HttpMessages.isSafeInHttpHeader(response.getMessage())) { + message = response.getMessage(); } - responseMessage.appendBytes(tmpMB); - - // Start headers - responseMessage.appendInt(numHeaders); + if (message == null) { + message = HttpMessages.getInstance(response.getLocale()).getMessage(response.getStatus()); + } + if (message == null) { + // mod_jk + httpd 2.x fails with a null status message - bug 45026 + message = Integer.toString(response.getStatus()); + } + tmpMB.setString(message); + } else { + // Reason phrase is optional but mod_jk + httpd 2.x fails with a null + // reason phrase - bug 45026 + tmpMB.setString(Integer.toString(response.getStatus())); } + responseMessage.appendBytes(tmpMB); - try { - // Write headers - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseMessage.appendInt(hC); - } else { - responseMessage.appendBytes(hN); + // Start headers + responseMessage.appendInt(numHeaders); + + needAjpMessageHeader = false; + + for (int i = 0; i < numHeaders; i++) { + try { + // Write headers + MessageBytes hN = headers.getName(i); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responseMessage.appendInt(hC); + } else { + responseMessage.appendBytes(hN); + } + MessageBytes hV = headers.getValue(i); + responseMessage.appendBytes(hV); + } catch (IllegalArgumentException iae) { + // Log the problematic header + log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), + iae); + // Remove the problematic header + headers.removeHeader(i); + numHeaders--; + // Restart writing of AJP message + needAjpMessageHeader = true; + break; } - MessageBytes hV = headers.getValue(i); - responseMessage.appendBytes(hV); - } catch (IllegalArgumentException iae) { - // Log the problematic header - log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), - iae); - // Remove the problematic header - headers.removeHeader(i); - numHeaders--; - // Reset loop and start again - i = -1; } }
test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java+55 −1 modified@@ -885,6 +885,60 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=66591 + */ + @Test + public void testNoHeaders() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "bug66591", new NoHeadersServlet()); + ctx.addServletMappingDecoded("/", "bug66591"); + + tomcat.start(); + + SimpleAjpClient ajpClient = new SimpleAjpClient(); + ajpClient.setPort(getPort()); + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = ajpClient.createForwardMessage(); + forwardMessage.end(); + + TesterAjpMessage responseHeaderMessage = ajpClient.sendMessage(forwardMessage, null); + + // Expect 3 messages: headers, body chunk, end + Map<String, List<String>> responseHeaders = validateResponseHeaders(responseHeaderMessage, 200, "200"); + Assert.assertTrue(responseHeaders.isEmpty()); + + String body = extractResponseBody(ajpClient.readMessage()); + Assert.assertTrue(body.isEmpty()); + + validateResponseEnd(ajpClient.readMessage(), true); + + // Double check the connection is still open + validateCpong(ajpClient.cping()); + + ajpClient.disconnect(); + } + + + private static class NoHeadersServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.flushBuffer(); + } + } + + /** * Process response header packet and checks the status. Any other data is ignored. */ @@ -950,7 +1004,7 @@ private String extractResponseBody(TesterAjpMessage message) throws Exception { Assert.assertEquals(0x03, message.readByte()); int len = message.readInt(); - Assert.assertTrue(len > 0); + Assert.assertTrue(len >= 0); return message.readString(len); }
webapps/docs/changelog.xml+5 −0 modified@@ -163,6 +163,11 @@ and <code>allowHostHeaderMismatch</code> as they have been removed in Tomcat 11 onwards. (markt) </update> + <fix> + <bug>66591</bug>: Fix a regression introduced in the fix for + <bug>66512</bug> that meant that an AJP Send Headers was not sent for + responses where no HTTP headers were set. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
2f0ca2378415Fix 66591 - Make sure every response includes a Send Headers packet
3 files changed · +99 −36
java/org/apache/coyote/ajp/AjpProcessor.java+39 −35 modified@@ -947,43 +947,47 @@ protected final void prepareResponse() throws IOException { responseMsgPos = -1; int numHeaders = headers.size(); - for (int i = 0; i < numHeaders; i++) { - if (i == 0) { - // Write AJP message header - responseMessage.reset(); - responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // Write HTTP response line - responseMessage.appendInt(statusCode); - // Reason phrase is optional but mod_jk + httpd 2.x fails with a null - // reason phrase - bug 45026 - tmpMB.setString(Integer.toString(response.getStatus())); - responseMessage.appendBytes(tmpMB); - - // Start headers - responseMessage.appendInt(numHeaders); - } + boolean needAjpMessageHeader = true; + while (needAjpMessageHeader) { + // Write AJP message header + responseMessage.reset(); + responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - try { - // Write headers - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseMessage.appendInt(hC); - } else { - responseMessage.appendBytes(hN); + // Write HTTP response line + responseMessage.appendInt(statusCode); + // Reason phrase is optional but mod_jk + httpd 2.x fails with a null + // reason phrase - bug 45026 + tmpMB.setString(Integer.toString(response.getStatus())); + responseMessage.appendBytes(tmpMB); + + // Start headers + responseMessage.appendInt(numHeaders); + + needAjpMessageHeader = false; + + for (int i = 0; i < numHeaders; i++) { + try { + // Write headers + MessageBytes hN = headers.getName(i); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responseMessage.appendInt(hC); + } else { + responseMessage.appendBytes(hN); + } + MessageBytes hV = headers.getValue(i); + responseMessage.appendBytes(hV); + } catch (IllegalArgumentException iae) { + // Log the problematic header + log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), + iae); + // Remove the problematic header + headers.removeHeader(i); + numHeaders--; + // Restart writing of AJP message + needAjpMessageHeader = true; + break; } - MessageBytes hV = headers.getValue(i); - responseMessage.appendBytes(hV); - } catch (IllegalArgumentException iae) { - // Log the problematic header - log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), - iae); - // Remove the problematic header - headers.removeHeader(i); - numHeaders--; - // Reset loop and start again - i = -1; } }
test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java+55 −1 modified@@ -885,6 +885,60 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=66591 + */ + @Test + public void testNoHeaders() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "bug66591", new NoHeadersServlet()); + ctx.addServletMappingDecoded("/", "bug66591"); + + tomcat.start(); + + SimpleAjpClient ajpClient = new SimpleAjpClient(); + ajpClient.setPort(getPort()); + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = ajpClient.createForwardMessage(); + forwardMessage.end(); + + TesterAjpMessage responseHeaderMessage = ajpClient.sendMessage(forwardMessage, null); + + // Expect 3 messages: headers, body chunk, end + Map<String, List<String>> responseHeaders = validateResponseHeaders(responseHeaderMessage, 200, "200"); + Assert.assertTrue(responseHeaders.isEmpty()); + + String body = extractResponseBody(ajpClient.readMessage()); + Assert.assertTrue(body.isEmpty()); + + validateResponseEnd(ajpClient.readMessage(), true); + + // Double check the connection is still open + validateCpong(ajpClient.cping()); + + ajpClient.disconnect(); + } + + + private static class NoHeadersServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.flushBuffer(); + } + } + + /** * Process response header packet and checks the status. Any other data is ignored. */ @@ -945,7 +999,7 @@ private String extractResponseBody(TesterAjpMessage message) throws Exception { Assert.assertEquals(0x03, message.readByte()); int len = message.readInt(); - Assert.assertTrue(len > 0); + Assert.assertTrue(len >= 0); return message.readString(len); }
webapps/docs/changelog.xml+5 −0 modified@@ -151,6 +151,11 @@ and <code>allowHostHeaderMismatch</code> as they have been removed in Tomcat 11 onwards. (markt) </update> + <fix> + <bug>66591</bug>: Fix a regression introduced in the fix for + <bug>66512</bug> that meant that an AJP Send Headers was not sent for + responses where no HTTP headers were set. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
739c7381aed2Fix 66591 - Make sure every response includes a Send Headers packet
3 files changed · +99 −36
java/org/apache/coyote/ajp/AjpProcessor.java+39 −35 modified@@ -956,43 +956,47 @@ protected final void prepareResponse() throws IOException { responseMsgPos = -1; int numHeaders = headers.size(); - for (int i = 0; i < numHeaders; i++) { - if (i == 0) { - // Write AJP message header - responseMessage.reset(); - responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - - // Write HTTP response line - responseMessage.appendInt(statusCode); - // Reason phrase is optional but mod_jk + httpd 2.x fails with a null - // reason phrase - bug 45026 - tmpMB.setString(Integer.toString(response.getStatus())); - responseMessage.appendBytes(tmpMB); - - // Start headers - responseMessage.appendInt(numHeaders); - } + boolean needAjpMessageHeader = true; + while (needAjpMessageHeader) { + // Write AJP message header + responseMessage.reset(); + responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS); - try { - // Write headers - MessageBytes hN = headers.getName(i); - int hC = Constants.getResponseAjpIndex(hN.toString()); - if (hC > 0) { - responseMessage.appendInt(hC); - } else { - responseMessage.appendBytes(hN); + // Write HTTP response line + responseMessage.appendInt(statusCode); + // Reason phrase is optional but mod_jk + httpd 2.x fails with a null + // reason phrase - bug 45026 + tmpMB.setString(Integer.toString(response.getStatus())); + responseMessage.appendBytes(tmpMB); + + // Start headers + responseMessage.appendInt(numHeaders); + + needAjpMessageHeader = false; + + for (int i = 0; i < numHeaders; i++) { + try { + // Write headers + MessageBytes hN = headers.getName(i); + int hC = Constants.getResponseAjpIndex(hN.toString()); + if (hC > 0) { + responseMessage.appendInt(hC); + } else { + responseMessage.appendBytes(hN); + } + MessageBytes hV = headers.getValue(i); + responseMessage.appendBytes(hV); + } catch (IllegalArgumentException iae) { + // Log the problematic header + log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), + iae); + // Remove the problematic header + headers.removeHeader(i); + numHeaders--; + // Restart writing of AJP message + needAjpMessageHeader = true; + break; } - MessageBytes hV = headers.getValue(i); - responseMessage.appendBytes(hV); - } catch (IllegalArgumentException iae) { - // Log the problematic header - log.warn(sm.getString("ajpprocessor.response.invalidHeader", headers.getName(i), headers.getValue(i)), - iae); - // Remove the problematic header - headers.removeHeader(i); - numHeaders--; - // Reset loop and start again - i = -1; } }
test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java+55 −1 modified@@ -883,6 +883,60 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se } + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=66591 + */ + @Test + public void testNoHeaders() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Tomcat.addServlet(ctx, "bug66591", new NoHeadersServlet()); + ctx.addServletMappingDecoded("/", "bug66591"); + + tomcat.start(); + + SimpleAjpClient ajpClient = new SimpleAjpClient(); + ajpClient.setPort(getPort()); + ajpClient.connect(); + + validateCpong(ajpClient.cping()); + + TesterAjpMessage forwardMessage = ajpClient.createForwardMessage(); + forwardMessage.end(); + + TesterAjpMessage responseHeaderMessage = ajpClient.sendMessage(forwardMessage, null); + + // Expect 3 messages: headers, body chunk, end + Map<String, List<String>> responseHeaders = validateResponseHeaders(responseHeaderMessage, 200, "200"); + Assert.assertTrue(responseHeaders.isEmpty()); + + String body = extractResponseBody(ajpClient.readMessage()); + Assert.assertTrue(body.isEmpty()); + + validateResponseEnd(ajpClient.readMessage(), true); + + // Double check the connection is still open + validateCpong(ajpClient.cping()); + + ajpClient.disconnect(); + } + + + private static class NoHeadersServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.flushBuffer(); + } + } + + /** * Process response header packet and checks the status. Any other data is ignored. */ @@ -943,7 +997,7 @@ private String extractResponseBody(TesterAjpMessage message) throws Exception { Assert.assertEquals(0x03, message.readByte()); int len = message.readInt(); - Assert.assertTrue(len > 0); + Assert.assertTrue(len >= 0); return message.readString(len); }
webapps/docs/changelog.xml+5 −0 modified@@ -152,6 +152,11 @@ <code>allowHostHeaderMismatch</code>. These are now hard-coded to the previous defaults. (markt) </update> + <fix> + <bug>66591</bug>: Fix a regression introduced in the fix for + <bug>66512</bug> that meant that an AJP Send Headers was not sent for + responses where no HTTP headers were set. (markt) + </fix> </changelog> </subsection> <subsection name="Jasper">
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- github.com/advisories/GHSA-mppv-79ch-vw6qghsaADVISORY
- lists.apache.org/thread/j1ksjh9m9gx1q60rtk1sbzmxhvj5h5qzghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2023-34981ghsaADVISORY
- bz.apache.org/bugzilla/show_bug.cgighsaWEB
- bz.apache.org/bugzilla/show_bug.cgighsaWEB
- github.com/apache/tomcat/commit/2214c8030522aa9b2a367dfa5d9acff1a03666aeghsaWEB
- github.com/apache/tomcat/commit/2f0ca2378415f4cf0748f4bc8fa955f41f803fa5ghsaWEB
- github.com/apache/tomcat/commit/739c7381aed22b7636351caf885ddc519ab6b442ghsaWEB
- github.com/apache/tomcat/commit/f0742f47b98aca943097f7f88e0d1163f57527e3ghsaWEB
- security.netapp.com/advisory/ntap-20230714-0003ghsaWEB
- tomcat.apache.org/security-10.htmlghsaWEB
- tomcat.apache.org/security-11.htmlghsaWEB
- tomcat.apache.org/security-8.htmlghsaWEB
- tomcat.apache.org/security-9.htmlghsaWEB
- security.netapp.com/advisory/ntap-20230714-0003/mitre
News mentions
0No linked articles in our index yet.