You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by re...@apache.org on 2016/09/01 14:51:08 UTC

svn commit: r1758791 - in /jackrabbit/branches/2.4: ./ jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/ ...

Author: reschke
Date: Thu Sep  1 14:51:07 2016
New Revision: 1758791

URL: http://svn.apache.org/viewvc?rev=1758791&view=rev
Log:
JCR-4009: CSRF in Jackrabbit-Webdav (ported to 2.4)

CSRFUtil: properly parse content types (handle params, normalize, handle case differences also multiple field instances), handle missing content type header field, handle partial-URI in referer, DEBUG logging

WebDAV servlet: disable bogus POST support

Davex: include Referer header field in POST requests used for davex remoting

Modified:
    jackrabbit/branches/2.4/   (props changed)
    jackrabbit/branches/2.4/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java
    jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java
    jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
    jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java
    jackrabbit/branches/2.4/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/CSRFUtilTest.java

Propchange: jackrabbit/branches/2.4/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Sep  1 14:51:07 2016
@@ -1,3 +1,3 @@
 /jackrabbit/branches/JCR-2272:1173165-1176545
 /jackrabbit/sandbox/JCR-2415-lucene-3.0:1060860-1064038
-/jackrabbit/trunk:1221447,1221579,1221593,1221789,1221818,1225179,1225191,1225196,1225207,1225525,1225528,1226452,1226472,1226515,1226750,1226863,1227171,1227240,1227590,1227593,1227615,1228058,1228149,1228155,1228160,1230507,1230681,1230688,1231204,1232035,1232100,1232404,1232831,1232920,1232922,1233069,1233344,1233446,1233468,1233471,1233544,1234807,1235192,1235375,1235423,1236709,1236775,1236819-1236821,1240053,1241461,1242775,1245443,1291424,1296202,1296226,1297526,1298428,1301046,1301397,1302401,1303438,1304323,1304382,1306337,1307456,1309908,1311861,1324713,1327180,1327432,1327926,1329198,1334998,1335017,1335030,1336017,1336252,1338172,1341373,1346045,1348860,1349185,1352440,1352791,1353920,1354499,1358543,1360013,1360571,1361941,1362796,1362924,1367057,1368796,1399576,1400843,1400935,1403408,1403768,1415093,1415574,1416387,1416863,1418236,1437374,1437384,1437618,1437963,1438158,1439346,1439797,1444755,1445122,1461064,1461137,1461613,1462115,1462153,1462205,1462211,1466060,146
 6085,1466938,1467255,1467363,1469312,1469799,1469892,1469940,1470573,1471286,1475718,1478684,1479518,1487803,1497492,1498840,1498850,1499285,1505795,1505907,1505942,1506594,1508053,1509101,1517602,1517627,1517711,1519376,1526945,1530005,1535539,1537027,1556248,1582373,1634584,1680757,1709811,1729382,1732436
+/jackrabbit/trunk:1221447,1221579,1221593,1221789,1221818,1225179,1225191,1225196,1225207,1225525,1225528,1226452,1226472,1226515,1226750,1226863,1227171,1227240,1227590,1227593,1227615,1228058,1228149,1228155,1228160,1230507,1230681,1230688,1231204,1232035,1232100,1232404,1232831,1232920,1232922,1233069,1233344,1233446,1233468,1233471,1233544,1234807,1235192,1235375,1235423,1236709,1236775,1236819-1236821,1240053,1241461,1242775,1245443,1291424,1296202,1296226,1297526,1298428,1301046,1301397,1302401,1303438,1304323,1304382,1306337,1307456,1309908,1311861,1324713,1327180,1327432,1327926,1329198,1334998,1335017,1335030,1336017,1336252,1338172,1341373,1346045,1348860,1349185,1352440,1352791,1353920,1354499,1358543,1360013,1360571,1361941,1362796,1362924,1367057,1368796,1399576,1400843,1400935,1403408,1403768,1415093,1415574,1416387,1416863,1418236,1437374,1437384,1437618,1437963,1438158,1439346,1439797,1444755,1445122,1461064,1461137,1461613,1462115,1462153,1462205,1462211,1466060,146
 6085,1466938,1467255,1467363,1469312,1469799,1469892,1469940,1470573,1471286,1475718,1478684,1479518,1487803,1497492,1498840,1498850,1499285,1505795,1505907,1505942,1506594,1508053,1509101,1517602,1517627,1517711,1519376,1526945,1530005,1535539,1537027,1556248,1582373,1634584,1680757,1709811,1729382,1732436,1758600

Modified: jackrabbit/branches/2.4/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java?rev=1758791&r1=1758790&r2=1758791&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/PostMethod.java Thu Sep  1 14:51:07 2016
@@ -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");
     }

Modified: jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java?rev=1758791&r1=1758790&r2=1758791&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavResource.java Thu Sep  1 14:51:07 2016
@@ -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

Modified: jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java?rev=1758791&r1=1758790&r2=1758791&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java Thu Sep  1 14:51:07 2016
@@ -596,7 +596,7 @@ abstract public class AbstractWebdavServ
      */
     protected void doPost(WebdavRequest request, WebdavResponse response,
                           DavResource resource) throws IOException, DavException {
-        doPut(request, response, resource);
+        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
     }
 
     /**
@@ -1384,7 +1384,6 @@ abstract public class AbstractWebdavServ
      * @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) {

Modified: jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java?rev=1758791&r1=1758790&r2=1758791&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/CSRFUtil.java Thu Sep  1 14:51:07 2016
@@ -16,18 +16,20 @@
  */
 package org.apache.jackrabbit.webdav.util;
 
-import org.apache.jackrabbit.webdav.DavMethods;
-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.Locale;
 import java.util.Set;
 
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * <code>CSRFUtil</code>...
  */
@@ -39,7 +41,7 @@ public class CSRFUtil {
     public static final String DISABLED = "disabled";
 
     /**
-     * Request content types for CSRF checking, see JCR-3909
+     * 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(
@@ -92,6 +94,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;
@@ -104,25 +107,62 @@ public class CSRFUtil {
                     allowedReferrerHosts.add(entry.trim());
                 }
             }
+            log.debug("CSRF protection enabled, allowed referrers: " + allowedReferrerHosts);
         }
     }
 
-    public boolean isValidRequest(HttpServletRequest request) throws MalformedURLException {
-        int methodCode = DavMethods.getMethodCode(request.getMethod());
-        if (disabled || DavMethods.DAV_POST != methodCode || !CONTENT_TYPES.contains(request.getContentType())) {
+    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)
+            // empty referrer headers are not allowed for POST + relevant
+            // content types (see JCR-3909)
             if (refHeader == null) {
+                log.debug("POST with content type" + ct + " blocked due to missing referer header field");
                 return false;
             }
 
-            String host = new URL(refHeader).getHost();
-            // test referrer-host equals server or
-            // if it is contained in the set of explicitly allowed host names
-            return host.equals(request.getServerName()) || allowedReferrerHosts.contains(host);
+            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

Modified: jackrabbit/branches/2.4/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/CSRFUtilTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.4/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/CSRFUtilTest.java?rev=1758791&r1=1758790&r2=1758791&view=diff
==============================================================================
--- jackrabbit/branches/2.4/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/CSRFUtilTest.java (original)
+++ jackrabbit/branches/2.4/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/CSRFUtilTest.java Thu Sep  1 14:51:07 2016
@@ -16,17 +16,9 @@
  */
 package org.apache.jackrabbit.webdav.util;
 
-import junit.framework.TestCase;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletInputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,6 +29,15 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.Vector;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import junit.framework.TestCase;
 
 /**
  * <code>CSRFUtilTest</code>...
@@ -55,16 +56,20 @@ public class CSRFUtilTest extends TestCa
         validURLs.add("http://localhost:4503/jackrabbit/server");
         validURLs.add("https://localhost:4503/jackrabbit/server");
         validURLs.add("https://localhost/jackrabbit/server");
+        validURLs.add("//localhost/jackrabbit/server");
+        validURLs.add("/jackrabbit/server");
 
         invalidURLs.add("http://invalidHost/test");
         invalidURLs.add("http://host1:8080/test");
         invalidURLs.add("http://user:pw@host2/test");
     }
 
-    private static void testValid(CSRFUtil util, Collection<String> validURLs, String method, Set<String> contentTypes) throws MalformedURLException {
+    static String[] noContentType = new String[0];
+
+    private static void testValid(CSRFUtil util, Collection<String> validURLs, String method, Set<String> contentTypes) {
         if (null == contentTypes) {
             for (String url : validURLs) {
-                assertTrue(url, util.isValidRequest(createRequest(url, method, null)));
+                assertTrue(url, util.isValidRequest(createRequest(url, method, noContentType)));
             }
         } else {
             for (String contentType : contentTypes) {
@@ -75,10 +80,10 @@ public class CSRFUtilTest extends TestCa
         }
     }
 
-    private static void testInvalid(CSRFUtil util, Collection<String> invalidURLs, String method, Set<String> contentTypes) throws MalformedURLException {
+    private static void testInvalid(CSRFUtil util, Collection<String> invalidURLs, String method, Set<String> contentTypes) {
         if (null == contentTypes) {
             for (String url : validURLs) {
-                assertFalse(url, util.isValidRequest(createRequest(url, method, null)));
+                assertFalse(url, util.isValidRequest(createRequest(url, method, noContentType)));
             }
         } else {
             for (String contentType : contentTypes) {
@@ -89,8 +94,12 @@ public class CSRFUtilTest extends TestCa
         }
     }
 
+    private static HttpServletRequest createRequest(String url, String method, String[] contentTypes) {
+        return new DummyRequest(url, SERVER_NAME, method, contentTypes);
+    }
+
     private static HttpServletRequest createRequest(String url, String method, String contentType) {
-        return new DummyRequest(url, SERVER_NAME, method, contentType);
+        return new DummyRequest(url, SERVER_NAME, method, new String[] { contentType });
     }
 
     public void testNullConfig() throws Exception {
@@ -109,6 +118,10 @@ public class CSRFUtilTest extends TestCa
         CSRFUtil util = new CSRFUtil("");
         testValid(util, validURLs, POST, CSRFUtil.CONTENT_TYPES);
         assertFalse("no referrer", util.isValidRequest(createRequest(null, POST, "text/plain")));
+        assertFalse("no referrer", util.isValidRequest(createRequest(null, POST, noContentType)));
+        assertFalse("no referrer", util.isValidRequest(createRequest(null, POST, "TEXT/PLAIN; foo=bar")));
+        assertTrue("no referrer", util.isValidRequest(createRequest(null, POST, "application/json")));
+        assertFalse("no referrer", util.isValidRequest(createRequest(null, POST, new String[] { "application/json", "foo/bar" })));
     }
 
     public void testDisabledConfig() throws Exception {
@@ -155,13 +168,13 @@ public class CSRFUtilTest extends TestCa
         private final String referer;
         private final String serverName;
         private final String method;
-        private final String contentType;
+        private final String[] contentTypes;
 
-        private DummyRequest(String referer, String serverName, String method, String contentType) {
+        private DummyRequest(String referer, String serverName, String method, String[] contentTypes) {
             this.referer = referer;
             this.serverName = serverName;
             this.method = method;
-            this.contentType = contentType;
+            this.contentTypes = contentTypes;
         }
 
         //---------------------------------------------< HttpServletRequest >---
@@ -178,6 +191,19 @@ public class CSRFUtilTest extends TestCa
             return serverName;
         }
 
+        public String getContentType() {
+            return contentTypes.length == 0 ? null : contentTypes[0];
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Enumeration getHeaders(String name) {
+            if (name != null && contentTypes.length > 0 && name.toLowerCase(Locale.ENGLISH).equals("content-type")) {
+                return new Vector(Arrays.asList(contentTypes)).elements();
+            } else {
+                return null;
+            }
+        }
+
         //---------------------------------------------------------< unused >---
         public String getAuthType() {
             return null;
@@ -188,9 +214,6 @@ public class CSRFUtilTest extends TestCa
         public long getDateHeader(String name) {
             return 0;
         }
-        public Enumeration getHeaders(String name) {
-            return null;
-        }
         public Enumeration getHeaderNames() {
             return null;
         }
@@ -266,9 +289,6 @@ public class CSRFUtilTest extends TestCa
         public int getContentLength() {
             return 0;
         }
-        public String getContentType() {
-            return contentType;
-        }
         public ServletInputStream getInputStream() throws IOException {
             return null;
         }