This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit ee3a13f9ef81d71a6d7af0b6d9f80756bca15c88 Author: Mark Thomas <ma...@apache.org> AuthorDate: Mon Jan 20 16:40:44 2025 +0000 serveSubpathOnly should apply to destinations as well When using the WebDAV servlet with serveSubpathOnly set to true, ensure that the destination for any requested WebDAV operation is also restricted to the sub-path. --- .../apache/catalina/servlets/WebdavServlet.java | 7 +- .../catalina/servlets/TestWebdavServlet.java | 77 +++++++++++++++++++++- webapps/docs/changelog.xml | 5 ++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java b/java/org/apache/catalina/servlets/WebdavServlet.java index 30b7722f7e..3368969bbe 100644 --- a/java/org/apache/catalina/servlets/WebdavServlet.java +++ b/java/org/apache/catalina/servlets/WebdavServlet.java @@ -2086,7 +2086,12 @@ public class WebdavServlet extends DefaultServlet implements PeriodicEventListen // Cross-context operations aren't supported String reqContextPath = getPathPrefix(req); - if (!destinationPath.startsWith(reqContextPath + "/")) { + String expectedTargetPath = reqContextPath; + // Also ensure copy (and move) operations do not escape the configured sub-path when limited to the sub-path + if (serveSubpathOnly && req.getServletPath() != null) { + expectedTargetPath = expectedTargetPath + req.getServletPath(); + } + if (!destinationPath.startsWith(expectedTargetPath + "/")) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return false; } diff --git a/test/org/apache/catalina/servlets/TestWebdavServlet.java b/test/org/apache/catalina/servlets/TestWebdavServlet.java index 007f3ead95..ef11d28ae1 100644 --- a/test/org/apache/catalina/servlets/TestWebdavServlet.java +++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java @@ -690,9 +690,84 @@ public class TestWebdavServlet extends TomcatBaseTest { Assert.assertEquals(WebdavStatus.SC_MULTI_STATUS, client.getStatusCode()); Assert.assertFalse(client.getResponseBody().contains("/myfolder")); validateXml(client.getResponseBody()); - } + + @Test + public void testCopyOutsideSubpath() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // Create a temp webapp that can be safely written to + File tempWebapp = new File(getTemporaryDirectory(), "webdav-subpath"); + File subPath = new File(tempWebapp, "aaa"); + Assert.assertTrue(subPath.mkdirs()); + + Context ctxt = tomcat.addContext("", tempWebapp.getAbsolutePath()); + Wrapper webdavServlet = Tomcat.addServlet(ctxt, "webdav", new WebdavServlet()); + webdavServlet.addInitParameter("listings", "true"); + webdavServlet.addInitParameter("readonly", "false"); + webdavServlet.addInitParameter("serveSubpathOnly", "true"); + ctxt.addServletMappingDecoded("/aaa/*", "webdav"); + tomcat.start(); + + ctxt.getResources().setCacheMaxSize(10); + ctxt.getResources().setCacheObjectMaxSize(1); + + Client client = new Client(); + client.setPort(getPort()); + + // Create a file + client.setRequest(new String[] { "PUT /aaa/file1.txt HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Content-Length: 6" + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF + CONTENT }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_CREATED, client.getStatusCode()); + + // Copy file1.txt to file2.txt + client.setRequest(new String[] { "COPY /aaa/file1.txt HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Destination: http://localhost:" + getPort() + "/aaa/file2.txt" + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_CREATED, client.getStatusCode()); + + // Move file2.txt to file3.txt + client.setRequest(new String[] { "MOVE /aaa/file2.txt HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Destination: http://localhost:" + getPort() + "/aaa/file3.txt" + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_CREATED, client.getStatusCode()); + + // Copy file1.txt outside sub-path + client.setRequest(new String[] { "COPY /aaa/file1.txt HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Destination: http://localhost:" + getPort() + "/file1.txt" + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, client.getStatusCode()); + + // Move file1.txt outside sub-path + client.setRequest(new String[] { "MOVE /aaa/file1.txt HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF + + "Destination: http://localhost:" + getPort() + "/file1.txt" + SimpleHttpClient.CRLF + + "Connection: Close" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF }); + client.connect(); + client.processRequest(true); + Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, client.getStatusCode()); +} + + @Test public void testSharedLocks() throws Exception { Tomcat tomcat = getTomcatInstance(); diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index a40d83ec54..8212628b47 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -154,6 +154,11 @@ be confirmed that the JVM has been correctly configured, prevent the impacted web applications from starting. (markt) </add> + <fix> + When using the WebDAV servlet with <code>serveSubpathOnly</code> set to + <code>true</code>, ensure that the destination for any requested WebDAV + operation is also restricted to the sub-path. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org