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

Bug#990710: marked as done (unblock: jetty9/9.4.39-1)



Your message dated Mon, 05 Jul 2021 21:03:28 +0000
with message-id <E1m0VkW-0000MP-L1@respighi.debian.org>
and subject line unblock jetty9
has caused the Debian Bug report #990710,
regarding unblock: jetty9/9.4.39-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
990710: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=990710
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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

--- End Message ---
--- Begin Message ---
Unblocked.

--- End Message ---

Reply to: