+ * 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.
+ *
* @author gregw
*
*/
@@ -1546,16 +1553,23 @@ public class Request implements HttpServ
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.mortbay.jetty.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);
@@ -1563,7 +1577,7 @@ public class Request implements HttpServ
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)
{
--- a/modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java 2012-01-16 14:38:35.000000000 -0500
+++ b/modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java 2012-01-16 14:45:16.000000000 -0500
@@ -15,10 +15,13 @@
package org.mortbay.jetty;
-
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
+import java.util.HashMap;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
@@ -28,10 +31,14 @@ import javax.servlet.http.HttpServletRes
import junit.framework.TestCase;
import org.mortbay.jetty.Request;
+import org.mortbay.jetty.MimeTypes;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.HandlerCollection;
import org.mortbay.util.IO;
import org.mortbay.util.StringUtil;
+import org.mortbay.log.Log;
+
+
/**
* @author gregw
@@ -482,7 +489,52 @@ public class RequestTest extends TestCas
assertEquals("value7" ,cookie[7]);
}
-
+ 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
@@ -498,8 +550,8 @@ public class RequestTest extends TestCas
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
{
((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))
--- a/modules/util/src/main/java/org/mortbay/util/UrlEncoded.java 2012-01-16 14:47:05.000000000 -0500
+++ b/modules/util/src/main/java/org/mortbay/util/UrlEncoded.java 2012-01-16 16:59:21.000000000 -0500
@@ -17,9 +17,11 @@ package org.mortbay.util;
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;
+import org.mortbay.log.Log;
/* ------------------------------------------------------------ */
@@ -73,13 +75,13 @@ public class UrlEncoded extends MultiMap
/* ----------------------------------------------------------------- */
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);
}
/* -------------------------------------------------------------- */
@@ -174,6 +176,15 @@ public class UrlEncoded extends MultiMap
*/
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;
@@ -204,6 +215,11 @@ public class UrlEncoded extends MultiMap
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys));
+ return;
+ }
break;
case '=':
if (key!=null)
@@ -320,9 +336,10 @@ public class UrlEncoded extends MultiMap
/** 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)
@@ -352,6 +369,11 @@ public class UrlEncoded extends MultiMap
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys));
+ return;
+ }
break;
case '=':
@@ -400,9 +422,10 @@ public class UrlEncoded extends MultiMap
/** 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)
@@ -432,6 +455,11 @@ public class UrlEncoded extends MultiMap
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys));
+ return;
+ }
break;
case '=':
@@ -477,43 +505,38 @@ public class UrlEncoded extends MultiMap
}
/* -------------------------------------------------------------- */
- 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++