-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Java: Promote Unsafe URL Forward query from experimental #14854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jcogs33
merged 40 commits into
github:main
from
jcogs33:jcogs33/unsafe-url-forward-promotion
Mar 29, 2024
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
0d38a96
Java: copy files from experimental
2793f28
Java: move config to Query.qll file
35a083a
Java: update test cases to use inline expectations
915e106
Java: remove path-injection related models and tests for now
2a68299
Java: move MaD models to correct files, delete ones that already exist
4ff884e
Java: remove more path-injection related classes (will maybe add some…
42e3825
Java: convert RequestDispatcherSink to MaD
8d66097
Java: switch StaplerResponse.forward from request-forgery sink to url…
1da1e89
Java: convert SpringModelAndViewSink to MaD
5a9d755
Java: add some comments and minor code reorg
6e7c054
Java: update query metadata and alert message
09bc21d
Java: rename 'UnsafeUrlForward' to 'UrlForward'
c331393
Java: update qhelp
5fa63ab
Java: update/add some TODO comments
e75c96c
Java: combine test cases; add test for StaplerResponse.forward
c8ec301
Java: add change note
911a61d
Java: initial update of barrier and test cases to remove FN
f573032
Java: remove todo comments from ext files
2708e53
Java: remove redundant imports
43b4962
Java: use new 'SimpleTypeSanitizer', and update some non-extending su…
d9772c1
Java: update change note
d220b3a
Java: some updates to test cases
052452b
Java: create UrlDecodeMethod
042dcf9
Java: some updates to UrlPathBarrier code
a807596
Java: add QLDocs to UrlPathBarrier code
a002674
Java: clean up comments on test cases
7310c15
Java: rename SpringUrlForwardSink
c5a59d6
Java: add QLDoc
e99cea3
Java: update UrlPathBarrier to include FollowsBarrierPrefix
04d27f2
Java: adjust prefix barriers
5ac453e
Java: add spurious test case for StringBuilder.append
1b01f26
Java: adjust BarrierPrefix to handle prepended chars
55f7369
Java: performance fix
658fffe
Java: remove experimental files
a8eb1d1
Java: remove experimental tests
35fbc95
Java: remove redundant line
121b24e
Java: remove parentheses
2391fe7
Java: use InlineFlowTest instead of InlineExpectationsTest
40c932a
Java: move UrlForward.qll code to UrlForwardQuery.qll
2f8c4df
docs wording updates
jcogs33 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
8 changes: 0 additions & 8 deletions
8
java/ql/lib/ext/experimental/io.undertow.server.handlers.resource.model.yml
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 0 additions & 16 deletions
16
java/ql/lib/ext/experimental/org.springframework.core.io.model.yml
This file was deleted.
Oops, something went wrong.
4 changes: 2 additions & 2 deletions
4
...perimental/jakarta.servlet.http.model.yml → ...ql/lib/ext/jakarta.servlet.http.model.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
extensions: | ||
- addsTo: | ||
pack: codeql/java-all | ||
extensible: experimentalSourceModel | ||
extensible: sourceModel | ||
data: | ||
- ["jakarta.servlet.http", "HttpServletRequest", True, "getServletPath", "", "", "ReturnValue", "remote", "manual", "unsafe-url-forward"] | ||
- ["jakarta.servlet.http", "HttpServletRequest", True, "getServletPath", "", "", "ReturnValue", "remote", "manual"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
extensions: | ||
- addsTo: | ||
pack: codeql/java-all | ||
extensible: sinkModel | ||
data: | ||
- ["jakarta.servlet", "ServletContext", True, "getRequestDispatcher", "(String)", "", "Argument[0]", "url-forward", "manual"] | ||
- ["jakarta.servlet", "ServletRequest", True, "getRequestDispatcher", "(String)", "", "Argument[0]", "url-forward", "manual"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
extensions: | ||
- addsTo: | ||
pack: codeql/java-all | ||
extensible: sinkModel | ||
data: | ||
- ["javax.portlet", "PortletContext", True, "getRequestDispatcher", "(String)", "", "Argument[0]", "url-forward", "manual"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
extensions: | ||
- addsTo: | ||
pack: codeql/java-all | ||
extensible: sinkModel | ||
data: | ||
- ["org.springframework.web.portlet", "ModelAndView", False, "ModelAndView", "", "", "Argument[0]", "url-forward", "manual"] | ||
- ["org.springframework.web.portlet", "ModelAndView", False, "setViewName", "", "", "Argument[0]", "url-forward", "manual"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
extensions: | ||
- addsTo: | ||
pack: codeql/java-all | ||
extensible: sinkModel | ||
data: | ||
- ["org.springframework.web.servlet", "ModelAndView", False, "ModelAndView", "", "", "Argument[0]", "url-forward", "manual"] | ||
- ["org.springframework.web.servlet", "ModelAndView", False, "setViewName", "", "", "Argument[0]", "url-forward", "manual"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
203 changes: 203 additions & 0 deletions
203
java/ql/lib/semmle/code/java/security/UrlForwardQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
/** Provides classes and a taint-tracking configuration to reason about unsafe URL forwarding. */ | ||
|
||
import java | ||
private import semmle.code.java.dataflow.ExternalFlow | ||
private import semmle.code.java.dataflow.FlowSources | ||
private import semmle.code.java.dataflow.StringPrefixes | ||
private import semmle.code.java.security.PathSanitizer | ||
private import semmle.code.java.controlflow.Guards | ||
private import semmle.code.java.security.Sanitizers | ||
|
||
/** A URL forward sink. */ | ||
abstract class UrlForwardSink extends DataFlow::Node { } | ||
|
||
/** | ||
* A default sink representing methods susceptible to URL | ||
* forwarding attacks. | ||
*/ | ||
private class DefaultUrlForwardSink extends UrlForwardSink { | ||
DefaultUrlForwardSink() { sinkNode(this, "url-forward") } | ||
} | ||
|
||
/** | ||
* An expression appended (perhaps indirectly) to `"forward:"` | ||
* and reachable from a Spring entry point. | ||
*/ | ||
private class SpringUrlForwardPrefixSink extends UrlForwardSink { | ||
SpringUrlForwardPrefixSink() { | ||
any(SpringRequestMappingMethod srmm).polyCalls*(this.getEnclosingCallable()) and | ||
appendedToForwardPrefix(this) | ||
} | ||
} | ||
|
||
pragma[nomagic] | ||
private predicate appendedToForwardPrefix(DataFlow::ExprNode exprNode) { | ||
exists(ForwardPrefix fp | exprNode.asExpr() = fp.getAnAppendedExpression()) | ||
} | ||
|
||
private class ForwardPrefix extends InterestingPrefix { | ||
ForwardPrefix() { this.getStringValue() = "forward:" } | ||
|
||
override int getOffset() { result = 0 } | ||
} | ||
|
||
/** A URL forward barrier. */ | ||
abstract class UrlForwardBarrier extends DataFlow::Node { } | ||
|
||
private class PrimitiveBarrier extends UrlForwardBarrier instanceof SimpleTypeSanitizer { } | ||
|
||
/** | ||
* A barrier for values appended to a "redirect:" prefix. | ||
* These results are excluded because they should be handled | ||
* by the `java/unvalidated-url-redirection` query instead. | ||
*/ | ||
private class RedirectPrefixBarrier extends UrlForwardBarrier { | ||
RedirectPrefixBarrier() { this.asExpr() = any(RedirectPrefix fp).getAnAppendedExpression() } | ||
} | ||
|
||
private class RedirectPrefix extends InterestingPrefix { | ||
RedirectPrefix() { this.getStringValue() = "redirect:" } | ||
|
||
override int getOffset() { result = 0 } | ||
} | ||
|
||
/** | ||
* A value that is the result of prepending a string that prevents | ||
* any value from controlling the path of a URL. | ||
*/ | ||
private class FollowsBarrierPrefix extends UrlForwardBarrier { | ||
FollowsBarrierPrefix() { this.asExpr() = any(BarrierPrefix fp).getAnAppendedExpression() } | ||
} | ||
|
||
private class BarrierPrefix extends InterestingPrefix { | ||
int offset; | ||
|
||
BarrierPrefix() { | ||
// Matches strings that look like when prepended to untrusted input, they will restrict | ||
// the path of a URL: for example, anything containing `?` or `#`. | ||
exists(this.getStringValue().regexpFind("[?#]", 0, offset)) | ||
or | ||
this.(CharacterLiteral).getValue() = ["?", "#"] and offset = 0 | ||
} | ||
|
||
override int getOffset() { result = offset } | ||
} | ||
|
||
/** | ||
* A barrier that protects against path injection vulnerabilities | ||
* while accounting for URL encoding. | ||
*/ | ||
private class UrlPathBarrier extends UrlForwardBarrier instanceof PathInjectionSanitizer { | ||
UrlPathBarrier() { | ||
this instanceof ExactPathMatchSanitizer or | ||
this instanceof NoUrlEncodingBarrier or | ||
this instanceof FullyDecodesUrlBarrier | ||
} | ||
} | ||
|
||
/** A call to a method that decodes a URL. */ | ||
abstract class UrlDecodeCall extends MethodCall { } | ||
|
||
private class DefaultUrlDecodeCall extends UrlDecodeCall { | ||
DefaultUrlDecodeCall() { | ||
this.getMethod() instanceof UrlDecodeMethod or | ||
this.getMethod().hasQualifiedName("org.eclipse.jetty.util.URIUtil", "URIUtil", "decodePath") | ||
} | ||
} | ||
|
||
/** A repeated call to a method that decodes a URL. */ | ||
abstract class RepeatedUrlDecodeCall extends MethodCall { } | ||
|
||
private class DefaultRepeatedUrlDecodeCall extends RepeatedUrlDecodeCall instanceof UrlDecodeCall { | ||
DefaultRepeatedUrlDecodeCall() { this.getAnEnclosingStmt() instanceof LoopStmt } | ||
} | ||
|
||
/** A method call that checks a string for URL encoding. */ | ||
abstract class CheckUrlEncodingCall extends MethodCall { } | ||
|
||
private class DefaultCheckUrlEncodingCall extends CheckUrlEncodingCall { | ||
DefaultCheckUrlEncodingCall() { | ||
this.getMethod() instanceof StringContainsMethod and | ||
this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "%" | ||
} | ||
} | ||
|
||
/** A guard that looks for a method call that checks for URL encoding. */ | ||
private class CheckUrlEncodingGuard extends Guard instanceof CheckUrlEncodingCall { | ||
Expr getCheckedExpr() { result = this.(MethodCall).getQualifier() } | ||
} | ||
|
||
/** Holds if `g` is guard for a URL that does not contain URL encoding. */ | ||
private predicate noUrlEncodingGuard(Guard g, Expr e, boolean branch) { | ||
e = g.(CheckUrlEncodingGuard).getCheckedExpr() and | ||
branch = false | ||
or | ||
branch = false and | ||
g.(Expr).getType() instanceof BooleanType and | ||
( | ||
exists(CheckUrlEncodingCall call, AssignExpr ae | | ||
ae.getSource() = call and | ||
e = call.getQualifier() and | ||
g = ae.getDest() | ||
) | ||
or | ||
exists(CheckUrlEncodingCall call, LocalVariableDeclExpr vde | | ||
vde.getInitOrPatternSource() = call and | ||
e = call.getQualifier() and | ||
g = vde.getAnAccess() | ||
) | ||
) | ||
} | ||
|
||
/** A barrier for URLs that do not contain URL encoding. */ | ||
private class NoUrlEncodingBarrier extends DataFlow::Node { | ||
NoUrlEncodingBarrier() { this = DataFlow::BarrierGuard<noUrlEncodingGuard/3>::getABarrierNode() } | ||
} | ||
|
||
/** Holds if `g` is guard for a URL that is fully decoded. */ | ||
private predicate fullyDecodesUrlGuard(Expr e) { | ||
exists(CheckUrlEncodingGuard g, RepeatedUrlDecodeCall decodeCall | | ||
e = g.getCheckedExpr() and | ||
g.controls(decodeCall.getBasicBlock(), true) | ||
) | ||
} | ||
|
||
/** A barrier for URLs that are fully decoded. */ | ||
private class FullyDecodesUrlBarrier extends DataFlow::Node { | ||
FullyDecodesUrlBarrier() { | ||
exists(Variable v, Expr e | this.asExpr() = v.getAnAccess() | | ||
fullyDecodesUrlGuard(e) and | ||
e = v.getAnAccess() and | ||
e.getBasicBlock().bbDominates(this.asExpr().getBasicBlock()) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A taint-tracking configuration for reasoning about URL forwarding. | ||
*/ | ||
module UrlForwardFlowConfig implements DataFlow::ConfigSig { | ||
predicate isSource(DataFlow::Node source) { | ||
source instanceof ThreatModelFlowSource and | ||
// excluded due to FPs | ||
not exists(MethodCall mc, Method m | | ||
m instanceof HttpServletRequestGetRequestUriMethod or | ||
m instanceof HttpServletRequestGetRequestUrlMethod or | ||
m instanceof HttpServletRequestGetPathMethod | ||
| | ||
mc.getMethod() = m and | ||
mc = source.asExpr() | ||
) | ||
} | ||
|
||
predicate isSink(DataFlow::Node sink) { sink instanceof UrlForwardSink } | ||
|
||
predicate isBarrier(DataFlow::Node node) { node instanceof UrlForwardBarrier } | ||
|
||
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext } | ||
} | ||
|
||
/** | ||
* Taint-tracking flow for URL forwarding. | ||
*/ | ||
module UrlForwardFlow = TaintTracking::Global<UrlForwardFlowConfig>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
public class UrlForward extends HttpServlet { | ||
private static final String VALID_FORWARD = "https://cwe.mitre.org/data/definitions/552.html"; | ||
|
||
protected void doGet(HttpServletRequest request, HttpServletResponse response) | ||
throws ServletException, IOException { | ||
ServletConfig cfg = getServletConfig(); | ||
ServletContext sc = cfg.getServletContext(); | ||
|
||
// BAD: a request parameter is incorporated without validation into a URL forward | ||
sc.getRequestDispatcher(request.getParameter("target")).forward(request, response); | ||
|
||
// GOOD: the request parameter is validated against a known fixed string | ||
if (VALID_FORWARD.equals(request.getParameter("target"))) { | ||
sc.getRequestDispatcher(VALID_FORWARD).forward(request, response); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.