From: Markus Koschany <apo@debian.org>
Date: Sun, 18 Sep 2016 16:46:33 +0200
Subject: CVE-2016-6801

The CSRF content-type check for POST requests did not handle missing
Content-Type header fields, nor variations in field values with respect to
upper/lower case or optional parameters. This could be exploited to create a
resource via CSRF.

Backported to the 2.3 branch.

Origin: https://svn.apache.org/viewvc?view=revision&revision=1758791
---
 .../apache/jackrabbit/spi2davex/PostMethod.java    |  1 +
 .../org/apache/jackrabbit/webdav/DavResource.java  |  2 +-
 .../webdav/server/AbstractWebdavServlet.java       |  3 +-
 .../apache/jackrabbit/webdav/util/CSRFUtil.java    | 83 ++++++++++++++++++----
 4 files changed, 74 insertions(+), 15 deletions(-)

diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java
index 5355a72..f6e243c 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java
@@ -47,6 +47,7 @@ class PostMethod extends DavMethodBase {
 
     public PostMethod(String uri) {
         super(uri);
+        super.setRequestHeader("Referer", uri);
         HttpMethodParams params = getParams();
         params.setContentCharset("UTF-8");
     }
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java
index c99b5cd..6e70a42 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java
@@ -40,7 +40,7 @@ public interface DavResource {
     /**
      * String constant representing the WebDAV 1 and 2 method set.
      */
-    public static final String METHODS = "OPTIONS, GET, HEAD, POST, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, PUT, DELETE, MOVE, LOCK, UNLOCK";
+    public static final String METHODS = "OPTIONS, GET, HEAD, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, PUT, DELETE, MOVE, LOCK, UNLOCK";
 
     /**
      * Returns a comma separated list of all compliance classes the given
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
index 128946e..a1bdbf4 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
@@ -568,7 +568,7 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
      */
     protected void doPost(WebdavRequest request, WebdavResponse response,
                           DavResource resource) throws IOException, DavException {
-        doPut(request, response, resource);
+        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
     }
 
     /**
@@ -1356,7 +1356,6 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
      * @param out
      * @return
      * @see #doPut(WebdavRequest, WebdavResponse, DavResource)
-     * @see #doPost(WebdavRequest, WebdavResponse, DavResource)
      * @see #doMkCol(WebdavRequest, WebdavResponse, DavResource)
      */
     protected OutputContext getOutputContext(DavServletResponse response, OutputStream out) {
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java
index 4d431eb..b5fc8f4 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java
@@ -19,12 +19,18 @@ package org.apache.jackrabbit.webdav.util;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.servlet.http.HttpServletRequest;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.Locale;
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * <code>CSRFUtil</code>...
@@ -37,6 +43,19 @@ public class CSRFUtil {
     public static final String DISABLED = "disabled";
 
     /**
+     * Request content types for CSRF checking, see JCR-3909, JCR-4002, and JCR-4009
+     */
+    public static final Set<String> CONTENT_TYPES = Collections.unmodifiableSet(new HashSet<String>(
+            Arrays.asList(
+                    new String[] {
+                            "application/x-www-form-urlencoded",
+                            "multipart/form-data",
+                            "text/plain"
+                    }
+            )
+    ));
+
+    /**
      * logger instance
      */
     private static final Logger log = LoggerFactory.getLogger(CSRFUtil.class);
@@ -77,6 +96,7 @@ public class CSRFUtil {
         if (config == null || config.length() == 0) {
             disabled = false;
             allowedReferrerHosts = Collections.emptySet();
+            log.debug("CSRF protection disabled");
         } else {
             if (DISABLED.equalsIgnoreCase(config.trim())) {
                 disabled = true;
@@ -89,23 +109,62 @@ public class CSRFUtil {
                     allowedReferrerHosts.add(entry.trim());
                 }
             }
+            log.debug("CSRF protection enabled, allowed referrers: " + allowedReferrerHosts);
         }
     }
 
-    public boolean isValidRequest(HttpServletRequest request) throws MalformedURLException {
+  public boolean isValidRequest(HttpServletRequest request) {
+
         if (disabled) {
             return true;
+        } else if (!"POST".equals(request.getMethod())) {
+            // protection only needed for POST
+            return true;
         } else {
+            Enumeration<String> cts = (Enumeration<String>) request.getHeaders("Content-Type");
+            String ct = null;
+            if (cts != null && cts.hasMoreElements()) {
+                String t = cts.nextElement();
+                // prune parameters
+                int semicolon = t.indexOf(';');
+                if (semicolon >= 0) {
+                    t = t.substring(0, semicolon);
+                }
+                ct = t.trim().toLowerCase(Locale.ENGLISH);
+            }
+            if (cts != null && cts.hasMoreElements()) {
+                // reject if there are more header field instances
+                log.debug("request blocked because there were multiple content-type header fields");
+                return false;
+            }
+            if (ct != null && !CONTENT_TYPES.contains(ct)) {
+                // type present and not in blacklist
+                return true;
+            }
+
             String refHeader = request.getHeader("Referer");
+            // empty referrer headers are not allowed for POST + relevant
+            // content types (see JCR-3909)
             if (refHeader == null) {
-                // empty referrer is always allowed
-                return true;
-            } else {
-                String host = new URL(refHeader).getHost();
-                // test referrer-host equelst server or
-                // if it is contained in the set of explicitly allowed host names
-                return host.equals(request.getServerName()) || allowedReferrerHosts.contains(host);
+                log.debug("POST with content type" + ct + " blocked due to missing referer header field");
+                return false;
+            }
+
+            try {
+                String host = new URI(refHeader).getHost();
+                // test referrer-host equals server or
+                // if it is contained in the set of explicitly allowed host
+                // names
+                boolean ok = host == null || host.equals(request.getServerName()) || allowedReferrerHosts.contains(host);
+                if (!ok) {
+                    log.debug("POST with content type" + ct + " blocked due to referer header field being: " + refHeader);
+                }
+                return ok;
+            } catch (URISyntaxException ex) {
+                // referrer malformed -> block access
+                log.debug("POST with content type" + ct + " blocked due to malformed referer header field: " + refHeader);
+                return false;
             }
         }
     }
-}
\ No newline at end of file
+}
