[Git][java-team/jetty9][master] 2 commits: Fix CVE-2021-28169 and CVE-2021-34428
Markus Koschany (@apo)
gitlab at salsa.debian.org
Sat Jul 3 18:28:20 BST 2021
Markus Koschany pushed to branch master at Debian Java Maintainers / jetty9
Commits:
cf19866f by Markus Koschany at 2021-07-03T19:07:55+02:00
Fix CVE-2021-28169 and CVE-2021-34428
Thanks: Salvatore Bonaccorso for the report.
Closes: #989999, #990578
- - - - -
6c7cc80f by Markus Koschany at 2021-07-03T19:12:48+02:00
Update changelog
- - - - -
4 changed files:
- debian/changelog
- + debian/patches/CVE-2021-28169.patch
- + debian/patches/CVE-2021-34428.patch
- debian/patches/series
Changes:
=====================================
debian/changelog
=====================================
@@ -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 <apo at debian.org> Sat, 03 Jul 2021 19:09:58 +0200
+
jetty9 (9.4.39-1) unstable; urgency=high
* New upstream release
=====================================
debian/patches/CVE-2021-28169.patch
=====================================
@@ -0,0 +1,520 @@
+From: Markus Koschany <apo at 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>
+ */
++ at 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/".
+ */
++ at 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));
++ }
++ }
++}
=====================================
debian/patches/CVE-2021-34428.patch
=====================================
@@ -0,0 +1,278 @@
+From: Markus Koschany <apo at 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()
=====================================
debian/patches/series
=====================================
@@ -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
View it on GitLab: https://salsa.debian.org/java-team/jetty9/-/compare/de3b7ff101337ce032a7515a57d0a93d182653aa...6c7cc80fcd072e4bb28d4e8ed441d625ea0ecc5c
--
View it on GitLab: https://salsa.debian.org/java-team/jetty9/-/compare/de3b7ff101337ce032a7515a57d0a93d182653aa...6c7cc80fcd072e4bb28d4e8ed441d625ea0ecc5c
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/20210703/52b7137f/attachment.htm>
More information about the pkg-java-commits
mailing list