You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by fm...@apache.org on 2013/05/03 15:07:18 UTC

svn commit: r1478765 - in /chemistry/opencmis/trunk: chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/ chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java...

Author: fmui
Date: Fri May  3 13:07:18 2013
New Revision: 1478765

URL: http://svn.apache.org/r1478765
Log:
Web Services server: added protection against oversized XML attacks

Added:
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java   (with props)
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java   (with props)
Modified:
    chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/CmisWebServicesServlet.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java
    chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CappedInputStreamTest.java

Modified: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java?rev=1478765&r1=1478764&r2=1478765&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java Fri May  3 13:07:18 2013
@@ -257,7 +257,9 @@ public class MimeHelper {
 
             // check content type
             String multipartContentType = token.getValue();
-            if (multipartContentType == null || !multipartContentType.equalsIgnoreCase("multipart/form-data")) {
+            if (multipartContentType == null
+                    || !(multipartContentType.equalsIgnoreCase("multipart/form-data") || multipartContentType
+                            .equalsIgnoreCase("multipart/related"))) {
                 return null;
             }
 

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/CmisWebServicesServlet.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/CmisWebServicesServlet.java?rev=1478765&r1=1478764&r2=1478765&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/CmisWebServicesServlet.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/CmisWebServicesServlet.java Fri May  3 13:07:18 2013
@@ -60,6 +60,8 @@ public class CmisWebServicesServlet exte
 
     private static final Logger LOG = LoggerFactory.getLogger(CmisWebServicesServlet.class.getName());
 
+    private static final int MAX_SOAP_SIZE = 10 * 1024 * 1024;
+
     private static final String CMIS10_PATH = "/WEB-INF/cmis10/";
     private static final String CMIS11_PATH = "/WEB-INF/cmis11/";
 
@@ -152,7 +154,7 @@ public class CmisWebServicesServlet exte
             return;
         }
 
-        super.service(request, response);
+        super.service(new ProtectionRequestWrapper(request, MAX_SOAP_SIZE), response);
     }
 
     private void printXml(HttpServletRequest request, HttpServletResponse response, String doc, UrlBuilder baseUrl)

Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java?rev=1478765&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java Fri May  3 13:07:18 2013
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.chemistry.opencmis.server.impl.webservices;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
+
+/**
+ * This request wrapper checks if the request is a multipart request (required
+ * by MTOM) and checks if the first part is not bigger than the provide max
+ * size. This protects the Web Services endpoint from oversized XML attacks.
+ */
+public class ProtectionRequestWrapper extends HttpServletRequestWrapper {
+
+    private static final String MULTIPART = "multipart/";
+
+    private final ServletInputStream inputStream;
+
+    public ProtectionRequestWrapper(HttpServletRequest request, int max) throws ServletException {
+        super(request);
+
+        // check multipart
+        String contentType = request.getContentType();
+        if (contentType == null || !contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
+            throw new ServletException("Invalid multipart request!");
+        }
+
+        // get boundary
+        byte[] boundary = MimeHelper.getBoundaryFromMultiPart(contentType);
+        if (boundary == null) {
+            throw new ServletException("Invalid multipart request!");
+        }
+
+        // set up checked stream
+        try {
+            inputStream = new CheckServletInputStream(super.getInputStream(), boundary, max);
+        } catch (IOException e) {
+            throw new ServletException(e);
+        }
+    }
+
+    @Override
+    public ServletInputStream getInputStream() throws IOException {
+        return inputStream;
+    }
+
+    public static class CheckServletInputStream extends ServletInputStream {
+
+        private static final byte CR = 0x0D;
+        private static final byte LF = 0x0A;
+        private static final byte DASH = 0x2D;
+
+        private final InputStream stream;
+        private final byte[] boundary;
+        private final int max;
+        private byte[] linebuffer;
+        private int pos;
+        private int count;
+        private int boundariesFound;
+
+        public CheckServletInputStream(InputStream stream, byte[] boundary, int max) {
+            this.stream = stream;
+            this.boundary = boundary;
+            this.max = max + 2 * (boundary.length + 6);
+            linebuffer = new byte[64 * 1024];
+            pos = 0;
+            count = 0;
+            boundariesFound = 0;
+        }
+
+        @Override
+        public boolean markSupported() {
+            return false;
+        }
+
+        @Override
+        public synchronized void mark(int readlimit) {
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+        }
+
+        @Override
+        public int available() throws IOException {
+            return stream.available();
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = stream.read();
+
+            if (boundariesFound == 2) {
+                return b;
+            }
+
+            addToLineBuffer((byte) b);
+
+            return b;
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            return read(b, 0, b.length);
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            if (len == 0) {
+                return 0;
+            }
+
+            int r = stream.read(b, off, len);
+
+            if (boundariesFound == 2) {
+                return r;
+            }
+
+            addToLineBuffer(b, off, r);
+
+            return r;
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            if (n <= 0) {
+                return 0;
+            }
+
+            return read(new byte[(n > 8 * 1024 ? 8 * 1024 : (int) n)]);
+        }
+
+        @Override
+        public void close() throws IOException {
+            stream.close();
+        }
+
+        private void checkBoundary(int startPos) {
+            int lastStartPos = 0;
+            for (int i = startPos; i < pos; i++) {
+                if (linebuffer[i] == LF) {
+                    if (countBoundaries(lastStartPos, i)) {
+                        return;
+                    }
+
+                    lastStartPos = i + 1;
+                }
+            }
+
+            if (lastStartPos > 0) {
+                if (lastStartPos == pos) {
+                    pos = 0;
+                } else {
+                    System.arraycopy(linebuffer, lastStartPos, linebuffer, 0, pos - lastStartPos);
+                    pos = pos - lastStartPos;
+                }
+            }
+        }
+
+        private boolean countBoundaries(int startPos, int newLinePos) {
+            if (isBoundary(startPos, newLinePos)) {
+                boundariesFound++;
+
+                if (boundariesFound == 2) {
+                    // found boundary a second time within the size
+                    // limit -> request is ok, no more checks necessary
+                    linebuffer = null;
+                }
+            }
+
+            return boundariesFound > 1;
+        }
+
+        private boolean isBoundary(int startPos, int newLinePos) {
+            // a boundary consists of two dashes, the boundary and a CR
+            // -> boundary line length == boundary length + three characters
+            if (newLinePos - startPos == boundary.length + 3) {
+                if (linebuffer[startPos] == DASH && linebuffer[startPos + 1] == DASH
+                        && linebuffer[startPos + boundary.length + 2] == CR) {
+
+                    for (int i = 0; i < boundary.length; i++) {
+                        if (linebuffer[startPos + i + 2] != boundary[i]) {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Adds a byte to the line buffer.
+         */
+        private void addToLineBuffer(byte b) throws IOException {
+            if (pos == linebuffer.length) {
+                expandBuffer(1);
+            }
+
+            linebuffer[pos++] = (byte) b;
+
+            if (b == LF) {
+                checkBoundary(pos - 1);
+            }
+
+            if (boundariesFound < 2) {
+                count++;
+                if (count > max) {
+                    throw new IOException("SOAP message too big!");
+                }
+            }
+        }
+
+        /**
+         * Adds a buffer to the line buffer.
+         */
+        private void addToLineBuffer(byte[] b, int off, int len) throws IOException {
+            if (len <= 0) {
+                return;
+            }
+
+            if (pos + len >= linebuffer.length) {
+                expandBuffer(len);
+            }
+
+            System.arraycopy(b, off, linebuffer, pos, len);
+            pos += len;
+
+            checkBoundary(pos - len);
+
+            if (boundariesFound < 2) {
+                count += len;
+                if (count > max) {
+                    throw new IOException("SOAP message too big!");
+                }
+            }
+        }
+
+        /**
+         * Expand the line buffer.
+         */
+        private void expandBuffer(int len) throws IOException {
+            if (pos + len > max) {
+                throw new IOException("SOAP message too big!");
+            }
+
+            int expand = (len < 64 * 1024 ? 64 * 1024 : len);
+            byte[] newBuffer = new byte[linebuffer.length + expand];
+            System.arraycopy(linebuffer, 0, newBuffer, 0, pos);
+            linebuffer = newBuffer;
+        }
+    }
+}

Propchange: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/impl/webservices/ProtectionRequestWrapper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java?rev=1478765&r1=1478764&r2=1478765&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/main/java/org/apache/chemistry/opencmis/server/shared/ThresholdOutputStream.java Fri May  3 13:07:18 2013
@@ -411,6 +411,10 @@ public class ThresholdOutputStream exten
                 return -1;
             }
 
+            if (len == 0) {
+                return 0;
+            }
+
             if ((pos + len) > bufSize) {
                 len = (bufSize - pos);
             }
@@ -427,12 +431,12 @@ public class ThresholdOutputStream exten
                 return -1;
             }
 
-            if ((pos + n) > bufSize) {
-                n = bufSize - pos;
+            if (n <= 0) {
+                return 0;
             }
 
-            if (n < 0) {
-                return 0;
+            if ((pos + n) > bufSize) {
+                n = bufSize - pos;
             }
 
             pos += n;

Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CappedInputStreamTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CappedInputStreamTest.java?rev=1478765&r1=1478764&r2=1478765&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CappedInputStreamTest.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CappedInputStreamTest.java Fri May  3 13:07:18 2013
@@ -63,6 +63,7 @@ public class CappedInputStreamTest {
             byteBuffer[i] = (byte) (i >= goodBegin && i <= goodEnd ? 1 : 0);
         }
 
+        // test read with buffer
         ByteArrayInputStream originStream = new ByteArrayInputStream(byteBuffer);
 
         try {
@@ -88,5 +89,25 @@ public class CappedInputStreamTest {
                 fail();
             }
         }
+
+        // test single byte read
+        originStream = new ByteArrayInputStream(byteBuffer);
+
+        try {
+            CappedInputStream stream = new CappedInputStream(originStream, max);
+
+            int b = 0;
+            while ((b = stream.read()) > -1) {
+                if (b == 1) {
+                    stream.deductBytes(1);
+                }
+            }
+
+            stream.close();
+        } catch (Exception e) {
+            if (success) {
+                fail();
+            }
+        }
     }
 }

Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java?rev=1478765&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java Fri May  3 13:07:18 2013
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.chemistry.opencmis.server.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+
+import org.apache.chemistry.opencmis.server.impl.webservices.ProtectionRequestWrapper;
+import org.junit.Test;
+
+public class CheckServletInputStreamTest {
+
+    private static final String BOUNDARY = "test.boundray";
+
+    @Test
+    public void testStream1() {
+        baseTest(200, 50, 20, 10, true);
+    }
+
+    @Test
+    public void testStream2() {
+        baseTest(100 * 1024, 10 * 1024, 8 * 1024, 4 * 1024, true);
+    }
+
+    @Test
+    public void testStream3() {
+        baseTest(100 * 1024, 8 * 1024, 10 * 1024, 4 * 1024, false);
+    }
+
+    @Test
+    public void testStream4() {
+        baseTest(100 * 1024, 10, 7, 4 * 1024, true);
+    }
+
+    @Test
+    public void testStream5() {
+        baseTest(100 * 1024, 99 * 1024, 2 * 1024, 11, true);
+    }
+
+    @Test
+    public void testStream6() {
+        baseTest(100 * 1024, 10 * 1024, 10 * 1024, 8 * 1024, true);
+    }
+
+    @Test
+    public void testStream7() {
+        baseTest(100 * 1024, 10 * 1024, 10 * 1024 + 1, 8 * 1024, false);
+    }
+
+    @Test
+    public void testStream8() {
+        baseTest(100 * 1024, 10 * 1024, 8 * 1024, 120 * 1024, false);
+    }
+
+    @Test
+    public void testStream9() {
+        baseTest(100 * 1024, 80 * 1024, 79 * 1024, 1234, true);
+    }
+
+    private void baseTest(int bufferSize, int max, int soap, int readBufferSize, boolean success) {
+
+        byte[] boundaryBytes = ("\r\n--" + BOUNDARY + "\r\n").getBytes();
+
+        byte[] byteBuffer = new byte[bufferSize + (boundaryBytes.length) * 3 + 2];
+
+        System.arraycopy(boundaryBytes, 0, byteBuffer, 0, boundaryBytes.length);
+
+        for (int i = 0; i < bufferSize; i++) {
+
+            if (i == soap) {
+                System.arraycopy(boundaryBytes, 0, byteBuffer, i + boundaryBytes.length, boundaryBytes.length);
+            }
+            if (i < soap) {
+                byteBuffer[i + boundaryBytes.length] = (byte) 'S';
+            } else {
+                byteBuffer[i + boundaryBytes.length * 2] = (byte) 'C';
+            }
+
+        }
+        System.arraycopy(boundaryBytes, 0, byteBuffer, bufferSize + boundaryBytes.length * 2, boundaryBytes.length);
+        byteBuffer[byteBuffer.length - 4] = '-';
+        byteBuffer[byteBuffer.length - 3] = '-';
+        byteBuffer[byteBuffer.length - 2] = '\r';
+        byteBuffer[byteBuffer.length - 1] = '\n';
+
+        // test read with buffer
+        ByteArrayInputStream originStream = new ByteArrayInputStream(byteBuffer);
+
+        try {
+            ProtectionRequestWrapper.CheckServletInputStream stream = new ProtectionRequestWrapper.CheckServletInputStream(
+                    originStream, BOUNDARY.getBytes(), max);
+
+            int countS = 0;
+            int countC = 0;
+
+            byte[] buffer = new byte[readBufferSize];
+            int b;
+
+            assertEquals(0, stream.read(byteBuffer, 0, 0));
+
+            while ((b = stream.read(buffer)) > -1) {
+                for (int i = 0; i < b; i++) {
+                    if (buffer[i] == 'S') {
+                        countS++;
+                    }
+                    if (buffer[i] == 'C') {
+                        countC++;
+                    }
+                }
+            }
+
+            stream.close();
+
+            assertEquals(soap, countS);
+            assertEquals(bufferSize - soap, countC);
+        } catch (Exception e) {
+            if (success) {
+                fail();
+            }
+        }
+
+        // test single byte read
+        originStream = new ByteArrayInputStream(byteBuffer);
+
+        try {
+            ProtectionRequestWrapper.CheckServletInputStream stream = new ProtectionRequestWrapper.CheckServletInputStream(
+                    originStream, BOUNDARY.getBytes(), max);
+
+            int countS = 0;
+            int countC = 0;
+
+            int b;
+            while ((b = stream.read()) > -1) {
+                if (b == 'S') {
+                    countS++;
+                }
+                if (b == 'C') {
+                    countC++;
+                }
+            }
+
+            stream.close();
+
+            assertEquals(soap, countS);
+            assertEquals(bufferSize - soap, countC);
+        } catch (Exception e) {
+            if (success) {
+                fail();
+            }
+        }
+    }
+}

Propchange: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings-classes/src/test/java/org/apache/chemistry/opencmis/server/impl/CheckServletInputStreamTest.java
------------------------------------------------------------------------------
    svn:eol-style = native