You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2022/02/15 16:50:14 UTC
[tomcat] branch main updated: Improve validation of Destination header
This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push:
new aea0e1c Improve validation of Destination header
aea0e1c is described below
commit aea0e1ce1a49eae90f91b49b8fddcfa08678b14d
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Feb 15 16:50:01 2022 +0000
Improve validation of Destination header
---
.../apache/catalina/servlets/WebdavServlet.java | 113 +++++++++++----------
webapps/docs/changelog.xml | 4 +
2 files changed, 61 insertions(+), 56 deletions(-)
diff --git a/java/org/apache/catalina/servlets/WebdavServlet.java b/java/org/apache/catalina/servlets/WebdavServlet.java
index c6a0963..b67db65 100644
--- a/java/org/apache/catalina/servlets/WebdavServlet.java
+++ b/java/org/apache/catalina/servlets/WebdavServlet.java
@@ -22,6 +22,8 @@ import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Enumeration;
@@ -47,7 +49,6 @@ import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.util.DOMWriter;
import org.apache.catalina.util.URLEncoder;
import org.apache.catalina.util.XMLWriter;
-import org.apache.tomcat.util.buf.UDecoder;
import org.apache.tomcat.util.http.ConcurrentDateFormat;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.RequestUtil;
@@ -1541,76 +1542,76 @@ public class WebdavServlet extends DefaultServlet {
*
* @param req Servlet request
* @param resp Servlet response
+ *
* @return boolean true if the copy is successful
+ *
* @throws IOException If an IO error occurs
*/
- private boolean copyResource(HttpServletRequest req,
- HttpServletResponse resp)
- throws IOException {
+ private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+
+ // Check the source exists
+ String path = getRelativePath(req);
+ WebResource source = resources.getResource(path);
+ if (!source.exists()) {
+ resp.sendError(WebdavStatus.SC_NOT_FOUND);
+ return false;
+ }
// Parsing destination header
+ // See RFC 4918
+ String destinationHeader = req.getHeader("Destination");
- String destinationPath = req.getHeader("Destination");
+ if (destinationHeader == null || destinationHeader.isEmpty()) {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return false;
+ }
- if (destinationPath == null) {
+ URI destinationUri;
+ try {
+ destinationUri = new URI (destinationHeader);
+ } catch (URISyntaxException e) {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return false;
}
- // Remove url encoding from destination
- destinationPath = UDecoder.URLDecode(destinationPath, StandardCharsets.UTF_8);
+ String destinationPath = destinationUri.getPath();
- int protocolIndex = destinationPath.indexOf("://");
- if (protocolIndex >= 0) {
- // if the Destination URL contains the protocol, we can safely
- // trim everything upto the first "/" character after "://"
- int firstSeparator =
- destinationPath.indexOf('/', protocolIndex + 4);
- if (firstSeparator < 0) {
- destinationPath = "/";
- } else {
- destinationPath = destinationPath.substring(firstSeparator);
- }
- } else {
- String hostName = req.getServerName();
- if ((hostName != null) && (destinationPath.startsWith(hostName))) {
- destinationPath = destinationPath.substring(hostName.length());
- }
+ // Destination isn't allowed to use '.' or '..' segments
+ if (!destinationPath.equals(RequestUtil.normalize(destinationPath))) {
+ resp.sendError(WebdavStatus.SC_BAD_REQUEST);
+ return false;
+ }
- int portIndex = destinationPath.indexOf(':');
- if (portIndex >= 0) {
- destinationPath = destinationPath.substring(portIndex);
+ if (destinationUri.isAbsolute()) {
+ // Scheme and host need to match
+ if (!req.getScheme().equals(destinationUri.getScheme()) ||
+ !req.getServerName().equals(destinationUri.getHost())) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return false;
}
-
- if (destinationPath.startsWith(":")) {
- int firstSeparator = destinationPath.indexOf('/');
- if (firstSeparator < 0) {
- destinationPath = "/";
+ // Port needs to match too but handled separately as the logic is a
+ // little more complicated
+ if (req.getServerPort() != destinationUri.getPort()) {
+ if (destinationUri.getPort() == -1 &&
+ ("http".equals(req.getScheme()) && req.getServerPort() == 80 ||
+ "https".equals(req.getScheme()) && req.getServerPort() == 443)) {
+ // All good.
} else {
- destinationPath =
- destinationPath.substring(firstSeparator);
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return false;
}
}
}
- // Normalise destination path (remove '.' and '..')
- destinationPath = RequestUtil.normalize(destinationPath);
-
- String contextPath = req.getContextPath();
- if ((contextPath != null) &&
- (destinationPath.startsWith(contextPath))) {
- destinationPath = destinationPath.substring(contextPath.length());
+ // Cross-context operations aren't supported
+ String reqContextPath = req.getContextPath();
+ if (!destinationPath.startsWith(reqContextPath + "/")) {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return false;
}
- String pathInfo = req.getPathInfo();
- if (pathInfo != null) {
- String servletPath = req.getServletPath();
- if ((servletPath != null) &&
- (destinationPath.startsWith(servletPath))) {
- destinationPath = destinationPath
- .substring(servletPath.length());
- }
- }
+ // Remove context path & servlet path
+ destinationPath = destinationPath.substring(reqContextPath.length() + req.getServletPath().length());
if (debug > 0) {
log("Dest path :" + destinationPath);
@@ -1622,18 +1623,20 @@ public class WebdavServlet extends DefaultServlet {
return false;
}
- String path = getRelativePath(req);
-
if (destinationPath.equals(path)) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
- // Parsing overwrite header
+ // Check src / dest are not sub-dirs of each other
+ if (destinationPath.startsWith(path) && destinationPath.charAt(path.length()) == '/' ||
+ path.startsWith(destinationPath) && path.charAt(destinationPath.length()) == '/') {
+ resp.sendError(WebdavStatus.SC_FORBIDDEN);
+ return false;
+ }
boolean overwrite = true;
String overwriteHeader = req.getHeader("Overwrite");
-
if (overwriteHeader != null) {
if (overwriteHeader.equalsIgnoreCase("T")) {
overwrite = true;
@@ -1643,9 +1646,7 @@ public class WebdavServlet extends DefaultServlet {
}
// Overwriting the destination
-
WebResource destination = resources.getResource(destinationPath);
-
if (overwrite) {
// Delete destination resource, if it exists
if (destination.exists()) {
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 78d6345..54a0e15 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -150,6 +150,10 @@
humans to read. The change ensures that there is always a line break
before starting a new element. (markt)
</fix>
+ <fix>
+ Improve validation of the <code>Destination</code> header for WebDAV
+ <code>MOVE</code> and <code>COPY</code> requests. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org