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