637638 limit number of form parameters to avoid DOS

This commit is contained in:
Greg Wilkins 2011-12-30 12:00:18 +11:00
parent f62c6edcb9
commit 085c79d7d6
5 changed files with 144 additions and 31 deletions

View File

@ -101,6 +101,14 @@ import org.eclipse.jetty.util.log.Logger;
* 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 class Request implements HttpServletRequest
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 class Request implements HttpServletRequest
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 class Request implements HttpServletRequest
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 File

@ -120,6 +120,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
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,11 +1349,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
}
/* ------------------------------------------------------------ */
/**
* 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 File

@ -19,12 +19,16 @@ import static org.junit.Assert.assertNotSame;
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 javax.servlet.http.HttpServletResponse;
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 class RequestTest
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 class RequestTest
{
((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 File

@ -16,6 +16,7 @@ package org.eclipse.jetty.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;
@ -78,13 +79,13 @@ public class UrlEncoded extends MultiMap implements Cloneable
/* ----------------------------------------------------------------- */
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);
}
/* -------------------------------------------------------------- */
@ -177,6 +178,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
* @param content the string containing the encoded parameters
*/
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 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
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 @@ public class UrlEncoded extends MultiMap implements Cloneable
/** 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 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
key = null;
value=null;
if (maxKeys>0 && map.size()>maxKeys)
{
LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
return;
}
break;
case '=':
@ -423,9 +444,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
/** 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 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
key = null;
value=null;
if (maxKeys>0 && map.size()>maxKeys)
{
LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
return;
}
break;
case '=':
@ -500,25 +527,20 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
/* -------------------------------------------------------------- */
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 class UrlEncoded extends MultiMap implements Cloneable
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 File

@ -178,7 +178,7 @@ public class URLEncodedTest
{
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 class URLEncodedTest
{
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"));
}