Apache Tomcat: Security constraint bypass for pre/post-resources
Description
Authentication Bypass Using an Alternate Path or Channel vulnerability in Apache Tomcat. When using PreResources or PostResources mounted other than at the root of the web application, it was possible to access those resources via an unexpected path. That path was likely not to be protected by the same security constraints as the expected path, allowing those security constraints to be bypassed.
This issue affects Apache Tomcat: from 11.0.0-M1 through 11.0.7, from 10.1.0-M1 through 10.1.41, from 9.0.0.M1 through 9.0.105. The following versions were EOL at the time the CVE was created but are known to be affected: 8.5.0 through 8.5.100. Other, older, EOL versions may also be affected.
Users are recommended to upgrade to version 11.0.8, 10.1.42 or 9.0.106, which fix the issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Tomcat allows security constraint bypass when PreResources or PostResources are mounted at a non-root path, enabling unauthorized resource access.
Vulnerability
CVE-2025-49125 is an authentication bypass vulnerability in Apache Tomcat that occurs when PreResources or PostResources are mounted at a non-root path within a web application. The root cause is that Tomcat's resource-serving code used a simple path.startsWith(webAppMount) check, which could match unintended paths. For example, a mount point of /public would also match /public-restricted, potentially exposing resources that should be protected by security constraints [1][2][3]. The fix, introduced in commit d94bd36, replaces this naive check with a more robust isPathMounted() method that ensures the path is exactly the mount point or a proper subdirectory [4].
Exploitation
Exploitation requires the target Tomcat instance to have PreResources or PostResources configured at a path other than the root (/). An attacker can request a crafted URL that, while beginning with the mount point string, actually refers to a different resource location. Since the path is unexpected, the request bypasses the security constraints (e.g., authentication or authorization filters) normally applied to the intended resource. The attack does not require authentication to Tomcat's management interfaces [1][2].
Impact
A successful attack allows an unauthenticated or unauthorized attacker to access resources that should be protected. This could include sensitive files, configuration data, or internal endpoints. The vulnerability compromises the confidentiality protections provided by Tomcat's security constraints [1].
Mitigation
Apache has released fixed versions: 11.0.8, 10.1.42, and 9.0.106. Users should upgrade immediately [1][2][3]. Note that Tomcat 8.5.x (up to 8.5.100) is affected but is end-of-life, so no patch will be provided; users must upgrade to a supported branch. The vulnerability is not known to be in CISA's KEV catalog at publication time.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.tomcat:tomcat-catalinaMaven | >= 11.0.0-M1, < 11.0.8 | 11.0.8 |
org.apache.tomcat:tomcat-catalinaMaven | >= 10.1.0-M1, < 10.1.42 | 10.1.42 |
org.apache.tomcat:tomcat-catalinaMaven | >= 9.0.0.M1, < 9.0.106 | 9.0.106 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 11.0.0-M1, < 11.0.8 | 11.0.8 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 10.1.0-M1, < 10.1.42 | 10.1.42 |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 9.0.0.M1, < 9.0.106 | 9.0.106 |
org.apache.tomcat:tomcat-catalinaMaven | >= 8.5.0, <= 8.5.100 | — |
org.apache.tomcat.embed:tomcat-embed-coreMaven | >= 8.5.0, <= 8.5.100 | — |
Affected products
62- osv-coords60 versionspkg:apk/chainguard/camunda-zeebe-8.6pkg:apk/chainguard/camunda-zeebe-8.6-compatpkg:apk/chainguard/thingsboardpkg:apk/chainguard/thingsboard-tb-js-executorpkg:apk/chainguard/thingsboard-tb-mqtt-transportpkg:apk/chainguard/thingsboard-tb-nodepkg:apk/chainguard/thingsboard-tb-web-uipkg:apk/wolfi/thingsboardpkg:apk/wolfi/thingsboard-tb-js-executorpkg:apk/wolfi/thingsboard-tb-mqtt-transportpkg:apk/wolfi/thingsboard-tb-nodepkg:apk/wolfi/thingsboard-tb-web-uipkg:bitnami/tomcatpkg:maven/org.apache.tomcat.embed/tomcat-embed-corepkg:maven/org.apache.tomcat/tomcat-catalinapkg:rpm/almalinux/tomcatpkg:rpm/almalinux/tomcat9pkg:rpm/almalinux/tomcat9-admin-webappspkg:rpm/almalinux/tomcat9-docs-webapppkg:rpm/almalinux/tomcat9-el-3.0-apipkg:rpm/almalinux/tomcat9-jsp-2.3-apipkg:rpm/almalinux/tomcat9-libpkg:rpm/almalinux/tomcat9-servlet-4.0-apipkg:rpm/almalinux/tomcat9-webappspkg:rpm/almalinux/tomcat-admin-webappspkg:rpm/almalinux/tomcat-docs-webapppkg:rpm/almalinux/tomcat-el-3.0-apipkg:rpm/almalinux/tomcat-jsp-2.3-apipkg:rpm/almalinux/tomcat-libpkg:rpm/almalinux/tomcat-servlet-4.0-apipkg:rpm/almalinux/tomcat-webappspkg:rpm/opensuse/tomcat10&distro=openSUSE%20Leap%2015.6pkg:rpm/opensuse/tomcat11&distro=openSUSE%20Leap%2015.6pkg:rpm/opensuse/tomcat&distro=openSUSE%20Leap%2015.6pkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP5-ESPOSpkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP5-LTSSpkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP6pkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP7pkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP5-LTSSpkg:rpm/suse/tomcat10&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP5pkg:rpm/suse/tomcat11&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP6pkg:rpm/suse/tomcat11&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP7pkg:rpm/suse/tomcat&distro=SUSE%20Enterprise%20Storage%207.1pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP4-ESPOSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP4-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP5-ESPOSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP5-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP6pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Web%20and%20Scripting%2015%20SP7pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP3-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP4-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP5-LTSSpkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP3pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP4pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Linux%20Enterprise%20Server%20LTSS%20Extended%20Security%2012%20SP5pkg:rpm/suse/tomcat&distro=SUSE%20Manager%20Server%204.3pkg:rpm/suse/tomcat&distro=SUSE%20Manager%20Server%20LTS%204.3
< 8.6.18-r2+ 59 more
- (no CPE)range: < 8.6.18-r2
- (no CPE)range: < 8.6.18-r2
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 4.0.1-r8
- (no CPE)range: < 9.0.106
- (no CPE)range: >= 11.0.0-M1, < 11.0.8
- (no CPE)range: >= 11.0.0-M1, < 11.0.8
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-5.el10_0.3
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 1:9.0.87-1.el8_10.6
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 11.0.9-150600.13.6.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 10.1.42-150200.5.45.1
- (no CPE)range: < 11.0.9-150600.13.6.1
- (no CPE)range: < 11.0.9-150600.13.6.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.36-3.145.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.36-3.145.1
- (no CPE)range: < 9.0.106-150200.86.1
- (no CPE)range: < 9.0.108-150200.91.1
- Apache Software Foundation/Apache Tomcatv5Range: 11.0.0-M1
Patches
3d94bd36fb7ebExpand checks for webAppMount
5 files changed · +39 −10
java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java+4 −3 modified@@ -68,7 +68,7 @@ public final String[] list(String path) { String webAppMount = getWebAppMount(); ArrayList<String> result = new ArrayList<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') { @@ -108,13 +108,14 @@ public final String[] list(String path) { return result.toArray(new String[0]); } + @Override public final Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path and make // sure it ends in '/' @@ -225,7 +226,7 @@ public final WebResource getResource(String path) { // If the JAR has been mounted below the web application root, return // an empty resource for requests outside of the mount point. - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') {
java/org/apache/catalina/webresources/AbstractResourceSet.java+12 −0 modified@@ -83,6 +83,18 @@ protected final String getWebAppMount() { return webAppMount; } + protected boolean isPathMounted(String path, String webAppMount) { + // Doesn't call getWebAppMount() as value might have changed + if (path.startsWith(webAppMount)) { + if (path.length() != webAppMount.length() && path.charAt(webAppMount.length()) != '/') { + return false; + } + return true; + } + return false; + } + + public final void setBase(String base) { this.base = base; }
java/org/apache/catalina/webresources/DirResourceSet.java+5 −5 modified@@ -103,7 +103,7 @@ public WebResource getResource(String path) { String webAppMount = getWebAppMount(); WebResourceRoot root = getRoot(); boolean readOnly = isReadOnly(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { /* * Lock the path for reading until the WebResource has been constructed. The lock prevents concurrent reads * and writes (e.g. HTTP GET and PUT / DELETE) for the same path causing corruption of the FileResource @@ -137,7 +137,7 @@ public WebResource getResource(String path) { public String[] list(String path) { checkPath(path); String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f == null) { return EMPTY_STRING_ARRAY; @@ -165,7 +165,7 @@ public Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f != null) { File[] list = f.listFiles(); @@ -242,7 +242,7 @@ public boolean mkdir(String path) { return false; } String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), false); if (f == null) { return false; @@ -272,7 +272,7 @@ public boolean write(String path, InputStream is, boolean overwrite) { } String webAppMount = getWebAppMount(); - if (!path.startsWith(webAppMount)) { + if (!isPathMounted(path, webAppMount)) { return false; }
test/org/apache/catalina/webresources/AbstractTestResourceSet.java+14 −2 modified@@ -97,7 +97,7 @@ private void doTestGetResourceRoot(boolean slash) { } @Test - public final void testGetResourceDirA() { + public final void testGetResourceDirWithoutTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -108,7 +108,7 @@ public final void testGetResourceDirA() { } @Test - public final void testGetResourceDirB() { + public final void testGetResourceDirWithTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1/"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -118,6 +118,18 @@ public final void testGetResourceDirB() { Assert.assertNull(webResource.getInputStream()); } + @Test + public final void testGetResourceDirWithoutLeadingFileSeperator() { + String mount = getMount(); + if (mount.isEmpty()) { + // Test is only meaningful when resource is mounted below web application root. + return; + } + WebResource webResource = resourceRoot.getResource(mount + "d1"); + Assert.assertFalse(webResource.exists()); + Assert.assertEquals(mount + "d1", webResource.getWebappPath()); + } + @Test public final void testGetResourceFile() { WebResource webResource =
webapps/docs/changelog.xml+4 −0 modified@@ -139,6 +139,10 @@ <bug>69706</bug>: Fix saved request serialization issue in FORM introduced when allowing infinite session timeouts. (remm) </fix> + <fix> + Expand the path checks for Pre-Resources and Post-Resources mounted at a + path within the web application. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote">
9418e3ff9f1fExpand checks for webAppMount
5 files changed · +39 −10
java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java+4 −3 modified@@ -68,7 +68,7 @@ public final String[] list(String path) { String webAppMount = getWebAppMount(); ArrayList<String> result = new ArrayList<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') { @@ -108,13 +108,14 @@ public final String[] list(String path) { return result.toArray(new String[0]); } + @Override public final Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path and make // sure it ends in '/' @@ -225,7 +226,7 @@ public final WebResource getResource(String path) { // If the JAR has been mounted below the web application root, return // an empty resource for requests outside of the mount point. - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') {
java/org/apache/catalina/webresources/AbstractResourceSet.java+12 −0 modified@@ -83,6 +83,18 @@ protected final String getWebAppMount() { return webAppMount; } + protected boolean isPathMounted(String path, String webAppMount) { + // Doesn't call getWebAppMount() as value might have changed + if (path.startsWith(webAppMount)) { + if (path.length() != webAppMount.length() && path.charAt(webAppMount.length()) != '/') { + return false; + } + return true; + } + return false; + } + + public final void setBase(String base) { this.base = base; }
java/org/apache/catalina/webresources/DirResourceSet.java+5 −5 modified@@ -102,7 +102,7 @@ public WebResource getResource(String path) { String webAppMount = getWebAppMount(); WebResourceRoot root = getRoot(); boolean readOnly = isReadOnly(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { /* * Lock the path for reading until the WebResource has been constructed. The lock prevents concurrent reads * and writes (e.g. HTTP GET and PUT / DELETE) for the same path causing corruption of the FileResource @@ -136,7 +136,7 @@ public WebResource getResource(String path) { public String[] list(String path) { checkPath(path); String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f == null) { return EMPTY_STRING_ARRAY; @@ -168,7 +168,7 @@ public Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f != null) { File[] list = f.listFiles(); @@ -245,7 +245,7 @@ public boolean mkdir(String path) { return false; } String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), false); if (f == null) { return false; @@ -275,7 +275,7 @@ public boolean write(String path, InputStream is, boolean overwrite) { } String webAppMount = getWebAppMount(); - if (!path.startsWith(webAppMount)) { + if (!isPathMounted(path, webAppMount)) { return false; }
test/org/apache/catalina/webresources/AbstractTestResourceSet.java+14 −2 modified@@ -97,7 +97,7 @@ private void doTestGetResourceRoot(boolean slash) { } @Test - public final void testGetResourceDirA() { + public final void testGetResourceDirWithoutTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -108,7 +108,7 @@ public final void testGetResourceDirA() { } @Test - public final void testGetResourceDirB() { + public final void testGetResourceDirWithTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1/"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -118,6 +118,18 @@ public final void testGetResourceDirB() { Assert.assertNull(webResource.getInputStream()); } + @Test + public final void testGetResourceDirWithoutLeadingFileSeperator() { + String mount = getMount(); + if (mount.isEmpty()) { + // Test is only meaningful when resource is mounted below web application root. + return; + } + WebResource webResource = resourceRoot.getResource(mount + "d1"); + Assert.assertFalse(webResource.exists()); + Assert.assertEquals(mount + "d1", webResource.getWebappPath()); + } + @Test public final void testGetResourceFile() { WebResource webResource =
webapps/docs/changelog.xml+4 −0 modified@@ -135,6 +135,10 @@ <bug>69706</bug>: Fix saved request serialization issue in FORM introduced when allowing infinite session timeouts. (remm) </fix> + <fix> + Expand the path checks for Pre-Resources and Post-Resources mounted at a + path within the web application. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote">
7617b9c247bcExpand checks for webAppMount
5 files changed · +39 −10
java/org/apache/catalina/webresources/AbstractArchiveResourceSet.java+4 −3 modified@@ -68,7 +68,7 @@ public final String[] list(String path) { String webAppMount = getWebAppMount(); ArrayList<String> result = new ArrayList<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') { @@ -108,13 +108,14 @@ public final String[] list(String path) { return result.toArray(new String[0]); } + @Override public final Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path and make // sure it ends in '/' @@ -225,7 +226,7 @@ public final WebResource getResource(String path) { // If the JAR has been mounted below the web application root, return // an empty resource for requests outside of the mount point. - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { String pathInJar = getInternalPath() + path.substring(webAppMount.length()); // Always strip off the leading '/' to get the JAR path if (!pathInJar.isEmpty() && pathInJar.charAt(0) == '/') {
java/org/apache/catalina/webresources/AbstractResourceSet.java+12 −0 modified@@ -83,6 +83,18 @@ protected final String getWebAppMount() { return webAppMount; } + protected boolean isPathMounted(String path, String webAppMount) { + // Doesn't call getWebAppMount() as value might have changed + if (path.startsWith(webAppMount)) { + if (path.length() != webAppMount.length() && path.charAt(webAppMount.length()) != '/') { + return false; + } + return true; + } + return false; + } + + public final void setBase(String base) { this.base = base; }
java/org/apache/catalina/webresources/DirResourceSet.java+5 −5 modified@@ -103,7 +103,7 @@ public WebResource getResource(String path) { String webAppMount = getWebAppMount(); WebResourceRoot root = getRoot(); boolean readOnly = isReadOnly(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { /* * Lock the path for reading until the WebResource has been constructed. The lock prevents concurrent reads * and writes (e.g. HTTP GET and PUT / DELETE) for the same path causing corruption of the FileResource @@ -137,7 +137,7 @@ public WebResource getResource(String path) { public String[] list(String path) { checkPath(path); String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f == null) { return EMPTY_STRING_ARRAY; @@ -165,7 +165,7 @@ public Set<String> listWebAppPaths(String path) { checkPath(path); String webAppMount = getWebAppMount(); ResourceSet<String> result = new ResourceSet<>(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), true); if (f != null) { File[] list = f.listFiles(); @@ -242,7 +242,7 @@ public boolean mkdir(String path) { return false; } String webAppMount = getWebAppMount(); - if (path.startsWith(webAppMount)) { + if (isPathMounted(path, webAppMount)) { File f = file(path.substring(webAppMount.length()), false); if (f == null) { return false; @@ -272,7 +272,7 @@ public boolean write(String path, InputStream is, boolean overwrite) { } String webAppMount = getWebAppMount(); - if (!path.startsWith(webAppMount)) { + if (!isPathMounted(path, webAppMount)) { return false; }
test/org/apache/catalina/webresources/AbstractTestResourceSet.java+14 −2 modified@@ -97,7 +97,7 @@ private void doTestGetResourceRoot(boolean slash) { } @Test - public final void testGetResourceDirA() { + public final void testGetResourceDirWithoutTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -108,7 +108,7 @@ public final void testGetResourceDirA() { } @Test - public final void testGetResourceDirB() { + public final void testGetResourceDirWithTrailingFileSeperator() { WebResource webResource = resourceRoot.getResource(getMount() + "/d1/"); Assert.assertTrue(webResource.isDirectory()); Assert.assertEquals("d1", webResource.getName()); @@ -118,6 +118,18 @@ public final void testGetResourceDirB() { Assert.assertNull(webResource.getInputStream()); } + @Test + public final void testGetResourceDirWithoutLeadingFileSeperator() { + String mount = getMount(); + if (mount.isEmpty()) { + // Test is only meaningful when resource is mounted below web application root. + return; + } + WebResource webResource = resourceRoot.getResource(mount + "d1"); + Assert.assertFalse(webResource.exists()); + Assert.assertEquals(mount + "d1", webResource.getWebappPath()); + } + @Test public final void testGetResourceFile() { WebResource webResource =
webapps/docs/changelog.xml+4 −0 modified@@ -135,6 +135,10 @@ <bug>69706</bug>: Fix saved request serialization issue in FORM introduced when allowing infinite session timeouts. (remm) </fix> + <fix> + Expand the path checks for Pre-Resources and Post-Resources mounted at a + path within the web application. (markt) + </fix> </changelog> </subsection> <subsection name="Coyote">
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-wc4r-xq3c-5cf3ghsaADVISORY
- lists.apache.org/thread/m66cytbfrty9k7dc4cg6tl1czhsnbywkghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2025-49125ghsaADVISORY
- www.openwall.com/lists/oss-security/2025/06/16/2ghsaWEB
- github.com/apache/tomcat/commit/7617b9c247bc77ed0444dd69adcd8aa48777886cghsaWEB
- github.com/apache/tomcat/commit/9418e3ff9f1f4c006b4661311ae9376c52d162b9ghsaWEB
- github.com/apache/tomcat/commit/d94bd36fb7eb32e790dae0339bc249069649a637ghsaWEB
- lists.debian.org/debian-lts-announce/2025/07/msg00009.htmlghsaWEB
- tomcat.apache.org/security-10.htmlghsaWEB
- tomcat.apache.org/security-11.htmlghsaWEB
- tomcat.apache.org/security-9.htmlghsaWEB
News mentions
0No linked articles in our index yet.