23
23
@ Controller
24
24
public class UrlForwardTest extends HttpServlet implements Filter {
25
25
26
- // (1) ORIGINAL
26
+ // Spring-related test cases
27
27
@ GetMapping ("/bad1" )
28
28
public ModelAndView bad1 (String url ) {
29
29
return new ModelAndView (url ); // $ hasUrlForward
@@ -91,7 +91,7 @@ public void good1(String url, HttpServletRequest request, HttpServletResponse re
91
91
}
92
92
}
93
93
94
- // (2) UnsafeRequestPath
94
+ // Non-Spring test cases (UnsafeRequest*Path*)
95
95
private static final String BASE_PATH = "/pages" ;
96
96
97
97
@ Override
@@ -107,12 +107,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
107
107
}
108
108
}
109
109
110
- // GOOD: Request dispatcher from servlet path with check
110
+ // BAD: Request dispatcher from servlet path with check that does not decode
111
+ // the user-supplied path; could bypass check with ".." encoded as "%2e%2e".
111
112
public void doFilter2 (ServletRequest request , ServletResponse response , FilterChain chain )
112
113
throws IOException , ServletException {
113
114
String path = ((HttpServletRequest ) request ).getServletPath ();
114
115
115
- // actually BAD since could potentially bypass with ".." encoded as "%2e%2e"?
116
116
if (path .startsWith (BASE_PATH ) && !path .contains (".." )) {
117
117
request .getRequestDispatcher (path ).forward (request , response ); // $ hasUrlForward
118
118
} else {
@@ -125,15 +125,14 @@ public void doFilter3(ServletRequest request, ServletResponse response, FilterCh
125
125
throws IOException , ServletException {
126
126
String path = ((HttpServletRequest ) request ).getServletPath ();
127
127
128
- // this is still good, should not flag here..., url-decoding first doesn't matter if looking for exact match... :(
129
128
if (path .equals ("/comaction" )) {
130
129
request .getRequestDispatcher (path ).forward (request , response );
131
130
} else {
132
131
chain .doFilter (request , response );
133
132
}
134
133
}
135
134
136
- // (3) UnsafeServletRequestDispatch
135
+ // Non-Spring test cases (UnsafeServletRequest*Dispatch*)
137
136
@ Override
138
137
// BAD: Request dispatcher constructed from `ServletContext` without input validation
139
138
protected void doGet (HttpServletRequest request , HttpServletResponse response )
@@ -190,49 +189,49 @@ protected void doHead2(HttpServletRequest request, HttpServletResponse response)
190
189
String path = request .getParameter ("path" );
191
190
192
191
// A sample payload "/pages/welcome.jsp/../WEB-INF/web.xml" can bypass the `startsWith` check
193
- // The payload "/pages/welcome.jsp/../../%57EB-INF/web.xml" can bypass the check as well since RequestDispatcher will decode `%57` as `W`
194
192
if (path .startsWith (BASE_PATH )) {
195
193
request .getServletContext ().getRequestDispatcher (path ).include (request , response ); // $ hasUrlForward
196
194
}
197
195
}
198
196
199
- // GOOD: Request dispatcher with path traversal check
197
+ // BAD: Request dispatcher with path traversal check that does not decode
198
+ // the user-supplied path; could bypass check with ".." encoded as "%2e%2e".
200
199
protected void doHead3 (HttpServletRequest request , HttpServletResponse response )
201
200
throws ServletException , IOException {
202
201
String path = request .getParameter ("path" );
203
202
204
- // actually BAD since could potentially bypass with ".." encoded as "%2e%2e"?
205
203
if (path .startsWith (BASE_PATH ) && !path .contains (".." )) {
206
204
request .getServletContext ().getRequestDispatcher (path ).include (request , response ); // $ hasUrlForward
207
205
}
208
206
}
209
207
210
- // GOOD: Request dispatcher with path normalization and comparison
208
+ // BAD: Request dispatcher with path normalization and comparison, but
209
+ // does not decode before normalization.
211
210
protected void doHead4 (HttpServletRequest request , HttpServletResponse response )
212
211
throws ServletException , IOException {
213
212
String path = request .getParameter ("path" );
213
+
214
+ // Since not decoded before normalization, "%2e%2e" can remain in the path
214
215
Path requestedPath = Paths .get (BASE_PATH ).resolve (path ).normalize ();
215
216
216
- // /pages/welcome.jsp/../../WEB-INF/web.xml becomes /WEB-INF/web.xml
217
- // /pages/welcome.jsp/../../%57EB-INF/web.xml becomes /%57EB-INF/web.xml
218
- // actually BAD since could potentially bypass with ".." encoded as "%2e%2e": "/pages/welcome.jsp/%2e%2e/%2e%2e/WEB-INF/web.xml" becomes /pages/welcome.jsp/%2e%2e/%2e%2e/WEB-INF/web.xml, which will pass this check and potentially be problematic if decoded later?
219
217
if (requestedPath .startsWith (BASE_PATH )) {
220
218
request .getServletContext ().getRequestDispatcher (requestedPath .toString ()).forward (request , response ); // $ hasUrlForward
221
219
}
222
220
}
223
221
224
- // BAD (original FN) : Request dispatcher with negation check and path normalization, but without URL decoding
222
+ // BAD: Request dispatcher with negation check and path normalization, but without URL decoding.
225
223
protected void doHead5 (HttpServletRequest request , HttpServletResponse response )
226
224
throws ServletException , IOException {
227
225
String path = request .getParameter ("path" );
226
+ // Since not decoded before normalization, "/%57EB-INF" can remain in the path and pass the `startsWith` check.
228
227
Path requestedPath = Paths .get (BASE_PATH ).resolve (path ).normalize ();
229
228
230
229
if (!requestedPath .startsWith ("/WEB-INF" ) && !requestedPath .startsWith ("/META-INF" )) {
231
230
request .getServletContext ().getRequestDispatcher (requestedPath .toString ()).forward (request , response ); // $ hasUrlForward
232
231
}
233
232
}
234
233
235
- // BAD (I added to test decode with no loop) : Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
234
+ // BAD: Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
236
235
protected void doHead7 (HttpServletRequest request , HttpServletResponse response )
237
236
throws ServletException , IOException {
238
237
String path = request .getParameter ("path" );
@@ -246,9 +245,9 @@ protected void doHead7(HttpServletRequest request, HttpServletResponse response)
246
245
// GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
247
246
protected void doHead6 (HttpServletRequest request , HttpServletResponse response )
248
247
throws ServletException , IOException {
249
- String path = request .getParameter ("path" ); // v
248
+ String path = request .getParameter ("path" ); // TODO: remove this debugging comment: // v
250
249
251
- if (path .contains ("%" )){ // v.getAnAccess()
250
+ if (path .contains ("%" )){ // TODO: remove this debugging comment: // v.getAnAccess()
252
251
while (path .contains ("%" )) {
253
252
path = URLDecoder .decode (path , "UTF-8" );
254
253
}
@@ -259,10 +258,53 @@ protected void doHead6(HttpServletRequest request, HttpServletResponse response)
259
258
}
260
259
}
261
260
261
+ // GOOD: Request dispatcher with URL encoding check and path traversal check
262
+ protected void doHead16 (HttpServletRequest request , HttpServletResponse response )
263
+ throws ServletException , IOException {
264
+ String path = request .getParameter ("path" );
265
+
266
+ if (!path .contains ("%" )){
267
+ if (!path .startsWith ("/WEB-INF/" ) && !path .contains (".." )) {
268
+ request .getServletContext ().getRequestDispatcher (path ).include (request , response );
269
+ }
270
+ }
271
+ }
272
+
273
+ // TODO: clean-up
274
+ // BAD (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
275
+ // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
276
+ // having previously been checked against a block-list of forbidden values."
277
+ protected void doHead10 (HttpServletRequest request , HttpServletResponse response )
278
+ throws ServletException , IOException {
279
+ String path = request .getParameter ("path" );
280
+ if (path .contains ("%" )){ // BAD: wrong check
281
+ if (!path .startsWith ("/WEB-INF/" ) && !path .contains (".." )) {
282
+ // if (path.contains("%")){ // BAD: wrong check
283
+ request .getServletContext ().getRequestDispatcher (path ).include (request , response ); // $ hasUrlForward
284
+ // }
285
+ }
286
+ }
287
+ }
288
+
289
+ // TODO: clean-up
290
+ // "GOOD" (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
291
+ // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
292
+ // having previously been checked against a block-list of forbidden values."
293
+ protected void doHead11 (HttpServletRequest request , HttpServletResponse response )
294
+ throws ServletException , IOException {
295
+ String path = request .getParameter ("path" );
296
+
297
+ if (!path .startsWith ("/WEB-INF/" ) && !path .contains (".." )) {
298
+ if (!path .contains ("%" )){ // GOOD: right check
299
+ request .getServletContext ().getRequestDispatcher (path ).include (request , response );
300
+ }
301
+ }
302
+ }
303
+
262
304
// GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
263
305
protected void doHead8 (HttpServletRequest request , HttpServletResponse response )
264
306
throws ServletException , IOException {
265
- String path = request .getParameter ("path" ); // v
307
+ String path = request .getParameter ("path" ); // TODO: remove this debugging comment: // v
266
308
while (path .contains ("%" )) {
267
309
path = URLDecoder .decode (path , "UTF-8" );
268
310
}
@@ -272,6 +314,7 @@ protected void doHead8(HttpServletRequest request, HttpServletResponse response)
272
314
}
273
315
}
274
316
317
+ // TODO: see if can fix?
275
318
// FP now....
276
319
// GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
277
320
protected void doHead9 (HttpServletRequest request , HttpServletResponse response )
@@ -288,78 +331,26 @@ protected void doHead9(HttpServletRequest request, HttpServletResponse response)
288
331
}
289
332
}
290
333
291
- // New Tests
334
+ // BAD: `StaplerResponse.forward` without any checks
292
335
public void generateResponse (StaplerRequest req , StaplerResponse rsp , Object obj ) throws IOException , ServletException {
293
336
String url = req .getParameter ("target" );
294
337
rsp .forward (obj , url , req ); // $ hasUrlForward
295
338
}
296
339
297
- // Other Tests for edge cases:
298
- // // GOOD (I added): Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
299
- // // testing `if` stmt requirement for BB controlling
300
- // protected void doHead12(HttpServletRequest request, HttpServletResponse response)
301
- // throws ServletException, IOException {
302
- // String path = request.getParameter("path");
303
- // if (path.contains("%")) {
304
- // while (path.contains("%")) {
305
- // path = URLDecoder.decode(path, "UTF-8");
306
- // }
307
- // }
308
- // if (!path.startsWith("/WEB-INF/") && !path.contains("..")) {
309
- // request.getServletContext().getRequestDispatcher(path).include(request, response);
310
- // }
311
- // }
312
- // // BAD (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
313
- // // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
314
- // // having previously been checked against a block-list of forbidden values."
315
- // protected void doHead8(HttpServletRequest request, HttpServletResponse response)
316
- // throws ServletException, IOException {
317
- // String path = request.getParameter("path");
318
-
319
- // if (!path.startsWith("/WEB-INF/") && !path.contains("..")) {
320
- // boolean hasEncoding = path.contains("%"); // BAD: doesn't do anything with the check...
321
- // request.getServletContext().getRequestDispatcher(path).include(request, response); // $ hasUrlForward
322
- // }
323
- // }
324
- // // BAD (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
325
- // // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
326
- // // having previously been checked against a block-list of forbidden values."
327
- // protected void doHead9(HttpServletRequest request, HttpServletResponse response)
328
- // throws ServletException, IOException {
329
- // String path = request.getParameter("path");
330
-
331
- // boolean hasEncoding = path.contains("%"); // BAD: doesn't do anything with the check... and check comes BEFORE blocklist so guard should not trigger
332
- // if (!path.startsWith("/WEB-INF/") && !path.contains("..")) {
333
- // request.getServletContext().getRequestDispatcher(path).include(request, response); // $ hasUrlForward
334
- // }
335
- // }
336
-
337
- // // BAD (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
338
- // // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
339
- // // having previously been checked against a block-list of forbidden values."
340
- // protected void doHead10(HttpServletRequest request, HttpServletResponse response)
341
- // throws ServletException, IOException {
342
- // String path = request.getParameter("path");
343
-
344
- // if (!path.startsWith("/WEB-INF/") && !path.contains("..")) {
345
- // if (path.contains("%")){ // BAD: wrong check
346
- // request.getServletContext().getRequestDispatcher(path).include(request, response); // $ hasUrlForward
347
- // }
348
- // }
349
- // }
350
-
351
- // // "GOOD" (I added): Request dispatcher with path traversal check and single URL decoding; may be vulnerable to double-encoding
352
- // // Tests urlEncoding BarrierGuard "a guard that considers a string safe because it is checked for URL encoding sequences,
353
- // // having previously been checked against a block-list of forbidden values."
354
- // protected void doHead11(HttpServletRequest request, HttpServletResponse response)
355
- // throws ServletException, IOException {
356
- // String path = request.getParameter("path");
357
-
358
- // if (!path.startsWith("/WEB-INF/") && !path.contains("..")) {
359
- // if (!path.contains("%")){ // GOOD: right check
360
- // request.getServletContext().getRequestDispatcher(path).include(request, response);
361
- // }
362
- // }
363
- // }
340
+ // QHelp example
341
+ private static final String VALID_FORWARD = "https://cwe.mitre.org/data/definitions/552.html" ;
342
+
343
+ protected void doGet2 (HttpServletRequest request , HttpServletResponse response )
344
+ throws ServletException , IOException {
345
+ ServletConfig cfg = getServletConfig ();
346
+ ServletContext sc = cfg .getServletContext ();
364
347
348
+ // BAD: a request parameter is incorporated without validation into a URL forward
349
+ sc .getRequestDispatcher (request .getParameter ("target" )).forward (request , response ); // $ hasUrlForward
350
+
351
+ // GOOD: the request parameter is validated against a known fixed string
352
+ if (VALID_FORWARD .equals (request .getParameter ("target" ))) {
353
+ sc .getRequestDispatcher (VALID_FORWARD ).forward (request , response );
354
+ }
355
+ }
365
356
}
0 commit comments