2323@ Controller
2424public class UrlForwardTest extends HttpServlet implements Filter {
2525
26- // (1) ORIGINAL
26+ // Spring-related test cases
2727 @ GetMapping ("/bad1" )
2828 public ModelAndView bad1 (String url ) {
2929 return new ModelAndView (url ); // $ hasUrlForward
@@ -91,7 +91,7 @@ public void good1(String url, HttpServletRequest request, HttpServletResponse re
9191 }
9292 }
9393
94- // (2) UnsafeRequestPath
94+ // Non-Spring test cases (UnsafeRequest*Path*)
9595 private static final String BASE_PATH = "/pages" ;
9696
9797 @ Override
@@ -107,12 +107,12 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
107107 }
108108 }
109109
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".
111112 public void doFilter2 (ServletRequest request , ServletResponse response , FilterChain chain )
112113 throws IOException , ServletException {
113114 String path = ((HttpServletRequest ) request ).getServletPath ();
114115
115- // actually BAD since could potentially bypass with ".." encoded as "%2e%2e"?
116116 if (path .startsWith (BASE_PATH ) && !path .contains (".." )) {
117117 request .getRequestDispatcher (path ).forward (request , response ); // $ hasUrlForward
118118 } else {
@@ -125,15 +125,14 @@ public void doFilter3(ServletRequest request, ServletResponse response, FilterCh
125125 throws IOException , ServletException {
126126 String path = ((HttpServletRequest ) request ).getServletPath ();
127127
128- // this is still good, should not flag here..., url-decoding first doesn't matter if looking for exact match... :(
129128 if (path .equals ("/comaction" )) {
130129 request .getRequestDispatcher (path ).forward (request , response );
131130 } else {
132131 chain .doFilter (request , response );
133132 }
134133 }
135134
136- // (3) UnsafeServletRequestDispatch
135+ // Non-Spring test cases (UnsafeServletRequest*Dispatch*)
137136 @ Override
138137 // BAD: Request dispatcher constructed from `ServletContext` without input validation
139138 protected void doGet (HttpServletRequest request , HttpServletResponse response )
@@ -190,49 +189,49 @@ protected void doHead2(HttpServletRequest request, HttpServletResponse response)
190189 String path = request .getParameter ("path" );
191190
192191 // 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`
194192 if (path .startsWith (BASE_PATH )) {
195193 request .getServletContext ().getRequestDispatcher (path ).include (request , response ); // $ hasUrlForward
196194 }
197195 }
198196
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".
200199 protected void doHead3 (HttpServletRequest request , HttpServletResponse response )
201200 throws ServletException , IOException {
202201 String path = request .getParameter ("path" );
203202
204- // actually BAD since could potentially bypass with ".." encoded as "%2e%2e"?
205203 if (path .startsWith (BASE_PATH ) && !path .contains (".." )) {
206204 request .getServletContext ().getRequestDispatcher (path ).include (request , response ); // $ hasUrlForward
207205 }
208206 }
209207
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.
211210 protected void doHead4 (HttpServletRequest request , HttpServletResponse response )
212211 throws ServletException , IOException {
213212 String path = request .getParameter ("path" );
213+
214+ // Since not decoded before normalization, "%2e%2e" can remain in the path
214215 Path requestedPath = Paths .get (BASE_PATH ).resolve (path ).normalize ();
215216
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?
219217 if (requestedPath .startsWith (BASE_PATH )) {
220218 request .getServletContext ().getRequestDispatcher (requestedPath .toString ()).forward (request , response ); // $ hasUrlForward
221219 }
222220 }
223221
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.
225223 protected void doHead5 (HttpServletRequest request , HttpServletResponse response )
226224 throws ServletException , IOException {
227225 String path = request .getParameter ("path" );
226+ // Since not decoded before normalization, "/%57EB-INF" can remain in the path and pass the `startsWith` check.
228227 Path requestedPath = Paths .get (BASE_PATH ).resolve (path ).normalize ();
229228
230229 if (!requestedPath .startsWith ("/WEB-INF" ) && !requestedPath .startsWith ("/META-INF" )) {
231230 request .getServletContext ().getRequestDispatcher (requestedPath .toString ()).forward (request , response ); // $ hasUrlForward
232231 }
233232 }
234233
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
236235 protected void doHead7 (HttpServletRequest request , HttpServletResponse response )
237236 throws ServletException , IOException {
238237 String path = request .getParameter ("path" );
@@ -246,9 +245,9 @@ protected void doHead7(HttpServletRequest request, HttpServletResponse response)
246245 // GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
247246 protected void doHead6 (HttpServletRequest request , HttpServletResponse response )
248247 throws ServletException , IOException {
249- String path = request .getParameter ("path" ); // v
248+ String path = request .getParameter ("path" ); // TODO: remove this debugging comment: // v
250249
251- if (path .contains ("%" )){ // v.getAnAccess()
250+ if (path .contains ("%" )){ // TODO: remove this debugging comment: // v.getAnAccess()
252251 while (path .contains ("%" )) {
253252 path = URLDecoder .decode (path , "UTF-8" );
254253 }
@@ -259,10 +258,53 @@ protected void doHead6(HttpServletRequest request, HttpServletResponse response)
259258 }
260259 }
261260
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+
262304 // GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
263305 protected void doHead8 (HttpServletRequest request , HttpServletResponse response )
264306 throws ServletException , IOException {
265- String path = request .getParameter ("path" ); // v
307+ String path = request .getParameter ("path" ); // TODO: remove this debugging comment: // v
266308 while (path .contains ("%" )) {
267309 path = URLDecoder .decode (path , "UTF-8" );
268310 }
@@ -272,6 +314,7 @@ protected void doHead8(HttpServletRequest request, HttpServletResponse response)
272314 }
273315 }
274316
317+ // TODO: see if can fix?
275318 // FP now....
276319 // GOOD: Request dispatcher with path traversal check and URL decoding in a loop to avoid double-encoding bypass
277320 protected void doHead9 (HttpServletRequest request , HttpServletResponse response )
@@ -288,78 +331,26 @@ protected void doHead9(HttpServletRequest request, HttpServletResponse response)
288331 }
289332 }
290333
291- // New Tests
334+ // BAD: `StaplerResponse.forward` without any checks
292335 public void generateResponse (StaplerRequest req , StaplerResponse rsp , Object obj ) throws IOException , ServletException {
293336 String url = req .getParameter ("target" );
294337 rsp .forward (obj , url , req ); // $ hasUrlForward
295338 }
296339
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 ();
364347
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+ }
365356}
0 commit comments