637638 limit number of form parameters to avoid DOS
This commit is contained in:
parent
f62c6edcb9
commit
085c79d7d6
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 '/'
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue