Skip to content

Commit 8409746

Browse files
authored
Merge pull request #7286 from luchua-bc/java/unsafe-url-forward-dispatch
Java: CWE-552 Query to detect unsafe request dispatcher usage
2 parents 63672ca + 1e32514 commit 8409746

File tree

10 files changed

+507
-38
lines changed

10 files changed

+507
-38
lines changed

java/ql/lib/semmle/code/java/frameworks/Servlets.qll

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,27 @@ predicate isRequestGetParamMethod(MethodAccess ma) {
347347
ma.getMethod() instanceof ServletRequestGetParameterMapMethod or
348348
ma.getMethod() instanceof HttpServletRequestGetQueryStringMethod
349349
}
350+
351+
/** The Java EE RequestDispatcher. */
352+
class RequestDispatcher extends RefType {
353+
RequestDispatcher() {
354+
this.hasQualifiedName(["javax.servlet", "jakarta.servlet"], "RequestDispatcher") or
355+
this.hasQualifiedName("javax.portlet", "PortletRequestDispatcher")
356+
}
357+
}
358+
359+
/** The `getRequestDispatcher` method. */
360+
class GetRequestDispatcherMethod extends Method {
361+
GetRequestDispatcherMethod() {
362+
this.getReturnType() instanceof RequestDispatcher and
363+
this.getName() = "getRequestDispatcher"
364+
}
365+
}
366+
367+
/** The request dispatch method. */
368+
class RequestDispatchMethod extends Method {
369+
RequestDispatchMethod() {
370+
this.getDeclaringType() instanceof RequestDispatcher and
371+
this.hasName(["forward", "include"])
372+
}
373+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// BAD: no URI validation
2+
String returnURL = request.getParameter("returnURL");
3+
RequestDispatcher rd = sc.getRequestDispatcher(returnURL);
4+
rd.forward(request, response);
5+
6+
// GOOD: check for a trusted prefix, ensuring path traversal is not used to erase that prefix:
7+
// (alternatively use `Path.normalize` instead of checking for `..`)
8+
if (!returnURL.contains("..") && returnURL.hasPrefix("/pages")) { ... }
9+
// Also GOOD: check for a forbidden prefix, ensuring URL-encoding is not used to evade the check:
10+
// (alternatively use `URLDecoder.decode` before `hasPrefix`)
11+
if (returnURL.hasPrefix("/internal") && !returnURL.contains("%")) { ... }

java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.qhelp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,47 @@
55

66

77
<overview>
8-
<p>Constructing a server-side redirect path with user input could allow an attacker to download application binaries
8+
<p>Constructing a server-side redirect path with user input could allow an attacker to download application binaries
99
(including application classes or jar files) or view arbitrary files within protected directories.</p>
1010

1111
</overview>
1212
<recommendation>
1313

14-
<p>In order to prevent untrusted URL forwarding, it is recommended to avoid concatenating user input directly into the forwarding URL.</p>
14+
<p>Unsanitized user provided data must not be used to construct the path for URL forwarding. In order to prevent
15+
untrusted URL forwarding, it is recommended to avoid concatenating user input directly into the forwarding URL.
16+
Instead, user input should be checked against allowed (e.g., must come within <code>user_content/</code>) or disallowed
17+
(e.g. must not come within <code>/internal</code>) paths, ensuring that neither path traversal using <code>../</code>
18+
or URL encoding are used to evade these checks.
19+
</p>
1520

1621
</recommendation>
1722
<example>
1823

1924
<p>The following examples show the bad case and the good case respectively.
2025
The <code>bad</code> methods show an HTTP request parameter being used directly in a URL forward
21-
without validating the input, which may cause file leakage. In <code>good1</code> method,
26+
without validating the input, which may cause file leakage. In the <code>good1</code> method,
2227
ordinary forwarding requests are shown, which will not cause file leakage.
2328
</p>
2429

2530
<sample src="UnsafeUrlForward.java" />
2631

32+
<p>The following examples show an HTTP request parameter or request path being used directly in a
33+
request dispatcher of Java EE without validating the input, which allows sensitive file exposure
34+
attacks. It also shows how to remedy the problem by validating the user input.
35+
</p>
36+
37+
<sample src="UnsafeServletRequestDispatch.java" />
38+
2739
</example>
2840
<references>
29-
<li>File Disclosure: <a href="https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.file_disclosure_spring">Unsafe Url Forward</a>.</li>
41+
<li>File Disclosure:
42+
<a href="https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.file_disclosure_spring">Unsafe Url Forward</a>.
43+
</li>
44+
<li>Jakarta Javadoc:
45+
<a href="https://jakarta.ee/specifications/webprofile/9/apidocs/jakarta/servlet/servletrequest#getRequestDispatcher-java.lang.String-">Security vulnerability with unsafe usage of RequestDispatcher</a>.
46+
</li>
47+
<li>Micro Focus:
48+
<a href="https://vulncat.fortify.com/en/detail?id=desc.dataflow.java.file_disclosure_j2ee">File Disclosure: J2EE</a>
49+
</li>
3050
</references>
3151
</qhelp>

java/ql/src/experimental/Security/CWE/CWE-552/UnsafeUrlForward.ql

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
11
/**
2-
* @name Unsafe url forward from remote source
3-
* @description URL forward based on unvalidated user-input
2+
* @name Unsafe URL forward or dispatch from remote source
3+
* @description URL forward or dispatch based on unvalidated user-input
44
* may cause file information disclosure.
55
* @kind path-problem
66
* @problem.severity error
77
* @precision high
8-
* @id java/unsafe-url-forward
8+
* @id java/unsafe-url-forward-dispatch
99
* @tags security
1010
* external/cwe-552
1111
*/
1212

1313
import java
1414
import UnsafeUrlForward
1515
import semmle.code.java.dataflow.FlowSources
16-
import semmle.code.java.frameworks.Servlets
1716
import DataFlow::PathGraph
1817

19-
private class StartsWithSanitizer extends DataFlow::BarrierGuard {
20-
StartsWithSanitizer() {
21-
this.(MethodAccess).getMethod().hasName("startsWith") and
22-
this.(MethodAccess).getMethod().getDeclaringType() instanceof TypeString and
23-
this.(MethodAccess).getMethod().getNumberOfParameters() = 1
24-
}
25-
26-
override predicate checks(Expr e, boolean branch) {
27-
e = this.(MethodAccess).getQualifier() and branch = true
28-
}
29-
}
30-
3118
class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
3219
UnsafeUrlForwardFlowConfig() { this = "UnsafeUrlForwardFlowConfig" }
3320

@@ -45,11 +32,15 @@ class UnsafeUrlForwardFlowConfig extends TaintTracking::Configuration {
4532

4633
override predicate isSink(DataFlow::Node sink) { sink instanceof UnsafeUrlForwardSink }
4734

35+
override predicate isSanitizer(DataFlow::Node node) { node instanceof UnsafeUrlForwardSanitizer }
36+
4837
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
49-
guard instanceof StartsWithSanitizer
38+
guard instanceof UnsafeUrlForwardBarrierGuard
5039
}
5140

52-
override predicate isSanitizer(DataFlow::Node node) { node instanceof UnsafeUrlForwardSanitizer }
41+
override DataFlow::FlowFeature getAFeature() {
42+
result instanceof DataFlow::FeatureHasSourceCallContext
43+
}
5344
}
5445

5546
from DataFlow::PathNode source, DataFlow::PathNode sink, UnsafeUrlForwardFlowConfig conf

0 commit comments

Comments
 (0)