Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

637638 limit number of form parameters to avoid DOS

  • Loading branch information...
commit 085c79d7d6cfbccc02821ffdb64968593df3e0bf 1 parent f62c6ed
@gregw gregw authored
View
25 jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -101,6 +101,14 @@
* to avoid reparsing headers and cookies that are likely to be the same for
* requests from the same connection.
*
+ * <p>
+ * The form content that a request can process is limited to protect from Denial of Service
+ * attacks. The size in bytes is limited by {@link ContextHandler#getMaxFormContentSize()} or if there is no
+ * context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server} attribute.
+ * The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no
+ * context then the "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
*/
public class Request implements HttpServletRequest
{
@@ -231,7 +239,7 @@ public void extractParameters()
if (content_type != null && content_type.length() > 0)
{
content_type = HttpFields.valueParameters(content_type, null);
-
+
if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState==__NONE &&
(HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
{
@@ -241,16 +249,21 @@ public void extractParameters()
try
{
int maxFormContentSize=-1;
+ int maxFormKeys=-1;
if (_context!=null)
+ {
maxFormContentSize=_context.getContextHandler().getMaxFormContentSize();
+ maxFormKeys=_context.getContextHandler().getMaxFormKeys();
+ }
else
{
- Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
- if (size!=null)
- maxFormContentSize =size.intValue();
+ Number size = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+ maxFormContentSize=size==null?200000:size.intValue();
+ Number keys = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+ maxFormKeys =keys==null?1000:keys.intValue();
}
-
+
if (content_length>maxFormContentSize && maxFormContentSize > 0)
{
throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize);
@@ -258,7 +271,7 @@ public void extractParameters()
InputStream in = getInputStream();
// Add form params to query params
- UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1);
+ UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1,maxFormKeys);
}
catch (IOException e)
{
View
21 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -120,6 +120,7 @@ public static Context getCurrentContext()
private EventListener[] _eventListeners;
private Logger _logger;
private boolean _allowNullPathInfo;
+ private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue();
private boolean _compactPath = false;
private boolean _aliases = false;
@@ -1348,12 +1349,32 @@ public int getMaxFormContentSize()
}
/* ------------------------------------------------------------ */
+ /**
+ * Set the maximum size of a form post, to protect against DOS attacks from large forms.
+ * @param maxSize
+ */
public void setMaxFormContentSize(int maxSize)
{
_maxFormContentSize = maxSize;
}
/* ------------------------------------------------------------ */
+ public int getMaxFormKeys()
+ {
+ return _maxFormKeys;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
+ * @param max
+ */
+ public void setMaxFormKeys(int max)
+ {
+ _maxFormKeys = max;
+ }
+
+ /* ------------------------------------------------------------ */
/**
* @return True if URLs are compacted to replace multiple '/'s with a single '/'
*/
View
62 jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -19,12 +19,16 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
@@ -34,9 +38,11 @@
import junit.framework.Assert;
+import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -740,6 +746,56 @@ public boolean check(HttpServletRequest request,HttpServletResponse response)
assertEquals(null,cookie[1]);
}
+
+ @Test
+ public void testHashDOS() throws Exception
+ {
+ _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
+ _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
+
+ // This file is not distributed - as it is dangerous
+ File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
+ if (!evil_keys.exists())
+ {
+ Log.info("testHashDOS skipped");
+ return;
+ }
+
+ BufferedReader in = new BufferedReader(new FileReader(evil_keys));
+ StringBuilder buf = new StringBuilder(4000000);
+
+ String key=null;
+ buf.append("a=b");
+ while((key=in.readLine())!=null)
+ {
+ buf.append("&").append(key).append("=").append("x");
+ }
+ buf.append("&c=d");
+
+ _handler._checker = new RequestTester()
+ {
+ public boolean check(HttpServletRequest request,HttpServletResponse response)
+ {
+ return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
+ }
+ };
+
+ String request="POST / HTTP/1.1\r\n"+
+ "Host: whatever\r\n"+
+ "Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+
+ "Content-Length: "+buf.length()+"\r\n"+
+ "Connection: close\r\n"+
+ "\r\n"+
+ buf;
+
+ long start=System.currentTimeMillis();
+ String response = _connector.getResponses(request);
+ assertTrue(response.contains("200 OK"));
+ long now=System.currentTimeMillis();
+ assertTrue((now-start)<5000);
+ }
+
+
interface RequestTester
{
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
@@ -754,13 +810,15 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
{
((Request)request).setHandled(true);
- if (request.getContentLength()>0)
+ if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType()))
_content=IO.toString(request.getInputStream());
-
+
if (_checker!=null && _checker.check(request,response))
response.setStatus(200);
else
response.sendError(500);
+
+
}
}
}
View
63 jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -16,6 +16,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;
@@ -78,13 +79,13 @@ public UrlEncoded(String s, String charset)
/* ----------------------------------------------------------------- */
public void decode(String query)
{
- decodeTo(query,this,ENCODING);
+ decodeTo(query,this,ENCODING,-1);
}
/* ----------------------------------------------------------------- */
public void decode(String query,String charset)
{
- decodeTo(query,this,charset);
+ decodeTo(query,this,charset,-1);
}
/* -------------------------------------------------------------- */
@@ -178,6 +179,15 @@ else if (equalsForNullValue)
*/
public static void decodeTo(String content, MultiMap map, String charset)
{
+ decodeTo(content,map,charset,-1);
+ }
+
+ /* -------------------------------------------------------------- */
+ /** Decoded parameters to Map.
+ * @param content the string containing the encoded parameters
+ */
+ public static void decodeTo(String content, MultiMap map, String charset, int maxKeys)
+ {
if (charset==null)
charset=ENCODING;
@@ -208,6 +218,11 @@ else if (value!=null&&value.length()>0)
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
case '=':
if (key!=null)
@@ -343,9 +358,10 @@ else if (buffer.length()>0)
/** Decoded parameters to Map.
* @param in InputSteam to read
* @param map MultiMap to add parameters to
- * @param maxLength maximum length of content to read 0r -1 for no limit
+ * @param maxLength maximum length of content to read or -1 for no limit
+ * @param maxLength maximum number of keys to read or -1 for no limit
*/
- public static void decode88591To(InputStream in, MultiMap map, int maxLength)
+ public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys)
throws IOException
{
synchronized(map)
@@ -375,6 +391,11 @@ else if (value!=null&&value.length()>0)
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
case '=':
@@ -423,9 +444,10 @@ else if (buffer.length()>0)
/** Decoded parameters to Map.
* @param in InputSteam to read
* @param map MultiMap to add parameters to
- * @param maxLength maximum length of content to read 0r -1 for no limit
+ * @param maxLength maximum length of content to read or -1 for no limit
+ * @param maxLength maximum number of keys to read or -1 for no limit
*/
- public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength)
+ public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys)
throws IOException
{
synchronized(map)
@@ -455,6 +477,11 @@ else if (value!=null&&value.length()>0)
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
case '=':
@@ -500,25 +527,20 @@ else if (buffer.length()>0)
}
/* -------------------------------------------------------------- */
- public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException
+ public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
{
InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
- StringBuffer buf = new StringBuffer();
-
- int c;
- int length=0;
- if (maxLength<0)
- maxLength=Integer.MAX_VALUE;
- while ((c=input.read())>0 && length++<maxLength)
- buf.append((char)c);
- decodeTo(buf.toString(),map,ENCODING);
+ StringWriter buf = new StringWriter(8192);
+ IO.copy(input,buf,maxLength);
+
+ decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys);
}
/* -------------------------------------------------------------- */
/** Decoded parameters to Map.
* @param in the stream containing the encoded parameters
*/
- public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength)
+ public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
throws IOException
{
//no charset present, use the configured default
@@ -527,22 +549,21 @@ public static void decodeTo(InputStream in, MultiMap map, String charset, int ma
charset=ENCODING;
}
-
if (StringUtil.__UTF8.equalsIgnoreCase(charset))
{
- decodeUtf8To(in,map,maxLength);
+ decodeUtf8To(in,map,maxLength,maxKeys);
return;
}
if (StringUtil.__ISO_8859_1.equals(charset))
{
- decode88591To(in,map,maxLength);
+ decode88591To(in,map,maxLength,maxKeys);
return;
}
if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
{
- decodeUtf16To(in,map,maxLength);
+ decodeUtf16To(in,map,maxLength,maxKeys);
return;
}
View
4 jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
@@ -178,7 +178,7 @@ public void testUrlEncodedStream()
{
ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0]));
MultiMap m = new MultiMap();
- UrlEncoded.decodeTo(in, m, charsets[i][1], -1);
+ UrlEncoded.decodeTo(in, m, charsets[i][1], -1,-1);
System.err.println(m);
assertEquals(i+" stream length",4,m.size());
assertEquals(i+" stream name\\n","value 0",m.getString("name\n"));
@@ -192,7 +192,7 @@ public void testUrlEncodedStream()
{
ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes());
MultiMap m2 = new MultiMap();
- UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1);
+ UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1,-1);
assertEquals("stream length",1,m2.size());
assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name"));
}

0 comments on commit 085c79d

Please sign in to comment.
Something went wrong with that request. Please try again.