[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