This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 603a959d18cf2e05f00956e4d91de7a49b99a674 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 98737e0329..a1af063378 100644 --- a/java/org/apache/catalina/servlets/WebdavServlet.java +++ b/java/org/apache/catalina/servlets/WebdavServlet.java @@ -2085,7 +2085,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 084cfcdf74..1c1bd53e3c 100644 --- a/test/org/apache/catalina/servlets/TestWebdavServlet.java +++ b/test/org/apache/catalina/servlets/TestWebdavServlet.java @@ -689,9 +689,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 b757d2b441..2ad10b243c 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -154,6 +154,11 @@ <code>CrawlerSessionManagerValve</code>. Submitted by Brian Matzon. (remm) </fix> + <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