Package: release.debian.org Severity: normal User: release.debian....@packages.debian.org Usertags: unblock
Please unblock package jetty9 [ Reason ] Fixing CVE-2021-28169 and CVE-2021-34428 [ Impact ] Version of Jetty 9 in Debian 11 is vulnerable [ Tests ] Integrated tests pass successfully [ Risks ] None, since all tests pass. [ Checklist ] [x] all changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in testing unblock jetty9/9.4.39-1
diff -Nru jetty9-9.4.39/debian/changelog jetty9-9.4.39/debian/changelog --- jetty9-9.4.39/debian/changelog 2021-04-12 00:11:03.000000000 +0200 +++ jetty9-9.4.39/debian/changelog 2021-07-03 19:09:58.000000000 +0200 @@ -1,3 +1,23 @@ +jetty9 (9.4.39-2) unstable; urgency=high + + * Team upload. + * Fix CVE-2021-28169: + It is possible for requests to the ConcatServlet with a doubly encoded path + to access protected resources within the WEB-INF directory. For example a + request to `/concat?/%2557EB-INF/web.xml` can retrieve the web.xml file. + This can reveal sensitive information regarding the implementation of a web + application. + * Fix CVE-2021-34428: + If an exception is thrown from the SessionListener#sessionDestroyed() + method, then the session ID is not invalidated in the session ID manager. + On deployments with clustered sessions and multiple contexts this can + result in a session not being invalidated. This can result in an + application used on a shared computer being left logged in. + + Thanks to Salvatore Bonaccorso for the report. (Closes: #989999, #990578) + + -- Markus Koschany <a...@debian.org> Sat, 03 Jul 2021 19:09:58 +0200 + jetty9 (9.4.39-1) unstable; urgency=high * New upstream release diff -Nru jetty9-9.4.39/debian/patches/CVE-2021-28169.patch jetty9-9.4.39/debian/patches/CVE-2021-28169.patch --- jetty9-9.4.39/debian/patches/CVE-2021-28169.patch 1970-01-01 01:00:00.000000000 +0100 +++ jetty9-9.4.39/debian/patches/CVE-2021-28169.patch 2021-07-03 19:09:58.000000000 +0200 @@ -0,0 +1,520 @@ +From: Markus Koschany <a...@debian.org> +Date: Sat, 3 Jul 2021 19:01:20 +0200 +Subject: CVE-2021-28169 + +Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=989999 +Origin: https://github.com/eclipse/jetty.project/commit/1c05b0bcb181c759e98b060bded0b9376976b055 +--- + .../org/eclipse/jetty/server/ResourceService.java | 4 +- + .../org/eclipse/jetty/servlets/ConcatServlet.java | 4 +- + .../org/eclipse/jetty/servlets/WelcomeFilter.java | 8 +- + .../eclipse/jetty/servlets/ConcatServletTest.java | 83 ++++++++---- + .../eclipse/jetty/servlets/WelcomeFilterTest.java | 143 +++++++++++++++++++++ + .../jetty/webapp/WebAppDefaultServletTest.java | 142 ++++++++++++++++++++ + 6 files changed, 353 insertions(+), 31 deletions(-) + create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java + create mode 100644 jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java + +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +index 1edfd83..c908e8d 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +@@ -240,7 +240,7 @@ public class ResourceService + // Find the content + content = _contentFactory.getContent(pathInContext, response.getBufferSize()); + if (LOG.isDebugEnabled()) +- LOG.info("content={}", content); ++ LOG.debug("content={}", content); + + // Not found? + if (content == null || !content.getResource().exists()) +@@ -430,7 +430,7 @@ public class ResourceService + return; + } + +- RequestDispatcher dispatcher = context.getRequestDispatcher(welcome); ++ RequestDispatcher dispatcher = context.getRequestDispatcher(URIUtil.encodePath(welcome)); + if (dispatcher != null) + { + // Forward to the index +diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java +index f6dde94..55700b4 100644 +--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java ++++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/ConcatServlet.java +@@ -61,6 +61,7 @@ import org.eclipse.jetty.util.URIUtil; + * appropriate. This means that when not in development mode, the servlet must be + * restarted before changed content will be served.</p> + */ ++@Deprecated + public class ConcatServlet extends HttpServlet + { + private boolean _development; +@@ -125,7 +126,8 @@ public class ConcatServlet extends HttpServlet + } + } + +- RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path); ++ // Use the original string and not the decoded path as the Dispatcher will decode again. ++ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(part); + if (dispatcher != null) + dispatchers.add(dispatcher); + } +diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java +index 9a25538..3caa85a 100644 +--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java ++++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java +@@ -27,6 +27,8 @@ import javax.servlet.ServletRequest; + import javax.servlet.ServletResponse; + import javax.servlet.http.HttpServletRequest; + ++import org.eclipse.jetty.util.URIUtil; ++ + /** + * Welcome Filter + * This filter can be used to server an index file for a directory +@@ -41,6 +43,7 @@ import javax.servlet.http.HttpServletRequest; + * + * Requests to "/some/directory" will be redirected to "/some/directory/". + */ ++@Deprecated + public class WelcomeFilter implements Filter + { + private String welcome; +@@ -61,7 +64,10 @@ public class WelcomeFilter implements Filter + { + String path = ((HttpServletRequest)request).getServletPath(); + if (welcome != null && path.endsWith("/")) +- request.getRequestDispatcher(path + welcome).forward(request, response); ++ { ++ String uriInContext = URIUtil.encodePath(URIUtil.addPaths(path, welcome)); ++ request.getRequestDispatcher(uriInContext).forward(request, response); ++ } + else + chain.doFilter(request, response); + } +diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java +index f8ea087..5cb9c89 100644 +--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java ++++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java +@@ -26,6 +26,7 @@ import java.io.StringReader; + import java.nio.charset.StandardCharsets; + import java.nio.file.Files; + import java.nio.file.Path; ++import java.util.stream.Stream; + import javax.servlet.RequestDispatcher; + import javax.servlet.ServletException; + import javax.servlet.http.HttpServlet; +@@ -41,7 +42,12 @@ import org.eclipse.jetty.webapp.WebAppContext; + import org.junit.jupiter.api.AfterEach; + import org.junit.jupiter.api.BeforeEach; + import org.junit.jupiter.api.Test; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.Arguments; ++import org.junit.jupiter.params.provider.MethodSource; + ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.startsWith; + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNotNull; + import static org.junit.jupiter.api.Assertions.assertNull; +@@ -112,7 +118,7 @@ public class ConcatServletTest + } + + @Test +- public void testWEBINFResourceIsNotServed() throws Exception ++ public void testDirectoryNotAccessible() throws Exception + { + File directoryFile = MavenTestingUtils.getTargetTestingDir(); + Path directoryPath = directoryFile.toPath(); +@@ -134,9 +140,8 @@ public class ConcatServletTest + // Verify that I can get the file programmatically, as required by the spec. + assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); + +- // Having a path segment and then ".." triggers a special case +- // that the ConcatServlet must detect and avoid. +- String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js"; ++ // Make sure ConcatServlet cannot see file system files. ++ String uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName(); + String request = + "GET " + uri + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + +@@ -144,35 +149,59 @@ public class ConcatServletTest + "\r\n"; + String response = connector.getResponse(request); + assertTrue(response.startsWith("HTTP/1.1 404 ")); ++ } + +- // Make sure ConcatServlet behaves well if it's case insensitive. +- uri = contextPath + concatPath + "?/trick/../web-inf/one.js"; +- request = +- "GET " + uri + " HTTP/1.1\r\n" + +- "Host: localhost\r\n" + +- "Connection: close\r\n" + +- "\r\n"; +- response = connector.getResponse(request); +- assertTrue(response.startsWith("HTTP/1.1 404 ")); ++ public static Stream<Arguments> webInfTestExamples() ++ { ++ return Stream.of( ++ // Cannot access WEB-INF. ++ Arguments.of("?/WEB-INF/", "HTTP/1.1 404 "), ++ Arguments.of("?/WEB-INF/one.js", "HTTP/1.1 404 "), ++ ++ // Having a path segment and then ".." triggers a special case that the ConcatServlet must detect and avoid. ++ Arguments.of("?/trick/../WEB-INF/one.js", "HTTP/1.1 404 "), ++ ++ // Make sure ConcatServlet behaves well if it's case insensitive. ++ Arguments.of("?/trick/../web-inf/one.js", "HTTP/1.1 404 "), ++ ++ // Make sure ConcatServlet behaves well if encoded. ++ Arguments.of("?/trick/..%2FWEB-INF%2Fone.js", "HTTP/1.1 404 "), ++ Arguments.of("?/%2557EB-INF/one.js", "HTTP/1.1 500 "), ++ Arguments.of("?/js/%252e%252e/WEB-INF/one.js", "HTTP/1.1 500 ") ++ ); ++ } + +- // Make sure ConcatServlet behaves well if encoded. +- uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js"; +- request = +- "GET " + uri + " HTTP/1.1\r\n" + +- "Host: localhost\r\n" + +- "Connection: close\r\n" + +- "\r\n"; +- response = connector.getResponse(request); +- assertTrue(response.startsWith("HTTP/1.1 404 ")); ++ @ParameterizedTest ++ @MethodSource("webInfTestExamples") ++ public void testWEBINFResourceIsNotServed(String querystring, String expectedStatus) throws Exception ++ { ++ File directoryFile = MavenTestingUtils.getTargetTestingDir(); ++ Path directoryPath = directoryFile.toPath(); ++ Path hiddenDirectory = directoryPath.resolve("WEB-INF"); ++ Files.createDirectories(hiddenDirectory); ++ Path hiddenResource = hiddenDirectory.resolve("one.js"); ++ try (OutputStream output = Files.newOutputStream(hiddenResource)) ++ { ++ output.write("function() {}".getBytes(StandardCharsets.UTF_8)); ++ } + +- // Make sure ConcatServlet cannot see file system files. +- uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName(); +- request = ++ String contextPath = ""; ++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), contextPath); ++ server.setHandler(context); ++ String concatPath = "/concat"; ++ context.addServlet(ConcatServlet.class, concatPath); ++ server.start(); ++ ++ // Verify that I can get the file programmatically, as required by the spec. ++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); ++ ++ String uri = contextPath + concatPath + querystring; ++ String request = + "GET " + uri + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n"; +- response = connector.getResponse(request); +- assertTrue(response.startsWith("HTTP/1.1 404 ")); ++ String response = connector.getResponse(request); ++ assertThat(response, startsWith(expectedStatus)); + } + } +diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java +new file mode 100644 +index 0000000..65e6503 +--- /dev/null ++++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java +@@ -0,0 +1,143 @@ ++// ++// ======================================================================== ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. ++// ------------------------------------------------------------------------ ++// All rights reserved. This program and the accompanying materials ++// are made available under the terms of the Eclipse Public License v1.0 ++// and Apache License v2.0 which accompanies this distribution. ++// ++// The Eclipse Public License is available at ++// http://www.eclipse.org/legal/epl-v10.html ++// ++// The Apache License v2.0 is available at ++// http://www.opensource.org/licenses/apache2.0.php ++// ++// You may elect to redistribute this code under either of these licenses. ++// ======================================================================== ++// ++ ++package org.eclipse.jetty.servlets; ++ ++import java.io.OutputStream; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.EnumSet; ++import java.util.stream.Stream; ++import javax.servlet.DispatcherType; ++ ++import org.eclipse.jetty.server.LocalConnector; ++import org.eclipse.jetty.server.Server; ++import org.eclipse.jetty.servlet.FilterHolder; ++import org.eclipse.jetty.toolchain.test.MavenTestingUtils; ++import org.eclipse.jetty.webapp.WebAppContext; ++import org.junit.jupiter.api.AfterEach; ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.Arguments; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.containsString; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++ ++public class WelcomeFilterTest ++{ ++ private Server server; ++ private LocalConnector connector; ++ ++ @BeforeEach ++ public void prepareServer() throws Exception ++ { ++ server = new Server(); ++ connector = new LocalConnector(server); ++ server.addConnector(connector); ++ ++ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath(); ++ Files.createDirectories(directoryPath); ++ Path welcomeResource = directoryPath.resolve("welcome.html"); ++ try (OutputStream output = Files.newOutputStream(welcomeResource)) ++ { ++ output.write("<h1>welcome page</h1>".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path otherResource = directoryPath.resolve("other.html"); ++ try (OutputStream output = Files.newOutputStream(otherResource)) ++ { ++ output.write("<h1>other resource</h1>".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path hiddenDirectory = directoryPath.resolve("WEB-INF"); ++ Files.createDirectories(hiddenDirectory); ++ Path hiddenResource = hiddenDirectory.resolve("one.js"); ++ try (OutputStream output = Files.newOutputStream(hiddenResource)) ++ { ++ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path hiddenWelcome = hiddenDirectory.resolve("index.html"); ++ try (OutputStream output = Files.newOutputStream(hiddenWelcome)) ++ { ++ output.write("CONFIDENTIAL".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/"); ++ server.setHandler(context); ++ String concatPath = "/*"; ++ ++ FilterHolder filterHolder = new FilterHolder(new WelcomeFilter()); ++ filterHolder.setInitParameter("welcome", "welcome.html"); ++ context.addFilter(filterHolder, concatPath, EnumSet.of(DispatcherType.REQUEST)); ++ server.start(); ++ ++ // Verify that I can get the file programmatically, as required by the spec. ++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); ++ } ++ ++ @AfterEach ++ public void destroy() throws Exception ++ { ++ if (server != null) ++ server.stop(); ++ } ++ ++ public static Stream<Arguments> argumentsStream() ++ { ++ return Stream.of( ++ // Normal requests for the directory are redirected to the welcome page. ++ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "<h1>welcome page</h1>"}), ++ ++ // Try a normal resource (will bypass the filter). ++ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "<h1>other resource</h1>"}), ++ ++ // Cannot access files in WEB-INF. ++ Arguments.of("/WEB-INF/one.js", new String[]{"HTTP/1.1 404 "}), ++ ++ // Cannot serve welcome from WEB-INF. ++ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}), ++ ++ // Try to trick the filter into serving a protected resource. ++ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}), ++ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}), ++ ++ // Test the URI is not double decoded in the dispatcher. ++ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 404 "}) ++ ); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("argumentsStream") ++ public void testWelcomeFilter(String uri, String[] contains) throws Exception ++ { ++ String request = ++ "GET " + uri + " HTTP/1.1\r\n" + ++ "Host: localhost\r\n" + ++ "Connection: close\r\n" + ++ "\r\n"; ++ String response = connector.getResponse(request); ++ for (String s : contains) ++ { ++ assertThat(response, containsString(s)); ++ } ++ } ++} +diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java +new file mode 100644 +index 0000000..933bb7a +--- /dev/null ++++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java +@@ -0,0 +1,142 @@ ++// ++// ======================================================================== ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. ++// ------------------------------------------------------------------------ ++// All rights reserved. This program and the accompanying materials ++// are made available under the terms of the Eclipse Public License v1.0 ++// and Apache License v2.0 which accompanies this distribution. ++// ++// The Eclipse Public License is available at ++// http://www.eclipse.org/legal/epl-v10.html ++// ++// The Apache License v2.0 is available at ++// http://www.opensource.org/licenses/apache2.0.php ++// ++// You may elect to redistribute this code under either of these licenses. ++// ======================================================================== ++// ++ ++package org.eclipse.jetty.webapp; ++ ++import java.io.OutputStream; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.stream.Stream; ++ ++import org.eclipse.jetty.server.LocalConnector; ++import org.eclipse.jetty.server.Server; ++import org.eclipse.jetty.toolchain.test.MavenTestingUtils; ++import org.eclipse.jetty.util.IO; ++import org.junit.jupiter.api.AfterEach; ++import org.junit.jupiter.api.BeforeEach; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.Arguments; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.containsString; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++ ++public class WebAppDefaultServletTest ++{ ++ private Server server; ++ private LocalConnector connector; ++ ++ @BeforeEach ++ public void prepareServer() throws Exception ++ { ++ server = new Server(); ++ connector = new LocalConnector(server); ++ server.addConnector(connector); ++ ++ Path directoryPath = MavenTestingUtils.getTargetTestingDir().toPath(); ++ IO.delete(directoryPath.toFile()); ++ Files.createDirectories(directoryPath); ++ Path welcomeResource = directoryPath.resolve("index.html"); ++ try (OutputStream output = Files.newOutputStream(welcomeResource)) ++ { ++ output.write("<h1>welcome page</h1>".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path otherResource = directoryPath.resolve("other.html"); ++ try (OutputStream output = Files.newOutputStream(otherResource)) ++ { ++ output.write("<h1>other resource</h1>".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path hiddenDirectory = directoryPath.resolve("WEB-INF"); ++ Files.createDirectories(hiddenDirectory); ++ Path hiddenResource = hiddenDirectory.resolve("one.js"); ++ try (OutputStream output = Files.newOutputStream(hiddenResource)) ++ { ++ output.write("this is confidential".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ // Create directory to trick resource service. ++ Path hackPath = directoryPath.resolve("%57EB-INF/one.js#/"); ++ Files.createDirectories(hackPath); ++ try (OutputStream output = Files.newOutputStream(hackPath.resolve("index.html"))) ++ { ++ output.write("this content does not matter".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ Path standardHashDir = directoryPath.resolve("welcome#"); ++ Files.createDirectories(standardHashDir); ++ try (OutputStream output = Files.newOutputStream(standardHashDir.resolve("index.html"))) ++ { ++ output.write("standard hash dir welcome".getBytes(StandardCharsets.UTF_8)); ++ } ++ ++ WebAppContext context = new WebAppContext(server, directoryPath.toString(), "/"); ++ server.setHandler(context); ++ server.start(); ++ ++ // Verify that I can get the file programmatically, as required by the spec. ++ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); ++ } ++ ++ @AfterEach ++ public void destroy() throws Exception ++ { ++ if (server != null) ++ server.stop(); ++ } ++ ++ public static Stream<Arguments> argumentsStream() ++ { ++ return Stream.of( ++ Arguments.of("/WEB-INF/", new String[]{"HTTP/1.1 404 "}), ++ Arguments.of("/welcome%23/", new String[]{"HTTP/1.1 200 ", "standard hash dir welcome"}), ++ ++ // Normal requests for the directory are redirected to the welcome page. ++ Arguments.of("/", new String[]{"HTTP/1.1 200 ", "<h1>welcome page</h1>"}), ++ ++ // We can be served other resources. ++ Arguments.of("/other.html", new String[]{"HTTP/1.1 200 ", "<h1>other resource</h1>"}), ++ ++ // The ContextHandler will filter these ones out as as WEB-INF is a protected target. ++ Arguments.of("/WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}), ++ Arguments.of("/js/../WEB-INF/one.js#/", new String[]{"HTTP/1.1 404 "}), ++ ++ // Test the URI is not double decoded by the dispatcher that serves the welcome file (we get index.html not one.js). ++ Arguments.of("/%2557EB-INF/one.js%23/", new String[]{"HTTP/1.1 200 ", "this content does not matter"}) ++ ); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("argumentsStream") ++ public void testResourceService(String uri, String[] contains) throws Exception ++ { ++ String request = ++ "GET " + uri + " HTTP/1.1\r\n" + ++ "Host: localhost\r\n" + ++ "Connection: close\r\n" + ++ "\r\n"; ++ String response = connector.getResponse(request); ++ for (String s : contains) ++ { ++ assertThat(response, containsString(s)); ++ } ++ } ++} diff -Nru jetty9-9.4.39/debian/patches/CVE-2021-34428.patch jetty9-9.4.39/debian/patches/CVE-2021-34428.patch --- jetty9-9.4.39/debian/patches/CVE-2021-34428.patch 1970-01-01 01:00:00.000000000 +0100 +++ jetty9-9.4.39/debian/patches/CVE-2021-34428.patch 2021-07-03 19:09:58.000000000 +0200 @@ -0,0 +1,278 @@ +From: Markus Koschany <a...@debian.org> +Date: Sat, 3 Jul 2021 19:00:12 +0200 +Subject: CVE-2021-34428 + +Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=990578 +Commit: https://github.com/eclipse/jetty.project/commit/087f486b4461746b4ded45833887b3ccb136ee85 +--- + .../org/eclipse/jetty/server/session/Session.java | 13 +-- + .../server/session/TestHttpSessionListener.java | 24 ++++-- + .../TestHttpSessionListenerWithWebappClasses.java | 6 +- + .../jetty/server/session/SessionListenerTest.java | 95 ++++++++++++++++++++-- + 4 files changed, 119 insertions(+), 19 deletions(-) + +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +index 65edd2e..4db2b40 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +@@ -498,10 +498,7 @@ public class Session implements SessionHandler.SessionIf + { + try (Lock lock = _lock.lock()) + { +- if (isInvalid()) +- { +- throw new IllegalStateException("Session not valid"); +- } ++ checkValidForRead(); + return _sessionData.getLastAccessed(); + } + } +@@ -947,14 +944,18 @@ public class Session implements SessionHandler.SessionIf + // do the invalidation + _handler.callSessionDestroyedListeners(this); + } ++ catch (Exception e) ++ { ++ LOG.warn("Error during Session destroy listener", e); ++ } + finally + { + // call the attribute removed listeners and finally mark it + // as invalid + finishInvalidate(); ++ // tell id mgr to remove sessions with same id from all contexts ++ _handler.getSessionIdManager().invalidateAll(_sessionData.getId()); + } +- // tell id mgr to remove sessions with same id from all contexts +- _handler.getSessionIdManager().invalidateAll(_sessionData.getId()); + } + } + catch (Exception e) +diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java +index e6cf138..060b6c8 100644 +--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java ++++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java +@@ -31,16 +31,18 @@ public class TestHttpSessionListener implements HttpSessionListener + public List<String> createdSessions = new ArrayList<>(); + public List<String> destroyedSessions = new ArrayList<>(); + public boolean accessAttribute = false; +- public Exception ex = null; ++ public boolean lastAccessTime = false; ++ public Exception attributeException = null; ++ public Exception accessTimeException = null; + +- public TestHttpSessionListener(boolean access) ++ public TestHttpSessionListener(boolean accessAttribute, boolean lastAccessTime) + { +- accessAttribute = access; ++ this.accessAttribute = accessAttribute; ++ this.lastAccessTime = lastAccessTime; + } + + public TestHttpSessionListener() + { +- accessAttribute = false; + } + + public void sessionDestroyed(HttpSessionEvent se) +@@ -54,7 +56,19 @@ public class TestHttpSessionListener implements HttpSessionListener + } + catch (Exception e) + { +- ex = e; ++ attributeException = e; ++ } ++ } ++ ++ if (lastAccessTime) ++ { ++ try ++ { ++ se.getSession().getLastAccessedTime(); ++ } ++ catch (Exception e) ++ { ++ accessTimeException = e; + } + } + } +diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListenerWithWebappClasses.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListenerWithWebappClasses.java +index bec36c0..50e00fd 100644 +--- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListenerWithWebappClasses.java ++++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListenerWithWebappClasses.java +@@ -35,9 +35,9 @@ public class TestHttpSessionListenerWithWebappClasses extends TestHttpSessionLis + super(); + } + +- public TestHttpSessionListenerWithWebappClasses(boolean access) ++ public TestHttpSessionListenerWithWebappClasses(boolean attribute, boolean lastAccessTime) + { +- super(access); ++ super(attribute, lastAccessTime); + } + + @Override +@@ -52,7 +52,7 @@ public class TestHttpSessionListenerWithWebappClasses extends TestHttpSessionLis + } + catch (Exception cnfe) + { +- ex = cnfe; ++ attributeException = cnfe; + } + super.sessionDestroyed(se); + } +diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java +index 76b566c..a20708c 100644 +--- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java ++++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java +@@ -58,6 +58,7 @@ import static org.hamcrest.Matchers.greaterThan; + import static org.hamcrest.Matchers.in; + import static org.hamcrest.Matchers.is; + import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertFalse; + import static org.junit.jupiter.api.Assertions.assertNotEquals; + import static org.junit.jupiter.api.Assertions.assertNotNull; + import static org.junit.jupiter.api.Assertions.assertNull; +@@ -92,7 +93,7 @@ public class SessionListenerTest + TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, + cacheFactory, storeFactory); + ServletContextHandler context = server.addContext(contextPath); +- TestHttpSessionListener listener = new TestHttpSessionListener(true); ++ TestHttpSessionListener listener = new TestHttpSessionListener(true, true); + context.getSessionHandler().addEventListener(listener); + TestServlet servlet = new TestServlet(); + ServletHolder holder = new ServletHolder(servlet); +@@ -136,6 +137,72 @@ public class SessionListenerTest + LifeCycle.stop(server); + } + } ++ ++ /** ++ * Test that if a session listener throws an exception during sessionDestroyed the session is still invalidated ++ */ ++ @Test ++ public void testListenerWithInvalidationException() throws Exception ++ { ++ String contextPath = ""; ++ String servletMapping = "/server"; ++ int inactivePeriod = 6; ++ int scavengePeriod = -1; ++ ++ DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); ++ cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); ++ TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); ++ storeFactory.setGracePeriodSec(scavengePeriod); ++ ++ TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, ++ cacheFactory, storeFactory); ++ ServletContextHandler context = server.addContext(contextPath); ++ ThrowingSessionListener listener = new ThrowingSessionListener(); ++ context.getSessionHandler().addEventListener(listener); ++ TestServlet servlet = new TestServlet(); ++ ServletHolder holder = new ServletHolder(servlet); ++ context.addServlet(holder, servletMapping); ++ ++ try ++ { ++ server.start(); ++ int port1 = server.getPort(); ++ ++ HttpClient client = new HttpClient(); ++ client.start(); ++ try ++ { ++ String url = "http://localhost:" + port1 + contextPath + servletMapping; ++ // Create the session ++ ContentResponse response1 = client.GET(url + "?action=init"); ++ assertEquals(HttpServletResponse.SC_OK, response1.getStatus()); ++ String sessionCookie = response1.getHeaders().get("Set-Cookie"); ++ assertNotNull(sessionCookie); ++ assertTrue(TestServlet.bindingListener.bound); ++ ++ String sessionId = TestServer.extractSessionId(sessionCookie); ++ ++ // Make a request which will invalidate the existing session ++ Request request2 = client.newRequest(url + "?action=test"); ++ ContentResponse response2 = request2.send(); ++ assertEquals(HttpServletResponse.SC_OK, response2.getStatus()); ++ ++ assertTrue(TestServlet.bindingListener.unbound); ++ ++ //check session no longer exists ++ assertFalse(context.getSessionHandler().getSessionCache().contains(sessionId)); ++ assertFalse(context.getSessionHandler().getSessionCache().getSessionDataStore().exists(sessionId)); ++ } ++ finally ++ { ++ LifeCycle.stop(client); ++ } ++ } ++ finally ++ { ++ LifeCycle.stop(server); ++ } ++ } + + /** + * Test that listeners are called when a session expires +@@ -177,7 +244,7 @@ public class SessionListenerTest + ServletContextHandler context = server1.addContext(contextPath); + context.setClassLoader(contextClassLoader); + context.addServlet(holder, servletMapping); +- TestHttpSessionListener listener = new TestHttpSessionListenerWithWebappClasses(true); ++ TestHttpSessionListener listener = new TestHttpSessionListenerWithWebappClasses(true, true); + context.getSessionHandler().addEventListener(listener); + + try +@@ -206,7 +273,8 @@ public class SessionListenerTest + + assertThat(sessionId, is(in(listener.destroyedSessions))); + +- assertNull(listener.ex); ++ assertNull(listener.attributeException); ++ assertNull(listener.accessTimeException); + } + finally + { +@@ -241,7 +309,7 @@ public class SessionListenerTest + ServletHolder holder = new ServletHolder(servlet); + ServletContextHandler context = server1.addContext(contextPath); + context.addServlet(holder, servletMapping); +- TestHttpSessionListener listener = new TestHttpSessionListener(); ++ TestHttpSessionListener listener = new TestHttpSessionListener(true, true); + + context.getSessionHandler().addEventListener(listener); + +@@ -276,7 +344,8 @@ public class SessionListenerTest + + assertTrue(listener.destroyedSessions.contains("1234")); + +- assertNull(listener.ex); ++ assertNull(listener.attributeException); ++ assertNull(listener.accessTimeException); + } + finally + { +@@ -301,6 +370,22 @@ public class SessionListenerTest + { + } + } ++ ++ public static class ThrowingSessionListener implements HttpSessionListener ++ { ++ ++ @Override ++ public void sessionCreated(HttpSessionEvent se) ++ { ++ } ++ ++ @Override ++ public void sessionDestroyed(HttpSessionEvent se) ++ { ++ throw new IllegalStateException("Exception during sessionDestroyed"); ++ } ++ ++ } + + @Test + public void testSessionListeners() diff -Nru jetty9-9.4.39/debian/patches/series jetty9-9.4.39/debian/patches/series --- jetty9-9.4.39/debian/patches/series 2021-04-12 00:00:36.000000000 +0200 +++ jetty9-9.4.39/debian/patches/series 2021-07-03 19:09:58.000000000 +0200 @@ -5,3 +5,5 @@ 07-assembly-plugin-configuration.patch 08-ignore-jetty-test-policy.patch 09-tweak-distribution.patch +CVE-2021-28169.patch +CVE-2021-34428.patch