Return-Path: X-Original-To: apmail-chemistry-commits-archive@www.apache.org Delivered-To: apmail-chemistry-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 1F46D109F7 for ; Fri, 3 May 2013 13:07:51 +0000 (UTC) Received: (qmail 59037 invoked by uid 500); 3 May 2013 13:07:51 -0000 Delivered-To: apmail-chemistry-commits-archive@chemistry.apache.org Received: (qmail 58900 invoked by uid 500); 3 May 2013 13:07:47 -0000 Mailing-List: contact commits-help@chemistry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@chemistry.apache.org Delivered-To: mailing list commits@chemistry.apache.org Received: (qmail 58712 invoked by uid 99); 3 May 2013 13:07:45 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 03 May 2013 13:07:45 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 03 May 2013 13:07:41 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 0108023888CD; Fri, 3 May 2013 13:07:19 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: 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... Date: Fri, 03 May 2013 13:07:18 -0000 To: commits@chemistry.apache.org From: fmui@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20130503130719.0108023888CD@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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