Ruby: move ActionDispatch::Request logic out of ActionController.qll

This commit is contained in:
Alex Ford
2023-05-23 13:48:58 +01:00
parent 9b4914c3f6
commit 27729af088
3 changed files with 150 additions and 144 deletions

View File

@@ -83,7 +83,8 @@ class ActionControllerClass extends DataFlow::ClassNode {
}
}
private DataFlow::LocalSourceNode actionControllerInstance() {
// TODO: private
DataFlow::LocalSourceNode actionControllerInstance() {
result = any(ActionControllerClass cls).getSelf()
}
@@ -204,149 +205,6 @@ private class ActionControllerParamsCall extends ParamsCallImpl {
}
}
/** Modeling for `ActionDispatch::Request`. */
private module Request {
/**
* A call to `request` from within a controller. This is an instance of
* `ActionDispatch::Request`.
*/
private class RequestNode extends DataFlow::CallNode {
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
}
/**
* A method call on `request`.
*/
private class RequestMethodCall extends DataFlow::CallNode {
RequestMethodCall() {
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
}
}
abstract private class RequestInputAccess extends RequestMethodCall,
Http::Server::RequestInputAccess::Range
{
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
}
/**
* A method call on `request` which returns request parameters.
*/
private class ParametersCall extends RequestInputAccess {
ParametersCall() {
this.getMethodName() =
[
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
"filtered_parameters"
]
}
override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}
/** A method call on `request` which returns part or all of the request path. */
private class PathCall extends RequestInputAccess {
PathCall() {
this.getMethodName() =
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
}
/** A method call on `request` which returns a specific request header. */
private class HeadersCall extends RequestInputAccess {
HeadersCall() {
this.getMethodName() =
[
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
"host_authority", "content_type", "host", "hostname", "accept_encoding",
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
]
or
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
// TODO: each_header
/**
* A method call on `request` which returns part or all of the host.
* This can be influenced by headers such as Host and X-Forwarded-Host.
*/
private class HostCall extends RequestInputAccess {
HostCall() {
this.getMethodName() =
[
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
"forwarded_host", "port", "forwarded_port"
]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/**
* A method call on `request` which is influenced by one or more request
* headers.
*/
private class HeaderTaintedCall extends RequestInputAccess {
HeaderTaintedCall() {
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
private module Env {
abstract private class Env extends DataFlow::LocalSourceNode { }
/**
* A method call on `request` which returns the rack env.
* This is a hash containing all the information about the request. Values
* under keys starting with `HTTP_` are user-controlled.
*/
private class RequestEnvCall extends DataFlow::CallNode, Env {
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
}
private import codeql.ruby.frameworks.Rack
private class RackEnv extends Env {
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
}
/**
* A read of a user-controlled parameter from the request env.
*/
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
EnvHttpAccess() {
this = any(Env c).getAMethodCall("[]") and
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
)
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
}
}
}
/** A call to `render` from within a controller. */
private class ActionControllerRenderCall extends RenderCallImpl {
ActionControllerRenderCall() {

View File

@@ -9,6 +9,7 @@
*/
module ActionDispatch {
private import actiondispatch.Mime
private import actiondispatch.Request
import actiondispatch.Routing
class MimeTypeMatchRegExpInterpretation = Mime::MimeTypeMatchRegExpInterpretation;

View File

@@ -0,0 +1,147 @@
/** Modeling for `ActionDispatch::Request`. */
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.frameworks.ActionController
/** Modeling for `ActionDispatch::Request`. */
module Request {
/**
* An instance of `ActionDispatch::Request`.
*/
private class RequestNode extends DataFlow::CallNode {
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
}
/**
* A method call on `request`.
*/
private class RequestMethodCall extends DataFlow::CallNode {
RequestMethodCall() {
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
}
}
abstract private class RequestInputAccess extends RequestMethodCall,
Http::Server::RequestInputAccess::Range
{
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
}
/**
* A method call on `request` which returns request parameters.
*/
private class ParametersCall extends RequestInputAccess {
ParametersCall() {
this.getMethodName() =
[
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
"filtered_parameters"
]
}
override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}
/** A method call on `request` which returns part or all of the request path. */
private class PathCall extends RequestInputAccess {
PathCall() {
this.getMethodName() =
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
}
/** A method call on `request` which returns a specific request header. */
private class HeadersCall extends RequestInputAccess {
HeadersCall() {
this.getMethodName() =
[
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
"host_authority", "content_type", "host", "hostname", "accept_encoding",
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
]
or
// Request headers are prefixed with `HTTP_` to distinguish them from
// "headers" supplied by Rack middleware.
this.getMethodName() = ["get_header", "fetch_header"] and
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
// TODO: each_header
/**
* A method call on `request` which returns part or all of the host.
* This can be influenced by headers such as Host and X-Forwarded-Host.
*/
private class HostCall extends RequestInputAccess {
HostCall() {
this.getMethodName() =
[
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
"forwarded_host", "port", "forwarded_port"
]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/**
* A method call on `request` which is influenced by one or more request
* headers.
*/
private class HeaderTaintedCall extends RequestInputAccess {
HeaderTaintedCall() {
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
}
/** A method call on `request` which returns the request body. */
private class BodyCall extends RequestInputAccess {
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
}
private module Env {
abstract private class Env extends DataFlow::LocalSourceNode { }
/**
* A method call on `request` which returns the rack env.
* This is a hash containing all the information about the request. Values
* under keys starting with `HTTP_` are user-controlled.
*/
private class RequestEnvCall extends DataFlow::CallNode, Env {
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
}
private import codeql.ruby.frameworks.Rack
private class RackEnv extends Env {
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
}
/**
* A read of a user-controlled parameter from the request env.
*/
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
EnvHttpAccess() {
this = any(Env c).getAMethodCall("[]") and
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
)
}
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
}
}
}