This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new d953c65e09 Prevent accidental directory traversal
d953c65e09 is described below
commit d953c65e09248055c932ba890662fbea6ff1d706
Author: Mark Thomas <[email protected]>
AuthorDate: Fri May 8 15:40:30 2026 +0100
Prevent accidental directory traversal
Note: This is NOT a security vulnerability as uploading a web application
effectively allows RCE anyway.
---
.../catalina/manager/LocalStrings.properties | 2 +
.../apache/catalina/manager/ManagerServlet.java | 43 ++++++++++++++++++++++
webapps/docs/changelog.xml | 8 ++++
3 files changed, 53 insertions(+)
diff --git a/java/org/apache/catalina/manager/LocalStrings.properties
b/java/org/apache/catalina/manager/LocalStrings.properties
index 2f40bed021..c94aaaf8d9 100644
--- a/java/org/apache/catalina/manager/LocalStrings.properties
+++ b/java/org/apache/catalina/manager/LocalStrings.properties
@@ -161,6 +161,8 @@ managerServlet.noWrapper=Container has not called
setWrapper() for this servlet
managerServlet.notDeployed=FAIL - Context [{0}] is defined in server.xml and
may not be undeployed
managerServlet.notSslConnector=SSL is not enabled for this connector
managerServlet.objectNameFail=FAIL - Unable to register object name [{0}] for
Manager Servlet
+managerServlet.pathCheckFail=FAIL - Unable to use [{0}] as that is outside the
expected directory [{1}]
+managerServlet.pathCheckError=FAIL - Unable to use [{0}] due to [{2}] while
checking if it was outside the expected directory [{1}]
managerServlet.postCommand=FAIL - Tried to use command [{0}] via a GET request
but POST is required
managerServlet.reloaded=OK - Reloaded application at context path [{0}]
managerServlet.renameFail=FAIL - Unable to rename [{0}] to [{1}]. This may
cause problems for future deployments.
diff --git a/java/org/apache/catalina/manager/ManagerServlet.java
b/java/org/apache/catalina/manager/ManagerServlet.java
index 806be52ce7..0410d26002 100644
--- a/java/org/apache/catalina/manager/ManagerServlet.java
+++ b/java/org/apache/catalina/manager/ManagerServlet.java
@@ -715,6 +715,10 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
}
File deployedWar = new File(host.getAppBaseFile(), baseName + ".war");
+ if (!pathCheck(deployedWar, host.getAppBaseFile(), writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
// Determine full path for uploaded WAR
File uploadedWar;
@@ -732,6 +736,10 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
}
} else {
File uploadPath = new File(versioned, tag);
+ if (!pathCheck(uploadPath, versioned, writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
if (!uploadPath.mkdirs() && !uploadPath.isDirectory()) {
writer.println(smClient.getString("managerServlet.mkdirFail",
uploadPath));
return;
@@ -817,8 +825,16 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
// Find the local WAR file
File localWar = new File(new File(versioned, tag), baseName + ".war");
+ if (!pathCheck(localWar, versioned, writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
File deployedWar = new File(host.getAppBaseFile(), baseName + ".war");
+ if (!pathCheck(deployedWar, host.getAppBaseFile(), writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
// Copy WAR to appBase
try {
@@ -850,6 +866,19 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
}
+ private static boolean pathCheck(File input, File expected, PrintWriter
writer, StringManager smClient) {
+ try {
+ if
(!input.getCanonicalFile().toPath().startsWith(expected.getCanonicalFile().toPath()))
{
+
writer.println(smClient.getString("managerServlet.pathCheckFail", input,
expected));
+ return false;
+ }
+ } catch (IOException ioe) {
+ writer.println(smClient.getString("managerServlet.pathCheckError",
input, expected, ioe.getMessage()));
+ return false;
+ }
+ return true;
+ }
+
/**
* Install an application for the specified path from the specified web
application archive.
*
@@ -918,7 +947,12 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
return;
}
File localConfigFile = new File(configBase, baseName +
".xml");
+ if (!pathCheck(localConfigFile, configBase, writer,
smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
File configFile = new File(config);
+
// Skip delete and copy if source == destination
if
(!configFile.getCanonicalPath().equals(localConfigFile.getCanonicalPath())) {
if (localConfigFile.isFile() &&
!localConfigFile.delete()) {
@@ -939,9 +973,18 @@ public class ManagerServlet extends HttpServlet implements
ContainerServlet {
} else {
localWarFile = new File(host.getAppBaseFile(),
baseName);
}
+ if (!pathCheck(localWarFile, host.getAppBaseFile(),
writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
+
File warFile = new File(war);
if (!warFile.isAbsolute()) {
warFile = new File(host.getAppBaseFile(), war);
+ if (!pathCheck(warFile, host.getAppBaseFile(),
writer, smClient)) {
+ // Any error reported in pathCheck()
+ return;
+ }
}
// Skip delete and copy if source == destination
if
(!warFile.getCanonicalPath().equals(localWarFile.getCanonicalPath())) {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8eb494bc92..969b4a73a0 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -124,6 +124,14 @@
</fix>
</changelog>
</subsection>
+ <subsection name="Web applications">
+ <changelog>
+ <add>
+ Manager: Add checks to ensure that any uploaded files are uploaded to
+ the expected location. (markt)
+ </add>
+ </changelog>
+ </subsection>
</section>
<section name="Tomcat 11.0.22 (markt)" rtext="release in progress">
<subsection name="Catalina">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]