1 |
diff -up ./modules/jetty/src/main/java/org/mortbay/jetty/handler/ContextHandler.java.fix ./modules/jetty/src/main/java/org/mortbay/jetty/handler/ContextHandler.java |
2 |
--- ./modules/jetty/src/main/java/org/mortbay/jetty/handler/ContextHandler.java.fix 2012-01-16 13:35:18.000000000 -0500 |
3 |
+++ ./modules/jetty/src/main/java/org/mortbay/jetty/handler/ContextHandler.java 2012-01-16 14:31:48.000000000 -0500 |
4 |
@@ -118,6 +118,7 @@ public class ContextHandler extends Hand |
5 |
private Logger _logger; |
6 |
private boolean _shutdown; |
7 |
private boolean _allowNullPathInfo; |
8 |
+ private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue(); |
9 |
private int _maxFormContentSize=Integer.getInteger("org.mortbay.jetty.Request.maxFormContentSize",200000).intValue(); |
10 |
private boolean _compactPath=false; |
11 |
|
12 |
@@ -1058,11 +1059,30 @@ public class ContextHandler extends Hand |
13 |
} |
14 |
|
15 |
/* ------------------------------------------------------------ */ |
16 |
+ /** |
17 |
+ * Set the maximum size of a form post, to protect against DOS attacks from large forms. |
18 |
+ * @param maxSize |
19 |
+ */ |
20 |
public void setMaxFormContentSize(int maxSize) |
21 |
{ |
22 |
_maxFormContentSize=maxSize; |
23 |
} |
24 |
|
25 |
+ /* ------------------------------------------------------------ */ |
26 |
+ public int getMaxFormKeys() |
27 |
+ { |
28 |
+ return _maxFormKeys; |
29 |
+ } |
30 |
+ |
31 |
+ /* ------------------------------------------------------------ */ |
32 |
+ /** |
33 |
+ * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys. |
34 |
+ * @param max |
35 |
+ */ |
36 |
+ public void setMaxFormKeys(int max) |
37 |
+ { |
38 |
+ _maxFormKeys = max; |
39 |
+ } |
40 |
|
41 |
/* ------------------------------------------------------------ */ |
42 |
/** |
43 |
diff -up ./modules/jetty/src/main/java/org/mortbay/jetty/Request.java.fix ./modules/jetty/src/main/java/org/mortbay/jetty/Request.java |
44 |
--- ./modules/jetty/src/main/java/org/mortbay/jetty/Request.java.fix 2012-01-16 13:24:22.000000000 -0500 |
45 |
+++ ./modules/jetty/src/main/java/org/mortbay/jetty/Request.java 2012-01-16 13:32:38.000000000 -0500 |
46 |
@@ -98,6 +98,13 @@ import org.mortbay.util.ajax.Continuatio |
47 |
* to avoid reparsing headers and cookies that are likely to be the same for |
48 |
* requests from the same connection. |
49 |
* |
50 |
+ * <p> |
51 |
+ * The form content that a request can process is limited to protect from Denial of Service |
52 |
+ * attacks. The size in bytes is limited by {@link ContextHandler#getMaxFormContentSize()} or if there is no |
53 |
+ * context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server} attribute. |
54 |
+ * The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no |
55 |
+ * context then the "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute. |
56 |
+ * |
57 |
* @author gregw |
58 |
* |
59 |
*/ |
60 |
@@ -1546,16 +1553,23 @@ public class Request implements HttpServ |
61 |
try |
62 |
{ |
63 |
int maxFormContentSize=-1; |
64 |
- |
65 |
+ int maxFormKeys=-1; |
66 |
+ |
67 |
+ |
68 |
+ |
69 |
if (_context!=null) |
70 |
+ { |
71 |
maxFormContentSize=_context.getContextHandler().getMaxFormContentSize(); |
72 |
+ maxFormKeys=_context.getContextHandler().getMaxFormKeys(); |
73 |
+ } |
74 |
else |
75 |
{ |
76 |
- Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.mortbay.jetty.Request.maxFormContentSize"); |
77 |
- if (size!=null) |
78 |
- maxFormContentSize =size.intValue(); |
79 |
+ Number size = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); |
80 |
+ maxFormContentSize=size==null?200000:size.intValue(); |
81 |
+ Number keys = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); |
82 |
+ maxFormKeys =keys==null?1000:keys.intValue(); |
83 |
} |
84 |
- |
85 |
+ |
86 |
if (content_length>maxFormContentSize && maxFormContentSize > 0) |
87 |
{ |
88 |
throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize); |
89 |
@@ -1563,7 +1577,7 @@ public class Request implements HttpServ |
90 |
InputStream in = getInputStream(); |
91 |
|
92 |
// Add form params to query params |
93 |
- UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1); |
94 |
+ UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1,maxFormKeys); |
95 |
} |
96 |
catch (IOException e) |
97 |
{ |
98 |
diff -up ./modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java.fix ./modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java |
99 |
--- ./modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java.fix 2012-01-16 14:38:35.000000000 -0500 |
100 |
+++ ./modules/jetty/src/test/java/org/mortbay/jetty/RequestTest.java 2012-01-16 14:45:16.000000000 -0500 |
101 |
@@ -15,10 +15,13 @@ |
102 |
|
103 |
package org.mortbay.jetty; |
104 |
|
105 |
- |
106 |
+import java.io.BufferedReader; |
107 |
+import java.io.File; |
108 |
+import java.io.FileReader; |
109 |
import java.io.IOException; |
110 |
import java.io.Reader; |
111 |
import java.util.ArrayList; |
112 |
+import java.util.HashMap; |
113 |
|
114 |
import javax.servlet.ServletException; |
115 |
import javax.servlet.http.Cookie; |
116 |
@@ -28,10 +31,14 @@ import javax.servlet.http.HttpServletRes |
117 |
import junit.framework.TestCase; |
118 |
|
119 |
import org.mortbay.jetty.Request; |
120 |
+import org.eclipse.jetty.http.MimeTypes; |
121 |
import org.mortbay.jetty.handler.AbstractHandler; |
122 |
import org.mortbay.jetty.handler.HandlerCollection; |
123 |
import org.mortbay.util.IO; |
124 |
import org.mortbay.util.StringUtil; |
125 |
+import org.eclipse.jetty.util.log.Log; |
126 |
+ |
127 |
+ |
128 |
|
129 |
/** |
130 |
* @author gregw |
131 |
@@ -482,7 +489,52 @@ public class RequestTest extends TestCas |
132 |
assertEquals("value7" ,cookie[7]); |
133 |
} |
134 |
|
135 |
- |
136 |
+ public void testHashDOS() throws Exception |
137 |
+ { |
138 |
+ _server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1); |
139 |
+ _server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000); |
140 |
+ |
141 |
+ // This file is not distributed - as it is dangerous |
142 |
+ File evil_keys = new File("/tmp/keys_mapping_to_zero_2m"); |
143 |
+ if (!evil_keys.exists()) |
144 |
+ { |
145 |
+ Log.info("testHashDOS skipped"); |
146 |
+ return; |
147 |
+ } |
148 |
+ |
149 |
+ BufferedReader in = new BufferedReader(new FileReader(evil_keys)); |
150 |
+ StringBuilder buf = new StringBuilder(4000000); |
151 |
+ |
152 |
+ String key=null; |
153 |
+ buf.append("a=b"); |
154 |
+ while((key=in.readLine())!=null) |
155 |
+ { |
156 |
+ buf.append("&").append(key).append("=").append("x"); |
157 |
+ } |
158 |
+ buf.append("&c=d"); |
159 |
+ |
160 |
+ _handler._checker = new RequestTester() |
161 |
+ { |
162 |
+ public boolean check(HttpServletRequest request,HttpServletResponse response) |
163 |
+ { |
164 |
+ return "b".equals(request.getParameter("a")) && request.getParameter("c")==null; |
165 |
+ } |
166 |
+ }; |
167 |
+ |
168 |
+ String request="POST / HTTP/1.1\r\n"+ |
169 |
+ "Host: whatever\r\n"+ |
170 |
+ "Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+ |
171 |
+ "Content-Length: "+buf.length()+"\r\n"+ |
172 |
+ "Connection: close\r\n"+ |
173 |
+ "\r\n"+ |
174 |
+ buf; |
175 |
+ |
176 |
+ long start=System.currentTimeMillis(); |
177 |
+ String response = _connector.getResponses(request); |
178 |
+ assertTrue(response.contains("200 OK")); |
179 |
+ long now=System.currentTimeMillis(); |
180 |
+ assertTrue((now-start)<5000); |
181 |
+ } |
182 |
|
183 |
|
184 |
interface RequestTester |
185 |
@@ -498,8 +550,8 @@ public class RequestTest extends TestCas |
186 |
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException |
187 |
{ |
188 |
((Request)request).setHandled(true); |
189 |
- |
190 |
- if (request.getContentLength()>0) |
191 |
+ |
192 |
+ if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType())) |
193 |
_content=IO.toString(request.getInputStream()); |
194 |
|
195 |
if (_checker!=null && _checker.check(request,response)) |
196 |
diff -up ./modules/util/src/main/java/org/mortbay/util/UrlEncoded.java.fix ./modules/util/src/main/java/org/mortbay/util/UrlEncoded.java |
197 |
--- ./modules/util/src/main/java/org/mortbay/util/UrlEncoded.java.fix 2012-01-16 14:47:05.000000000 -0500 |
198 |
+++ ./modules/util/src/main/java/org/mortbay/util/UrlEncoded.java 2012-01-16 16:59:21.000000000 -0500 |
199 |
@@ -17,9 +17,11 @@ package org.mortbay.util; |
200 |
import java.io.IOException; |
201 |
import java.io.InputStream; |
202 |
import java.io.InputStreamReader; |
203 |
+import java.io.StringWriter; |
204 |
import java.io.UnsupportedEncodingException; |
205 |
import java.util.Iterator; |
206 |
import java.util.Map; |
207 |
+import org.mortbay.log.Log; |
208 |
|
209 |
|
210 |
/* ------------------------------------------------------------ */ |
211 |
@@ -73,13 +75,13 @@ public class UrlEncoded extends MultiMap |
212 |
/* ----------------------------------------------------------------- */ |
213 |
public void decode(String query) |
214 |
{ |
215 |
- decodeTo(query,this,ENCODING); |
216 |
+ decodeTo(query,this,ENCODING,-1); |
217 |
} |
218 |
|
219 |
/* ----------------------------------------------------------------- */ |
220 |
public void decode(String query,String charset) |
221 |
{ |
222 |
- decodeTo(query,this,charset); |
223 |
+ decodeTo(query,this,charset,-1); |
224 |
} |
225 |
|
226 |
/* -------------------------------------------------------------- */ |
227 |
@@ -174,6 +176,15 @@ public class UrlEncoded extends MultiMap |
228 |
*/ |
229 |
public static void decodeTo(String content, MultiMap map, String charset) |
230 |
{ |
231 |
+ decodeTo(content,map,charset,-1); |
232 |
+ } |
233 |
+ |
234 |
+ /* -------------------------------------------------------------- */ |
235 |
+ /** Decoded parameters to Map. |
236 |
+ * @param content the string containing the encoded parameters |
237 |
+ */ |
238 |
+ public static void decodeTo(String content, MultiMap map, String charset, int maxKeys) |
239 |
+ { |
240 |
if (charset==null) |
241 |
charset=ENCODING; |
242 |
|
243 |
@@ -204,6 +215,11 @@ public class UrlEncoded extends MultiMap |
244 |
} |
245 |
key = null; |
246 |
value=null; |
247 |
+ if (maxKeys>0 && map.size()>maxKeys) |
248 |
+ { |
249 |
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys)); |
250 |
+ return; |
251 |
+ } |
252 |
break; |
253 |
case '=': |
254 |
if (key!=null) |
255 |
@@ -320,9 +336,10 @@ public class UrlEncoded extends MultiMap |
256 |
/** Decoded parameters to Map. |
257 |
* @param in InputSteam to read |
258 |
* @param map MultiMap to add parameters to |
259 |
- * @param maxLength maximum length of content to read 0r -1 for no limit |
260 |
+ * @param maxLength maximum length of content to read or -1 for no limit |
261 |
+ * @param maxLength maximum number of keys to read or -1 for no limit |
262 |
*/ |
263 |
- public static void decode88591To(InputStream in, MultiMap map, int maxLength) |
264 |
+ public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys) |
265 |
throws IOException |
266 |
{ |
267 |
synchronized(map) |
268 |
@@ -352,6 +369,11 @@ public class UrlEncoded extends MultiMap |
269 |
} |
270 |
key = null; |
271 |
value=null; |
272 |
+ if (maxKeys>0 && map.size()>maxKeys) |
273 |
+ { |
274 |
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys)); |
275 |
+ return; |
276 |
+ } |
277 |
break; |
278 |
|
279 |
case '=': |
280 |
@@ -400,9 +422,10 @@ public class UrlEncoded extends MultiMap |
281 |
/** Decoded parameters to Map. |
282 |
* @param in InputSteam to read |
283 |
* @param map MultiMap to add parameters to |
284 |
- * @param maxLength maximum length of content to read 0r -1 for no limit |
285 |
+ * @param maxLength maximum length of content to read or -1 for no limit |
286 |
+ * @param maxLength maximum number of keys to read or -1 for no limit |
287 |
*/ |
288 |
- public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength) |
289 |
+ public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys) |
290 |
throws IOException |
291 |
{ |
292 |
synchronized(map) |
293 |
@@ -432,6 +455,11 @@ public class UrlEncoded extends MultiMap |
294 |
} |
295 |
key = null; |
296 |
value=null; |
297 |
+ if (maxKeys>0 && map.size()>maxKeys) |
298 |
+ { |
299 |
+ Log.warn("maxFormKeys limit exceeded keys>{}",Integer.valueOf(maxKeys)); |
300 |
+ return; |
301 |
+ } |
302 |
break; |
303 |
|
304 |
case '=': |
305 |
@@ -477,43 +505,38 @@ public class UrlEncoded extends MultiMap |
306 |
} |
307 |
|
308 |
/* -------------------------------------------------------------- */ |
309 |
- public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException |
310 |
+ public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException |
311 |
{ |
312 |
InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16); |
313 |
- StringBuffer buf = new StringBuffer(); |
314 |
- |
315 |
- int c; |
316 |
- int length=0; |
317 |
- if (maxLength<0) |
318 |
- maxLength=Integer.MAX_VALUE; |
319 |
- while ((c=input.read())>0 && length++<maxLength) |
320 |
- buf.append((char)c); |
321 |
- decodeTo(buf.toString(),map,ENCODING); |
322 |
+ StringWriter buf = new StringWriter(8192); |
323 |
+ IO.copy(input,buf,maxLength); |
324 |
+ |
325 |
+ decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys); |
326 |
} |
327 |
|
328 |
/* -------------------------------------------------------------- */ |
329 |
/** Decoded parameters to Map. |
330 |
* @param in the stream containing the encoded parameters |
331 |
*/ |
332 |
- public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength) |
333 |
+ public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys) |
334 |
throws IOException |
335 |
{ |
336 |
|
337 |
if (charset==null || StringUtil.__UTF8.equalsIgnoreCase(charset)) |
338 |
{ |
339 |
- decodeUtf8To(in,map,maxLength); |
340 |
+ decodeUtf8To(in,map,maxLength,maxKeys); |
341 |
return; |
342 |
} |
343 |
|
344 |
if (StringUtil.__ISO_8859_1.equals(charset)) |
345 |
{ |
346 |
- decode88591To(in,map,maxLength); |
347 |
+ decode88591To(in,map,maxLength,maxKeys); |
348 |
return; |
349 |
} |
350 |
|
351 |
if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings |
352 |
{ |
353 |
- decodeUtf16To(in,map,maxLength); |
354 |
+ decodeUtf16To(in,map,maxLength,maxKeys); |
355 |
return; |
356 |
} |
357 |
|
358 |
diff -up ./modules/util/src/test/java/org/mortbay/util/URLEncodedTest.java.fix ./modules/util/src/test/java/org/mortbay/util/URLEncodedTest.java |
359 |
--- ./modules/util/src/test/java/org/mortbay/util/URLEncodedTest.java.fix 2012-01-16 15:19:40.000000000 -0500 |
360 |
+++ ./modules/util/src/test/java/org/mortbay/util/URLEncodedTest.java 2012-01-16 15:20:36.000000000 -0500 |
361 |
@@ -163,7 +163,7 @@ public class URLEncodedTest extends juni |
362 |
{ |
363 |
ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0])); |
364 |
MultiMap m = new MultiMap(); |
365 |
- UrlEncoded.decodeTo(in, m, charsets[i][1], -1); |
366 |
+ UrlEncoded.decodeTo(in, m, charsets[i][1], -1, -1); |
367 |
System.err.println(m); |
368 |
assertEquals(i+" stream length",4,m.size()); |
369 |
assertEquals(i+" stream name\\n","value 0",m.getString("name\n")); |
370 |
@@ -177,7 +177,7 @@ public class URLEncodedTest extends juni |
371 |
{ |
372 |
ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes()); |
373 |
MultiMap m2 = new MultiMap(); |
374 |
- UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1); |
375 |
+ UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1, -1); |
376 |
assertEquals("stream length",1,m2.size()); |
377 |
assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name")); |
378 |
} |