Skip to content

Commit 27729af

Browse files
committed
Ruby: move ActionDispatch::Request logic out of ActionController.qll
1 parent 9b4914c commit 27729af

File tree

3 files changed

+150
-144
lines changed

3 files changed

+150
-144
lines changed

ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll

Lines changed: 2 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ class ActionControllerClass extends DataFlow::ClassNode {
8383
}
8484
}
8585

86-
private DataFlow::LocalSourceNode actionControllerInstance() {
86+
// TODO: private
87+
DataFlow::LocalSourceNode actionControllerInstance() {
8788
result = any(ActionControllerClass cls).getSelf()
8889
}
8990

@@ -204,149 +205,6 @@ private class ActionControllerParamsCall extends ParamsCallImpl {
204205
}
205206
}
206207

207-
/** Modeling for `ActionDispatch::Request`. */
208-
private module Request {
209-
/**
210-
* A call to `request` from within a controller. This is an instance of
211-
* `ActionDispatch::Request`.
212-
*/
213-
private class RequestNode extends DataFlow::CallNode {
214-
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
215-
}
216-
217-
/**
218-
* A method call on `request`.
219-
*/
220-
private class RequestMethodCall extends DataFlow::CallNode {
221-
RequestMethodCall() {
222-
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
223-
}
224-
}
225-
226-
abstract private class RequestInputAccess extends RequestMethodCall,
227-
Http::Server::RequestInputAccess::Range
228-
{
229-
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
230-
}
231-
232-
/**
233-
* A method call on `request` which returns request parameters.
234-
*/
235-
private class ParametersCall extends RequestInputAccess {
236-
ParametersCall() {
237-
this.getMethodName() =
238-
[
239-
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
240-
"filtered_parameters"
241-
]
242-
}
243-
244-
override Http::Server::RequestInputKind getKind() {
245-
result = Http::Server::parameterInputKind()
246-
}
247-
}
248-
249-
/** A method call on `request` which returns part or all of the request path. */
250-
private class PathCall extends RequestInputAccess {
251-
PathCall() {
252-
this.getMethodName() =
253-
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
254-
}
255-
256-
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
257-
}
258-
259-
/** A method call on `request` which returns a specific request header. */
260-
private class HeadersCall extends RequestInputAccess {
261-
HeadersCall() {
262-
this.getMethodName() =
263-
[
264-
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
265-
"host_authority", "content_type", "host", "hostname", "accept_encoding",
266-
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
267-
]
268-
or
269-
// Request headers are prefixed with `HTTP_` to distinguish them from
270-
// "headers" supplied by Rack middleware.
271-
this.getMethodName() = ["get_header", "fetch_header"] and
272-
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
273-
}
274-
275-
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
276-
}
277-
278-
// TODO: each_header
279-
/**
280-
* A method call on `request` which returns part or all of the host.
281-
* This can be influenced by headers such as Host and X-Forwarded-Host.
282-
*/
283-
private class HostCall extends RequestInputAccess {
284-
HostCall() {
285-
this.getMethodName() =
286-
[
287-
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
288-
"forwarded_host", "port", "forwarded_port"
289-
]
290-
}
291-
292-
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
293-
}
294-
295-
/**
296-
* A method call on `request` which is influenced by one or more request
297-
* headers.
298-
*/
299-
private class HeaderTaintedCall extends RequestInputAccess {
300-
HeaderTaintedCall() {
301-
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
302-
}
303-
304-
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
305-
}
306-
307-
/** A method call on `request` which returns the request body. */
308-
private class BodyCall extends RequestInputAccess {
309-
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
310-
311-
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
312-
}
313-
314-
private module Env {
315-
abstract private class Env extends DataFlow::LocalSourceNode { }
316-
317-
/**
318-
* A method call on `request` which returns the rack env.
319-
* This is a hash containing all the information about the request. Values
320-
* under keys starting with `HTTP_` are user-controlled.
321-
*/
322-
private class RequestEnvCall extends DataFlow::CallNode, Env {
323-
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
324-
}
325-
326-
private import codeql.ruby.frameworks.Rack
327-
328-
private class RackEnv extends Env {
329-
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
330-
}
331-
332-
/**
333-
* A read of a user-controlled parameter from the request env.
334-
*/
335-
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
336-
EnvHttpAccess() {
337-
this = any(Env c).getAMethodCall("[]") and
338-
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
339-
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
340-
)
341-
}
342-
343-
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
344-
345-
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
346-
}
347-
}
348-
}
349-
350208
/** A call to `render` from within a controller. */
351209
private class ActionControllerRenderCall extends RenderCallImpl {
352210
ActionControllerRenderCall() {

ruby/ql/lib/codeql/ruby/frameworks/ActionDispatch.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
module ActionDispatch {
1111
private import actiondispatch.Mime
12+
private import actiondispatch.Request
1213
import actiondispatch.Routing
1314

1415
class MimeTypeMatchRegExpInterpretation = Mime::MimeTypeMatchRegExpInterpretation;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/** Modeling for `ActionDispatch::Request`. */
2+
3+
private import codeql.ruby.Concepts
4+
private import codeql.ruby.DataFlow
5+
private import codeql.ruby.frameworks.ActionController
6+
7+
/** Modeling for `ActionDispatch::Request`. */
8+
module Request {
9+
/**
10+
* An instance of `ActionDispatch::Request`.
11+
*/
12+
private class RequestNode extends DataFlow::CallNode {
13+
RequestNode() { this = actionControllerInstance().getAMethodCall("request") }
14+
}
15+
16+
/**
17+
* A method call on `request`.
18+
*/
19+
private class RequestMethodCall extends DataFlow::CallNode {
20+
RequestMethodCall() {
21+
any(RequestNode r).(DataFlow::LocalSourceNode).flowsTo(this.getReceiver())
22+
}
23+
}
24+
25+
abstract private class RequestInputAccess extends RequestMethodCall,
26+
Http::Server::RequestInputAccess::Range
27+
{
28+
override string getSourceType() { result = "ActionDispatch::Request#" + this.getMethodName() }
29+
}
30+
31+
/**
32+
* A method call on `request` which returns request parameters.
33+
*/
34+
private class ParametersCall extends RequestInputAccess {
35+
ParametersCall() {
36+
this.getMethodName() =
37+
[
38+
"parameters", "params", "GET", "POST", "query_parameters", "request_parameters",
39+
"filtered_parameters"
40+
]
41+
}
42+
43+
override Http::Server::RequestInputKind getKind() {
44+
result = Http::Server::parameterInputKind()
45+
}
46+
}
47+
48+
/** A method call on `request` which returns part or all of the request path. */
49+
private class PathCall extends RequestInputAccess {
50+
PathCall() {
51+
this.getMethodName() =
52+
["path", "filtered_path", "fullpath", "original_fullpath", "original_url", "url"]
53+
}
54+
55+
override Http::Server::RequestInputKind getKind() { result = Http::Server::urlInputKind() }
56+
}
57+
58+
/** A method call on `request` which returns a specific request header. */
59+
private class HeadersCall extends RequestInputAccess {
60+
HeadersCall() {
61+
this.getMethodName() =
62+
[
63+
"authorization", "script_name", "path_info", "user_agent", "referer", "referrer",
64+
"host_authority", "content_type", "host", "hostname", "accept_encoding",
65+
"accept_language", "if_none_match", "if_none_match_etags", "content_mime_type"
66+
]
67+
or
68+
// Request headers are prefixed with `HTTP_` to distinguish them from
69+
// "headers" supplied by Rack middleware.
70+
this.getMethodName() = ["get_header", "fetch_header"] and
71+
this.getArgument(0).getConstantValue().getString().regexpMatch("^HTTP_.+")
72+
}
73+
74+
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
75+
}
76+
77+
// TODO: each_header
78+
/**
79+
* A method call on `request` which returns part or all of the host.
80+
* This can be influenced by headers such as Host and X-Forwarded-Host.
81+
*/
82+
private class HostCall extends RequestInputAccess {
83+
HostCall() {
84+
this.getMethodName() =
85+
[
86+
"authority", "host", "host_authority", "host_with_port", "hostname", "forwarded_for",
87+
"forwarded_host", "port", "forwarded_port"
88+
]
89+
}
90+
91+
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
92+
}
93+
94+
/**
95+
* A method call on `request` which is influenced by one or more request
96+
* headers.
97+
*/
98+
private class HeaderTaintedCall extends RequestInputAccess {
99+
HeaderTaintedCall() {
100+
this.getMethodName() = ["media_type", "media_type_params", "content_charset", "base_url"]
101+
}
102+
103+
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
104+
}
105+
106+
/** A method call on `request` which returns the request body. */
107+
private class BodyCall extends RequestInputAccess {
108+
BodyCall() { this.getMethodName() = ["body", "raw_post", "body_stream"] }
109+
110+
override Http::Server::RequestInputKind getKind() { result = Http::Server::bodyInputKind() }
111+
}
112+
113+
private module Env {
114+
abstract private class Env extends DataFlow::LocalSourceNode { }
115+
116+
/**
117+
* A method call on `request` which returns the rack env.
118+
* This is a hash containing all the information about the request. Values
119+
* under keys starting with `HTTP_` are user-controlled.
120+
*/
121+
private class RequestEnvCall extends DataFlow::CallNode, Env {
122+
RequestEnvCall() { this.getMethodName() = ["env", "filtered_env"] }
123+
}
124+
125+
private import codeql.ruby.frameworks.Rack
126+
127+
private class RackEnv extends Env {
128+
RackEnv() { this = any(Rack::AppCandidate app).getEnv().getALocalUse() }
129+
}
130+
131+
/**
132+
* A read of a user-controlled parameter from the request env.
133+
*/
134+
private class EnvHttpAccess extends DataFlow::CallNode, Http::Server::RequestInputAccess::Range {
135+
EnvHttpAccess() {
136+
this = any(Env c).getAMethodCall("[]") and
137+
exists(string key | key = this.getArgument(0).getConstantValue().getString() |
138+
key.regexpMatch("^HTTP_.+") or key = "PATH_INFO"
139+
)
140+
}
141+
142+
override Http::Server::RequestInputKind getKind() { result = Http::Server::headerInputKind() }
143+
144+
override string getSourceType() { result = "ActionDispatch::Request#env[]" }
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)