-
Notifications
You must be signed in to change notification settings - Fork 2k
Ruby: rack - model redirect responses #13289
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
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
e7e0cf5
ruby: add Rack::ResponseNode class
alexrford c87c266
ruby: add Rack::ResponseNode#getAStatusCode
alexrford f8d2cbb
ruby: rack responses implement are HTTP responses
alexrford c3ab867
ruby: start restructuring rack
alexrford b2958f8
ruby: rack - add redirect responses
alexrford a5a15f3
Ruby: restructure rack model
alexrford 1966487
ruby: slightly expand a TODO
alexrford 4905a70
Ruby: update rack test output
alexrford 40da7d4
ruby: make a predicate private
alexrford 24635df
ruby: add some qldoc for rack
alexrford 23e2279
ruby: rack - modelling -> modeling
alexrford b62a02f
ruby: remove unused field
alexrford 57508b2
ruby: Limit rack PotentialResponseNode to things that look like they …
alexrford a5d8db6
Ruby: fix qldoc
alexrford 0a7ae58
Ruby: revert to simpler Rack PotentialResponseNode def and use TypeBa…
alexrford c531b94
Ruby: add a change note for rack redirect support
alexrford 21b4f88
ruby: fix qldoc
alexrford 397a809
Merge remote-tracking branch 'origin/main' into rb/rack-redirect
alexrford b462004
Ruby: fix use of deprecated predicate
alexrford af1ca7f
Update ruby/ql/lib/codeql/ruby/frameworks/rack/internal/App.qll
alexrford 977ceb8
Ruby: rack - remove PotentialResponseNode#getAStatusCode
alexrford 75ccbe5
Ruby: rack - use Mimetype rather than MimeType in predicate names for…
alexrford 7aec22c
Ruby: rack - remove MIME modelling
alexrford 8ef8a0d
qlformat
alexrford 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
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,4 @@ | ||
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * HTTP redirect responses from Rack applications are now recognized as a potential sink for open redirect alerts. |
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,53 @@ | ||
| /** | ||
| * Provides modeling for Rack applications. | ||
| */ | ||
|
|
||
| private import codeql.ruby.ApiGraphs | ||
| private import codeql.ruby.DataFlow | ||
| private import codeql.ruby.typetracking.TypeTracker | ||
| private import Response::Private as RP | ||
|
|
||
| /** A method node for a method named `call`. */ | ||
| private class CallMethodNode extends DataFlow::MethodNode { | ||
| CallMethodNode() { this.getMethodName() = "call" } | ||
| } | ||
|
|
||
| private DataFlow::LocalSourceNode trackRackResponse(TypeBackTracker t, CallMethodNode call) { | ||
| t.start() and | ||
| result = call.getAReturnNode().getALocalSource() | ||
| or | ||
| exists(TypeBackTracker t2 | result = trackRackResponse(t2, call).backtrack(t2, t)) | ||
| } | ||
|
|
||
| private RP::PotentialResponseNode trackRackResponse(CallMethodNode call) { | ||
| result = trackRackResponse(TypeBackTracker::end(), call) | ||
| } | ||
|
|
||
| /** | ||
| * Provides modeling for Rack applications. | ||
| */ | ||
| module App { | ||
| /** | ||
| * A class that may be a rack application. | ||
| * This is a class that has a `call` method that takes a single argument | ||
| * (traditionally called `env`) and returns a rack-compatible response. | ||
| */ | ||
| class AppCandidate extends DataFlow::ClassNode { | ||
| private CallMethodNode call; | ||
| private RP::PotentialResponseNode resp; | ||
|
|
||
| AppCandidate() { | ||
| call = this.getInstanceMethod("call") and | ||
| call.getNumberOfParameters() = 1 and | ||
| resp = trackRackResponse(call) | ||
| } | ||
|
|
||
| /** | ||
| * Gets the environment of the request, which is the lone parameter to the `call` method. | ||
| */ | ||
| DataFlow::ParameterNode getEnv() { result = call.getParameter(0) } | ||
|
|
||
| /** Gets the response returned from a request to this application. */ | ||
| RP::PotentialResponseNode getResponse() { result = resp } | ||
| } | ||
| } | ||
82 changes: 82 additions & 0 deletions
82
ruby/ql/lib/codeql/ruby/frameworks/rack/internal/Response.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,82 @@ | ||
| /** | ||
| * Provides modeling for the `Response` component of the `Rack` library. | ||
| */ | ||
|
|
||
| private import codeql.ruby.AST | ||
| private import codeql.ruby.ApiGraphs | ||
| private import codeql.ruby.Concepts | ||
| private import codeql.ruby.controlflow.CfgNodes::ExprNodes | ||
| private import codeql.ruby.DataFlow | ||
| private import codeql.ruby.typetracking.TypeTracker | ||
| private import App as A | ||
|
|
||
| /** Contains implementation details for modeling `Rack::Response`. */ | ||
| module Private { | ||
| /** A `DataFlow::Node` that may be a rack response. This is detected heuristically, if something "looks like" a rack response syntactically then we consider it to be a potential response node. */ | ||
| class PotentialResponseNode extends DataFlow::ArrayLiteralNode { | ||
| // [status, headers, body] | ||
| PotentialResponseNode() { this.getNumberOfArguments() = 3 } | ||
|
|
||
| /** Gets the headers returned with this response. */ | ||
| DataFlow::Node getHeaders() { result = this.getElement(1) } | ||
|
|
||
| /** Gets the body of this response. */ | ||
| DataFlow::Node getBody() { result = this.getElement(2) } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Provides modeling for the `Response` component of the `Rack` library. | ||
| */ | ||
| module Public { | ||
| bindingset[headerName] | ||
| private DataFlow::Node getHeaderValue(ResponseNode resp, string headerName) { | ||
| exists(DataFlow::Node headers | headers = resp.getHeaders() | | ||
| // set via `headers.<header_name>=` | ||
| exists( | ||
| DataFlow::CallNode contentTypeAssignment, Assignment assignment, | ||
| DataFlow::PostUpdateNode postUpdateHeaders | ||
| | | ||
| contentTypeAssignment.getMethodName() = headerName.replaceAll("-", "_").toLowerCase() + "=" and | ||
| assignment = | ||
| contentTypeAssignment.getArgument(0).(DataFlow::OperationNode).asOperationAstNode() and | ||
| postUpdateHeaders.(DataFlow::LocalSourceNode).flowsTo(headers) and | ||
| postUpdateHeaders.getPreUpdateNode() = contentTypeAssignment.getReceiver() | ||
| | | ||
| result.asExpr().getExpr() = assignment.getRightOperand() | ||
| ) | ||
| or | ||
| // set within a hash | ||
| exists(DataFlow::HashLiteralNode headersHash | headersHash.flowsTo(headers) | | ||
| result = | ||
| headersHash | ||
| .getElementFromKey(any(ConstantValue v | | ||
| v.getStringlikeValue().toLowerCase() = headerName.toLowerCase() | ||
| )) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| /** A `DataFlow::Node` returned from a rack request. */ | ||
| class ResponseNode extends Private::PotentialResponseNode, Http::Server::HttpResponse::Range { | ||
| ResponseNode() { this = any(A::App::AppCandidate app).getResponse() } | ||
|
|
||
| override DataFlow::Node getBody() { result = this.getElement(2) } | ||
|
|
||
| override DataFlow::Node getMimetypeOrContentTypeArg() { | ||
| result = getHeaderValue(this, "content-type") | ||
| } | ||
|
|
||
| // TODO: is there a sensible value for this? | ||
| override string getMimetypeDefault() { none() } | ||
| } | ||
|
|
||
| /** A `DataFlow::Node` returned from a rack request that has a redirect HTTP status code. */ | ||
| class RedirectResponse extends ResponseNode, Http::Server::HttpRedirectResponse::Range { | ||
| private DataFlow::Node redirectLocation; | ||
|
|
||
| RedirectResponse() { redirectLocation = getHeaderValue(this, "location") } | ||
|
|
||
| override DataFlow::Node getRedirectLocation() { result = redirectLocation } | ||
| } | ||
| } |
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,4 +1,11 @@ | ||
| | rack.rb:1:1:5:3 | HelloWorld | rack.rb:2:12:2:14 | env | | ||
| | rack.rb:7:1:16:3 | Proxy | rack.rb:12:12:12:18 | the_env | | ||
| | rack.rb:18:1:31:3 | Logger | rack.rb:24:12:24:14 | env | | ||
| | rack.rb:45:1:61:3 | Baz | rack.rb:46:12:46:14 | env | | ||
| rackApps | ||
| | rack.rb:1:1:10:3 | HelloWorld | rack.rb:2:12:2:14 | env | | ||
| | rack.rb:12:1:22:3 | Proxy | rack.rb:17:12:17:18 | the_env | | ||
| | rack.rb:24:1:37:3 | Logger | rack.rb:30:12:30:14 | env | | ||
| | rack.rb:39:1:45:3 | Redirector | rack.rb:40:12:40:14 | env | | ||
| | rack.rb:59:1:75:3 | Baz | rack.rb:60:12:60:14 | env | | ||
| rackResponseContentTypes | ||
| | rack.rb:8:5:8:38 | call to [] | rack.rb:7:34:7:45 | "text/plain" | | ||
| | rack.rb:20:5:20:27 | call to [] | rack.rb:19:28:19:38 | "text/html" | | ||
| redirectResponses | ||
| | rack.rb:43:5:43:45 | call to [] | rack.rb:42:30:42:40 | "/foo.html" | |
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,4 +1,17 @@ | ||
| private import codeql.ruby.AST | ||
| private import codeql.ruby.frameworks.Rack | ||
| private import codeql.ruby.DataFlow | ||
|
|
||
| query predicate rackApps(Rack::AppCandidate c, DataFlow::ParameterNode env) { env = c.getEnv() } | ||
| query predicate rackApps(Rack::App::AppCandidate c, DataFlow::ParameterNode env) { | ||
| env = c.getEnv() | ||
| } | ||
|
|
||
| query predicate rackResponseContentTypes( | ||
| Rack::Response::ResponseNode resp, DataFlow::Node contentType | ||
| ) { | ||
| contentType = resp.getMimetypeOrContentTypeArg() | ||
| } | ||
|
|
||
| query predicate redirectResponses(Rack::Response::RedirectResponse resp, DataFlow::Node location) { | ||
| location = resp.getRedirectLocation() | ||
| } |
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
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.