[Git][java-team/tomcat9][buster] 7 commits: Fix CVE-2022-42252 and CVE-2023-28708
Markus Koschany (@apo)
gitlab at salsa.debian.org
Wed Apr 5 17:25:11 BST 2023
Markus Koschany pushed to branch buster at Debian Java Maintainers / tomcat9
Commits:
9ab609c6 by Markus Koschany at 2023-04-05T02:39:44+02:00
Fix CVE-2022-42252 and CVE-2023-28708
- - - - -
36ca2d38 by Markus Koschany at 2023-04-05T02:40:02+02:00
Start new changelog entry
- - - - -
ea67dacb by Markus Koschany at 2023-04-05T02:47:02+02:00
update CVE-2022-42252.patch
- - - - -
f3cccbb8 by Markus Koschany at 2023-04-05T02:54:07+02:00
Update CVE-2022-42252.patch and include private class
- - - - -
e4e46de2 by Markus Koschany at 2023-04-05T18:04:57+02:00
Update changelog
- - - - -
bb71c063 by Markus Koschany at 2023-04-05T18:08:30+02:00
Update patch header
- - - - -
b2f8131c by Markus Koschany at 2023-04-05T18:16:51+02:00
Update changelog
- - - - -
4 changed files:
- debian/changelog
- + debian/patches/CVE-2022-42252.patch
- + debian/patches/CVE-2023-28708.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,21 @@
+tomcat9 (9.0.31-1~deb10u8) buster-security; urgency=high
+
+ * Team upload.
+ * Fix CVE-2022-42252:
+ Apache Tomcat was configured to ignore invalid HTTP headers via setting
+ rejectIllegalHeader to false. Tomcat did not reject a request containing an
+ invalid Content-Length header making a request smuggling attack possible if
+ Tomcat was located behind a reverse proxy that also failed to reject the
+ request with the invalid header.
+ * Fix CVE-2023-28708:
+ When using the RemoteIpFilter with requests received from a reverse proxy
+ via HTTP that include the X-Forwarded-Proto header set to https, session
+ cookies created by Apache Tomcat did not include the secure attribute. This
+ could result in the user agent transmitting the session cookie over an
+ insecure channel.
+
+ -- Markus Koschany <apo at debian.org> Wed, 05 Apr 2023 18:04:52 +0200
+
tomcat9 (9.0.31-1~deb10u7) buster-security; urgency=high
* Team upload.
=====================================
debian/patches/CVE-2022-42252.patch
=====================================
@@ -0,0 +1,214 @@
+From: Markus Koschany <apo at debian.org>
+Date: Wed, 5 Apr 2023 02:22:58 +0200
+Subject: CVE-2022-42252
+
+Origin: https://github.com/apache/tomcat/commit/4c7f4fd09d2cc1692112ef70b8ee23a7a037ae77
+---
+ .../apache/coyote/http11/Http11InputBuffer.java | 42 ++++++++----
+ .../coyote/http11/TestHttp11InputBuffer.java | 79 ++++++++++++++++++++++
+ webapps/docs/changelog.xml | 5 ++
+ 3 files changed, 113 insertions(+), 13 deletions(-)
+
+diff --git a/java/org/apache/coyote/http11/Http11InputBuffer.java b/java/org/apache/coyote/http11/Http11InputBuffer.java
+index 04543ef..4e311de 100644
+--- a/java/org/apache/coyote/http11/Http11InputBuffer.java
++++ b/java/org/apache/coyote/http11/Http11InputBuffer.java
+@@ -832,7 +832,7 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
+ headerData.lastSignificantChar = pos;
+ byteBuffer.position(byteBuffer.position() - 1);
+ // skipLine() will handle the error
+- return skipLine();
++ return skipLine(false);
+ }
+
+ // chr is next byte of header name. Convert to lowercase.
+@@ -843,7 +843,7 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
+
+ // Skip the line and ignore the header
+ if (headerParsePos == HeaderParsePosition.HEADER_SKIPLINE) {
+- return skipLine();
++ return skipLine(false);
+ }
+
+ //
+@@ -894,15 +894,11 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
+ } else if (prevChr == Constants.CR && chr == Constants.LF) {
+ eol = true;
+ } else if (prevChr == Constants.CR) {
+- // Invalid value
+- // Delete the header (it will be the most recent one)
+- headers.removeHeader(headers.size() - 1);
+- return skipLine();
++ // Invalid value - also need to delete header
++ return skipLine(true);
+ } else if (chr != Constants.HT && HttpParser.isControl(chr)) {
+- // Invalid value
+- // Delete the header (it will be the most recent one)
+- headers.removeHeader(headers.size() - 1);
+- return skipLine();
++ // Invalid value - also need to delete header
++ return skipLine(true);
+ } else if (chr == Constants.SP || chr == Constants.HT) {
+ byteBuffer.put(headerData.realPos, chr);
+ headerData.realPos++;
+@@ -950,7 +946,27 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
+ }
+
+
+- private HeaderParseStatus skipLine() throws IOException {
++ private HeaderParseStatus skipLine(boolean deleteHeader) throws IOException {
++ boolean rejectThisHeader = rejectIllegalHeader;
++ // Check if rejectIllegalHeader is disabled and needs to be overridden
++ // for this header. The header name is required to determine if this
++ // override is required. The header name is only available once the
++ // header has been created. If the header has been created then
++ // deleteHeader will be true.
++ if (!rejectThisHeader && deleteHeader) {
++ if (headers.getName(headers.size() - 1).equalsIgnoreCase("content-length")) {
++ // Malformed content-length headers must always be rejected
++ // RFC 9112, section 6.3, bullet 5.
++ rejectThisHeader = true;
++ } else {
++ // Only need to delete the header if the request isn't going to
++ // be rejected (it will be the most recent one)
++ headers.removeHeader(headers.size() - 1);
++ }
++ }
++
++ // Parse the rest of the invalid header so we can construct a useful
++ // exception and/or debug message.
+ headerParsePos = HeaderParsePosition.HEADER_SKIPLINE;
+ boolean eol = false;
+
+@@ -978,11 +994,11 @@ public class Http11InputBuffer implements InputBuffer, ApplicationBufferHandler
+ headerData.lastSignificantChar = pos;
+ }
+ }
+- if (rejectIllegalHeader || log.isDebugEnabled()) {
++ if (rejectThisHeader || log.isDebugEnabled()) {
+ String message = sm.getString("iib.invalidheader",
+ HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart,
+ headerData.lastSignificantChar - headerData.lineStart + 1));
+- if (rejectIllegalHeader) {
++ if (rejectThisHeader) {
+ throw new IllegalArgumentException(message);
+ }
+ log.debug(message);
+diff --git a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
+index 73c7b03..1b77289 100644
+--- a/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
++++ b/test/org/apache/coyote/http11/TestHttp11InputBuffer.java
+@@ -38,6 +38,11 @@ import org.apache.catalina.startup.TomcatBaseTest;
+
+ public class TestHttp11InputBuffer extends TomcatBaseTest {
+
++ private static final String CR = "\r";
++ private static final String LF = "\n";
++ private static final String CRLF = CR + LF;
++
++
+ /**
+ * Test case for https://bz.apache.org/bugzilla/show_bug.cgi?id=48839
+ */
+@@ -644,6 +649,37 @@ public class TestHttp11InputBuffer extends TomcatBaseTest {
+ }
+
+
++ @Test
++ public void testInvalidContentLength01() {
++ doTestInvalidContentLength(false);
++ }
++
++
++ @Test
++ public void testInvalidContentLength02() {
++ doTestInvalidContentLength(true);
++ }
++
++
++ private void doTestInvalidContentLength(boolean rejectIllegalHeader) {
++ getTomcatInstance().getConnector().setProperty("rejectIllegalHeader", Boolean.toString(rejectIllegalHeader));
++
++ String[] request = new String[1];
++ request[0] =
++ "POST /test HTTP/1.1" + CRLF +
++ "Host: localhost:8080" + CRLF +
++ "Content-Length: 12\u000734" + CRLF +
++ "Connection: close" + CRLF +
++ CRLF;
++
++ InvalidClient client = new InvalidClient(request);
++
++ client.doRequest();
++ Assert.assertTrue(client.getResponseLine(), client.isResponse400());
++ Assert.assertTrue(client.isResponseBodyOK());
++ }
++
++
+ /**
+ * Bug 48839 test client.
+ */
+@@ -688,4 +724,47 @@ public class TestHttp11InputBuffer extends TomcatBaseTest {
+ return true;
+ }
+ }
++
++ /**
++ * Invalid request test client.
++ */
++ private class InvalidClient extends SimpleHttpClient {
++
++ private final String[] request;
++
++ public InvalidClient(String[] request) {
++ this.request = request;
++ }
++
++ private Exception doRequest() {
++
++ Tomcat tomcat = getTomcatInstance();
++
++ tomcat.addContext("", TEMP_DIR);
++
++ try {
++ tomcat.start();
++ setPort(tomcat.getConnector().getLocalPort());
++
++ // Open connection
++ connect();
++ setRequest(request);
++ processRequest(); // blocks until response has been read
++
++ // Close the connection
++ disconnect();
++ } catch (Exception e) {
++ return e;
++ }
++ return null;
++ }
++
++ @Override
++ public boolean isResponseBodyOK() {
++ if (getResponseBody() == null) {
++ return false;
++ }
++ return true;
++ }
++ }
+ }
+diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
+index 9c45802..9c446bd 100644
+--- a/webapps/docs/changelog.xml
++++ b/webapps/docs/changelog.xml
+@@ -230,6 +230,11 @@
+ Make handling of OpenSSL read errors more robust when plain text data is
+ reported to be available to read. (markt)
+ </fix>
++ <fix>
++ Enforce the requirement of RFC 7230 onwards that a request with a
++ malformed <code>content-length</code> header should always be rejected
++ with a 400 response. (markt)
++ </fix>
+ </changelog>
+ </subsection>
+ <subsection name="Web applications">
=====================================
debian/patches/CVE-2023-28708.patch
=====================================
@@ -0,0 +1,209 @@
+From: Markus Koschany <apo at debian.org>
+Date: Wed, 5 Apr 2023 02:39:13 +0200
+Subject: CVE-2023-28708
+
+Bug-Debian: https://bugs.debian.org/1033475
+Origin: https://github.com/apache/tomcat/commit/3b51230764da595bb19e8d0962dd8c69ab40dfab
+---
+ java/org/apache/catalina/Globals.java | 7 ++
+ java/org/apache/catalina/connector/Request.java | 15 ++++
+ .../apache/catalina/filters/RemoteIpFilter.java | 7 +-
+ .../catalina/filters/TestRemoteIpFilter.java | 96 ++++++++++++++++------
+ 4 files changed, 96 insertions(+), 29 deletions(-)
+
+diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java
+index b25ee32..e928116 100644
+--- a/java/org/apache/catalina/Globals.java
++++ b/java/org/apache/catalina/Globals.java
+@@ -148,6 +148,13 @@ public final class Globals {
+ org.apache.coyote.Constants.SENDFILE_SUPPORTED_ATTR;
+
+
++ /**
++ * The request attribute that is set to the value of {@code Boolean.TRUE}
++ * if {@link org.apache.catalina.filters.RemoteIpFilter} determines
++ * that this request was submitted via a secure channel.
++ */
++ public static final String REMOTE_IP_FILTER_SECURE = "org.apache.catalina.filters.RemoteIpFilter.secure";
++
+ /**
+ * The request attribute that can be used by a servlet to pass
+ * to the connector the name of the file that is to be served
+diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
+index 7f27bdd..619fdce 100644
+--- a/java/org/apache/catalina/connector/Request.java
++++ b/java/org/apache/catalina/connector/Request.java
+@@ -3523,6 +3523,21 @@ public class Request implements HttpServletRequest {
+ // NO-OP
+ }
+ });
++ specialAttributes.put(Globals.REMOTE_IP_FILTER_SECURE,
++ new SpecialAttributeAdapter() {
++ @Override
++ public Object get(Request request, String name) {
++ return Boolean.valueOf(request.isSecure());
++ }
++
++ @Override
++ public void set(Request request, String name, Object value) {
++ if (value instanceof Boolean) {
++ request.setSecure(((Boolean) value).booleanValue());
++ }
++ }
++ });
++
+
+ for (SimpleDateFormat sdf : formatsTemplate) {
+ sdf.setTimeZone(GMT_ZONE);
+diff --git a/java/org/apache/catalina/filters/RemoteIpFilter.java b/java/org/apache/catalina/filters/RemoteIpFilter.java
+index 4664a85..0830e91 100644
+--- a/java/org/apache/catalina/filters/RemoteIpFilter.java
++++ b/java/org/apache/catalina/filters/RemoteIpFilter.java
+@@ -572,11 +572,6 @@ public class RemoteIpFilter extends GenericFilter {
+ return serverPort;
+ }
+
+- @Override
+- public boolean isSecure() {
+- return secure;
+- }
+-
+ public void removeHeader(String name) {
+ Map.Entry<String, List<String>> header = getHeaderEntry(name);
+ if (header != null) {
+@@ -616,7 +611,7 @@ public class RemoteIpFilter extends GenericFilter {
+ }
+
+ public void setSecure(boolean secure) {
+- this.secure = secure;
++ super.getRequest().setAttribute(Globals.REMOTE_IP_FILTER_SECURE, Boolean.valueOf(secure));
+ }
+
+ public void setServerName(String serverName) {
+diff --git a/test/org/apache/catalina/filters/TestRemoteIpFilter.java b/test/org/apache/catalina/filters/TestRemoteIpFilter.java
+index 88e8f6b..2ba68ba 100644
+--- a/test/org/apache/catalina/filters/TestRemoteIpFilter.java
++++ b/test/org/apache/catalina/filters/TestRemoteIpFilter.java
+@@ -82,15 +82,21 @@ public class TestRemoteIpFilter extends TomcatBaseTest {
+
+ private static final long serialVersionUID = 1L;
+
+- private transient HttpServletRequest request;
+-
+- public HttpServletRequest getRequest() {
+- return request;
+- }
++ public String remoteAddr;
++ public String remoteHost;
++ public String scheme;
++ public String serverName;
++ public int serverPort;
++ public boolean isSecure;
+
+ @Override
+ public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+- this.request = request;
++ this.isSecure = request.isSecure();
++ this.remoteAddr = request.getRemoteAddr();
++ this.remoteHost = request.getRemoteHost();
++ this.scheme = request.getScheme();
++ this.serverName = request.getServerName();
++ this.serverPort = request.getServerPort();
+ PrintWriter writer = response.getWriter();
+
+ writer.println("request.remoteAddr=" + request.getRemoteAddr());
+@@ -129,16 +135,6 @@ public class TestRemoteIpFilter extends TomcatBaseTest {
+ getCoyoteRequest().scheme().setString(scheme);
+ }
+
+- @Override
+- public void setAttribute(String name, Object value) {
+- getCoyoteRequest().getAttributes().put(name, value);
+- }
+-
+- @Override
+- public Object getAttribute(String name) {
+- return getCoyoteRequest().getAttributes().get(name);
+- }
+-
+ @Override
+ public String getServerName() {
+ return "localhost";
+@@ -770,16 +766,70 @@ public class TestRemoteIpFilter extends TomcatBaseTest {
+
+ // VALIDATE
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, httpURLConnection.getResponseCode());
+- HttpServletRequest request = mockServlet.getRequest();
+- Assert.assertNotNull(request);
+
+ // VALIDATE X-FORWARDED-FOR
+- Assert.assertEquals(expectedRemoteAddr, request.getRemoteAddr());
+- Assert.assertEquals(expectedRemoteAddr, request.getRemoteHost());
++ Assert.assertEquals(expectedRemoteAddr, mockServlet.remoteAddr);
++ Assert.assertEquals(expectedRemoteAddr, mockServlet.remoteHost);
+
+ // VALIDATE X-FORWARDED-PROTO
+- Assert.assertTrue(request.isSecure());
+- Assert.assertEquals("https", request.getScheme());
+- Assert.assertEquals(443, request.getServerPort());
++ Assert.assertTrue(mockServlet.isSecure);
++ Assert.assertEquals("https", mockServlet.scheme);
++ Assert.assertEquals(443, mockServlet.serverPort);
++ }
++
++ @Test
++ public void testJSessionIdSecureAttributeMissing() throws Exception {
++
++ // mostly default configuration : enable "x-forwarded-proto"
++ Map<String, String> remoteIpFilterParameter = new HashMap<>();
++ remoteIpFilterParameter.put("protocolHeader", "x-forwarded-proto");
++
++ // SETUP
++ Tomcat tomcat = getTomcatInstance();
++ Context root = tomcat.addContext("", TEMP_DIR);
++
++ FilterDef filterDef = new FilterDef();
++ filterDef.getParameterMap().putAll(remoteIpFilterParameter);
++ filterDef.setFilterClass(RemoteIpFilter.class.getName());
++ filterDef.setFilterName(RemoteIpFilter.class.getName());
++
++ root.addFilterDef(filterDef);
++
++ FilterMap filterMap = new FilterMap();
++ filterMap.setFilterName(RemoteIpFilter.class.getName());
++ filterMap.addURLPatternDecoded("*");
++ root.addFilterMap(filterMap);
++
++ Bug66471Servlet bug66471Servlet = new Bug66471Servlet();
++
++ Tomcat.addServlet(root, bug66471Servlet.getClass().getName(), bug66471Servlet);
++ root.addServletMappingDecoded("/test", bug66471Servlet.getClass().getName());
++
++ getTomcatInstance().start();
++
++ Map<String, List<String>> resHeaders = new HashMap<>();
++ Map<String, List<String>> reqHeaders = new HashMap<>();
++ String expectedRemoteAddr = "my-remote-addr";
++ List<String> forwardedFor = new ArrayList<>(1);
++ forwardedFor.add(expectedRemoteAddr);
++ List<String> forwardedProto = new ArrayList<>(1);
++ forwardedProto.add("https");
++ reqHeaders.put("x-forwarded-for", forwardedFor);
++ reqHeaders.put("x-forwarded-proto", forwardedProto);
++
++ getUrl("http://localhost:" + tomcat.getConnector().getLocalPort() +
++ "/test", null, reqHeaders, resHeaders);
++ String setCookie = resHeaders.get("Set-Cookie").get(0);
++ Assert.assertTrue(setCookie.contains("Secure"));
++ Assert.assertTrue(bug66471Servlet.isSecure.booleanValue());
++ }
++ public static class Bug66471Servlet extends HttpServlet {
++ private static final long serialVersionUID = 1L;
++ public Boolean isSecure;
++ @Override
++ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
++ req.getSession();
++ isSecure = (Boolean) req.getAttribute(Globals.REMOTE_IP_FILTER_SECURE);
++ }
+ }
+ }
=====================================
debian/patches/series
=====================================
@@ -27,3 +27,5 @@ CVE-2021-41079.patch
CVE-2021-43980.patch
CVE-2022-23181.patch
CVE-2022-29885.patch
+CVE-2022-42252.patch
+CVE-2023-28708.patch
View it on GitLab: https://salsa.debian.org/java-team/tomcat9/-/compare/25fbc3e1cac27ccf4ea319b8b93e4171e246757d...b2f8131c17f8f6ef60e7b9cf02ca40d1ed7614b1
--
View it on GitLab: https://salsa.debian.org/java-team/tomcat9/-/compare/25fbc3e1cac27ccf4ea319b8b93e4171e246757d...b2f8131c17f8f6ef60e7b9cf02ca40d1ed7614b1
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20230405/66ddb99e/attachment.htm>
More information about the pkg-java-commits
mailing list