Skip to content

Commit d220b3a

Browse files
Jami CogswellJami Cogswell
authored andcommitted
Java: some updates to test cases
1 parent d9772c1 commit d220b3a

File tree

3 files changed

+90
-101
lines changed

3 files changed

+90
-101
lines changed

java/ql/lib/semmle/code/java/security/PathSanitizer.qll

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ private predicate exactPathMatchGuard(Guard g, Expr e, boolean branch) {
6464
)
6565
}
6666

67-
// TODO: switch back to private if possible
67+
/**
68+
* A sanitizer that protects against path injection vulnerabilities
69+
* by checking for a matching path.
70+
*/
6871
class ExactPathMatchSanitizer extends PathInjectionSanitizer {
6972
ExactPathMatchSanitizer() {
7073
this = DataFlow::BarrierGuard<exactPathMatchGuard/3>::getABarrierNode()
@@ -152,8 +155,7 @@ private class DotDotCheckSanitizer extends PathInjectionSanitizer {
152155
}
153156
}
154157

155-
// TODO: switch back to private if possible
156-
class BlockListGuard extends PathGuard instanceof MethodCall {
158+
private class BlockListGuard extends PathGuard instanceof MethodCall {
157159
BlockListGuard() {
158160
(isStringPartialMatch(this) or isPathPrefixMatch(this)) and
159161
isDisallowedWord(super.getAnArgument())
@@ -230,7 +232,6 @@ private predicate isStringPartialMatch(MethodCall ma) {
230232
exists(RefType t | t = ma.getMethod().getDeclaringType() |
231233
t instanceof TypeString or t instanceof StringsKt
232234
) and
233-
// TODO ! Why not use `StringPartialMatchMethod` for the below?
234235
getSourceMethod(ma.getMethod())
235236
.hasName(["contains", "matches", "regionMatches", "indexOf", "lastIndexOf"])
236237
}

java/ql/lib/semmle/code/java/security/UrlForward.qll

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,20 @@ private class FollowsBarrierPrefix extends UrlForwardBarrier {
5050
private class BarrierPrefix extends InterestingPrefix {
5151
BarrierPrefix() {
5252
not this.getStringValue().matches("/WEB-INF/%") and
53-
not this.getStringValue() = "forward:"
53+
not this instanceof ForwardPrefix
5454
}
5555

5656
override int getOffset() { result = 0 }
5757
}
5858

59-
private class UrlPathBarrier extends UrlForwardBarrier {
59+
private class UrlPathBarrier extends UrlForwardBarrier instanceof PathInjectionSanitizer {
6060
UrlPathBarrier() {
61-
this instanceof PathInjectionSanitizer and
62-
(
63-
this instanceof ExactPathMatchSanitizer //TODO: still need a better solution for this edge case...
64-
or
65-
// TODO: these don't enforce order of checks and PathSanitization... make bypass test cases.
66-
this instanceof NoEncodingBarrier
67-
or
68-
this instanceof FullyDecodesBarrier
69-
)
61+
this instanceof ExactPathMatchSanitizer //TODO: still need a better solution for this edge case...
62+
or
63+
// TODO: these don't enforce order of checks and PathSanitization... make bypass test cases.
64+
this instanceof NoEncodingBarrier
65+
or
66+
this instanceof FullyDecodesBarrier
7067
}
7168
}
7269

java/ql/test/query-tests/security/CWE-552/UrlForwardTest.java

Lines changed: 77 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
@Controller
2424
public 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

Comments
 (0)