[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#990710: unblock: jetty9/9.4.39-1



Package: release.debian.org
Severity: normal
User: release.debian.org@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 <apo@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 <apo@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 <apo@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

Reply to: