Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
category: feature
---
* Added modeling for `Grape::API`'s `request` sub-accessors (`body.read`, `body.string`, `params`, `GET`, `POST`, `cookies`, `env`, `query_string`, and `path_info`) as remote flow sources.
147 changes: 147 additions & 0 deletions ruby/ql/lib/codeql/ruby/frameworks/Grape.qll
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,153 @@ module Grape {
}
}

/**
* Gets an API node for the `request` object accessible within a Grape API endpoint.
*/
private API::Node grapeRequestInstance() { result = grapeApiInstance().getReturn("request") }

/**
* A call to `request.body.read` from within a Grape API endpoint, which reads
* the raw, unparsed request body and can contain user input.
*/
class GrapeRequestBodyReadSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestBodyReadSource() {
this.asExpr().getExpr() =
grapeRequestInstance().getReturn("body").getAMethodCall("read").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#body.read" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
Comment on lines +226 to +228

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like Copilot is right here. I would also expect bodyInputKind on line 291 and cookieInputKind on line 305.

}

/**
* A call to `request.body.string` from within a Grape API endpoint, which reads
* the raw, unparsed request body and can contain user input.
*/
class GrapeRequestBodyStringSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestBodyStringSource() {
this.asExpr().getExpr() =
grapeRequestInstance().getReturn("body").getAMethodCall("string").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#body.string" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.params` from within a Grape API endpoint.
*/
class GrapeRequestParamsSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestParamsSource() {
this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("params").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#params" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.GET` from within a Grape API endpoint, which returns the
* parsed query-string parameters.
*/
class GrapeRequestGetSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestGetSource() {
this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("GET").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#GET" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.POST` from within a Grape API endpoint, which returns the
* parsed form/body parameters.
*/
class GrapeRequestPostSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestPostSource() {
this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("POST").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#POST" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.cookies` from within a Grape API endpoint.
*/
class GrapeRequestCookiesSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestCookiesSource() {
this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("cookies").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#cookies" }

override Http::Server::RequestInputKind getKind() { result = Http::Server::cookieInputKind() }
}

/**
* A call to `request.env` from within a Grape API endpoint, which returns the
* underlying Rack environment hash.
*/
class GrapeRequestEnvSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestEnvSource() {
this.asExpr().getExpr() = grapeRequestInstance().getAMethodCall("env").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#env" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.query_string` from within a Grape API endpoint.
*/
class GrapeRequestQueryStringSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestQueryStringSource() {
this.asExpr().getExpr() =
grapeRequestInstance().getAMethodCall("query_string").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#query_string" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `request.path_info` from within a Grape API endpoint.
*/
class GrapeRequestPathInfoSource extends Http::Server::RequestInputAccess::Range {
GrapeRequestPathInfoSource() {
this.asExpr().getExpr() =
grapeRequestInstance().getAMethodCall("path_info").asExpr().getExpr()
}

override string getSourceType() { result = "Grape::Request#path_info" }

override Http::Server::RequestInputKind getKind() {
result = Http::Server::parameterInputKind()
}
}

/**
* A call to `route_param` from within a Grape API endpoint.
*/
Expand Down
60 changes: 60 additions & 0 deletions ruby/ql/test/library-tests/frameworks/grape/Flow.expected
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ edges
| app.rb:169:9:169:15 | session | app.rb:176:14:176:20 | session | provenance | |
| app.rb:169:19:169:25 | call to cookies | app.rb:169:19:169:38 | ...[...] | provenance | |
| app.rb:169:19:169:38 | ...[...] | app.rb:169:9:169:15 | session | provenance | |
| app.rb:170:9:170:17 | body_data | app.rb:177:14:177:22 | body_data | provenance | |
| app.rb:170:21:170:37 | call to read | app.rb:170:9:170:17 | body_data | provenance | |
| app.rb:183:9:183:14 | result | app.rb:184:14:184:19 | result | provenance | |
| app.rb:183:9:183:14 | result | app.rb:184:14:184:19 | result | provenance | |
| app.rb:183:18:183:43 | call to vulnerable_helper | app.rb:183:9:183:14 | result | provenance | |
Expand Down Expand Up @@ -76,6 +78,24 @@ edges
| app.rb:235:13:235:23 | case_result | app.rb:236:18:236:28 | case_result | provenance | |
| app.rb:235:27:235:37 | call to test_helper | app.rb:235:13:235:23 | case_result | provenance | |
| app.rb:235:27:235:37 | call to test_helper | app.rb:235:13:235:23 | case_result | provenance | |
| app.rb:247:5:247:13 | body_data | app.rb:248:10:248:18 | body_data | provenance | |
| app.rb:247:17:247:33 | call to read | app.rb:247:5:247:13 | body_data | provenance | |
| app.rb:252:5:252:13 | body_data | app.rb:253:10:253:18 | body_data | provenance | |
| app.rb:252:17:252:35 | call to string | app.rb:252:5:252:13 | body_data | provenance | |
| app.rb:257:5:257:15 | params_hash | app.rb:258:10:258:20 | params_hash | provenance | |
| app.rb:257:19:257:32 | call to params | app.rb:257:5:257:15 | params_hash | provenance | |
| app.rb:262:5:262:14 | get_params | app.rb:263:10:263:19 | get_params | provenance | |
| app.rb:262:18:262:28 | call to GET | app.rb:262:5:262:14 | get_params | provenance | |
| app.rb:267:5:267:15 | post_params | app.rb:268:10:268:20 | post_params | provenance | |
| app.rb:267:19:267:30 | call to POST | app.rb:267:5:267:15 | post_params | provenance | |
| app.rb:272:5:272:15 | cookie_data | app.rb:273:10:273:20 | cookie_data | provenance | |
| app.rb:272:19:272:33 | call to cookies | app.rb:272:5:272:15 | cookie_data | provenance | |
| app.rb:277:5:277:12 | env_data | app.rb:278:10:278:17 | env_data | provenance | |
| app.rb:277:16:277:26 | call to env | app.rb:277:5:277:12 | env_data | provenance | |
| app.rb:282:5:282:6 | qs | app.rb:283:10:283:11 | qs | provenance | |
| app.rb:282:10:282:29 | call to query_string | app.rb:282:5:282:6 | qs | provenance | |
| app.rb:287:5:287:8 | path | app.rb:288:10:288:13 | path | provenance | |
| app.rb:287:12:287:28 | call to path_info | app.rb:287:5:287:8 | path | provenance | |
nodes
| app.rb:103:13:103:18 | call to params | semmle.label | call to params |
| app.rb:103:13:103:70 | call to select | semmle.label | call to select |
Expand Down Expand Up @@ -107,10 +127,13 @@ nodes
| app.rb:169:9:169:15 | session | semmle.label | session |
| app.rb:169:19:169:25 | call to cookies | semmle.label | call to cookies |
| app.rb:169:19:169:38 | ...[...] | semmle.label | ...[...] |
| app.rb:170:9:170:17 | body_data | semmle.label | body_data |
| app.rb:170:21:170:37 | call to read | semmle.label | call to read |
| app.rb:173:14:173:20 | user_id | semmle.label | user_id |
| app.rb:174:14:174:21 | route_id | semmle.label | route_id |
| app.rb:175:14:175:17 | auth | semmle.label | auth |
| app.rb:176:14:176:20 | session | semmle.label | session |
| app.rb:177:14:177:22 | body_data | semmle.label | body_data |
| app.rb:183:9:183:14 | result | semmle.label | result |
| app.rb:183:9:183:14 | result | semmle.label | result |
| app.rb:183:18:183:43 | call to vulnerable_helper | semmle.label | call to vulnerable_helper |
Expand Down Expand Up @@ -173,13 +196,41 @@ nodes
| app.rb:235:27:235:37 | call to test_helper | semmle.label | call to test_helper |
| app.rb:236:18:236:28 | case_result | semmle.label | case_result |
| app.rb:236:18:236:28 | case_result | semmle.label | case_result |
| app.rb:247:5:247:13 | body_data | semmle.label | body_data |
| app.rb:247:17:247:33 | call to read | semmle.label | call to read |
| app.rb:248:10:248:18 | body_data | semmle.label | body_data |
| app.rb:252:5:252:13 | body_data | semmle.label | body_data |
| app.rb:252:17:252:35 | call to string | semmle.label | call to string |
| app.rb:253:10:253:18 | body_data | semmle.label | body_data |
| app.rb:257:5:257:15 | params_hash | semmle.label | params_hash |
| app.rb:257:19:257:32 | call to params | semmle.label | call to params |
| app.rb:258:10:258:20 | params_hash | semmle.label | params_hash |
| app.rb:262:5:262:14 | get_params | semmle.label | get_params |
| app.rb:262:18:262:28 | call to GET | semmle.label | call to GET |
| app.rb:263:10:263:19 | get_params | semmle.label | get_params |
| app.rb:267:5:267:15 | post_params | semmle.label | post_params |
| app.rb:267:19:267:30 | call to POST | semmle.label | call to POST |
| app.rb:268:10:268:20 | post_params | semmle.label | post_params |
| app.rb:272:5:272:15 | cookie_data | semmle.label | cookie_data |
| app.rb:272:19:272:33 | call to cookies | semmle.label | call to cookies |
| app.rb:273:10:273:20 | cookie_data | semmle.label | cookie_data |
| app.rb:277:5:277:12 | env_data | semmle.label | env_data |
| app.rb:277:16:277:26 | call to env | semmle.label | call to env |
| app.rb:278:10:278:17 | env_data | semmle.label | env_data |
| app.rb:282:5:282:6 | qs | semmle.label | qs |
| app.rb:282:10:282:29 | call to query_string | semmle.label | call to query_string |
| app.rb:283:10:283:11 | qs | semmle.label | qs |
| app.rb:287:5:287:8 | path | semmle.label | path |
| app.rb:287:12:287:28 | call to path_info | semmle.label | call to path_info |
| app.rb:288:10:288:13 | path | semmle.label | path |
subpaths
testFailures
#select
| app.rb:173:14:173:20 | user_id | app.rb:166:19:166:24 | call to params | app.rb:173:14:173:20 | user_id | $@ | app.rb:166:19:166:24 | call to params | call to params |
| app.rb:174:14:174:21 | route_id | app.rb:167:20:167:40 | call to route_param | app.rb:174:14:174:21 | route_id | $@ | app.rb:167:20:167:40 | call to route_param | call to route_param |
| app.rb:175:14:175:17 | auth | app.rb:168:16:168:22 | call to headers | app.rb:175:14:175:17 | auth | $@ | app.rb:168:16:168:22 | call to headers | call to headers |
| app.rb:176:14:176:20 | session | app.rb:169:19:169:25 | call to cookies | app.rb:176:14:176:20 | session | $@ | app.rb:169:19:169:25 | call to cookies | call to cookies |
| app.rb:177:14:177:22 | body_data | app.rb:170:21:170:37 | call to read | app.rb:177:14:177:22 | body_data | $@ | app.rb:170:21:170:37 | call to read | call to read |
| app.rb:184:14:184:19 | result | app.rb:107:13:107:32 | call to source | app.rb:184:14:184:19 | result | $@ | app.rb:107:13:107:32 | call to source | call to source |
| app.rb:184:14:184:19 | result | app.rb:107:13:107:32 | call to source | app.rb:184:14:184:19 | result | $@ | app.rb:107:13:107:32 | call to source | call to source |
| app.rb:191:14:191:22 | user_data | app.rb:103:13:103:18 | call to params | app.rb:191:14:191:22 | user_data | $@ | app.rb:103:13:103:18 | call to params | call to params |
Expand All @@ -199,3 +250,12 @@ testFailures
| app.rb:231:14:231:26 | rescue_result | app.rb:140:17:140:37 | call to source | app.rb:231:14:231:26 | rescue_result | $@ | app.rb:140:17:140:37 | call to source | call to source |
| app.rb:236:18:236:28 | case_result | app.rb:150:17:150:35 | call to source | app.rb:236:18:236:28 | case_result | $@ | app.rb:150:17:150:35 | call to source | call to source |
| app.rb:236:18:236:28 | case_result | app.rb:150:17:150:35 | call to source | app.rb:236:18:236:28 | case_result | $@ | app.rb:150:17:150:35 | call to source | call to source |
| app.rb:248:10:248:18 | body_data | app.rb:247:17:247:33 | call to read | app.rb:248:10:248:18 | body_data | $@ | app.rb:247:17:247:33 | call to read | call to read |
| app.rb:253:10:253:18 | body_data | app.rb:252:17:252:35 | call to string | app.rb:253:10:253:18 | body_data | $@ | app.rb:252:17:252:35 | call to string | call to string |
| app.rb:258:10:258:20 | params_hash | app.rb:257:19:257:32 | call to params | app.rb:258:10:258:20 | params_hash | $@ | app.rb:257:19:257:32 | call to params | call to params |
| app.rb:263:10:263:19 | get_params | app.rb:262:18:262:28 | call to GET | app.rb:263:10:263:19 | get_params | $@ | app.rb:262:18:262:28 | call to GET | call to GET |
| app.rb:268:10:268:20 | post_params | app.rb:267:19:267:30 | call to POST | app.rb:268:10:268:20 | post_params | $@ | app.rb:267:19:267:30 | call to POST | call to POST |
| app.rb:273:10:273:20 | cookie_data | app.rb:272:19:272:33 | call to cookies | app.rb:273:10:273:20 | cookie_data | $@ | app.rb:272:19:272:33 | call to cookies | call to cookies |
| app.rb:278:10:278:17 | env_data | app.rb:277:16:277:26 | call to env | app.rb:278:10:278:17 | env_data | $@ | app.rb:277:16:277:26 | call to env | call to env |
| app.rb:283:10:283:11 | qs | app.rb:282:10:282:29 | call to query_string | app.rb:283:10:283:11 | qs | $@ | app.rb:282:10:282:29 | call to query_string | call to query_string |
| app.rb:288:10:288:13 | path | app.rb:287:12:287:28 | call to path_info | app.rb:288:10:288:13 | path | $@ | app.rb:287:12:287:28 | call to path_info | call to path_info |
39 changes: 39 additions & 0 deletions ruby/ql/test/library-tests/frameworks/grape/Grape.expected
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ grapeApiClasses
| app.rb:1:1:90:3 | MyAPI |
| app.rb:92:1:96:3 | AdminAPI |
| app.rb:98:1:239:3 | UserAPI |
| app.rb:243:1:290:3 | RequestAccessorsAPI |
grapeEndpoints
| app.rb:1:1:90:3 | MyAPI | app.rb:7:3:11:5 | call to get | GET | /hello/:name |
| app.rb:1:1:90:3 | MyAPI | app.rb:17:3:20:5 | call to post | POST | /messages |
Expand All @@ -19,6 +20,15 @@ grapeEndpoints
| app.rb:98:1:239:3 | UserAPI | app.rb:187:5:193:7 | call to post | POST | /users |
| app.rb:98:1:239:3 | UserAPI | app.rb:204:5:207:7 | call to post | POST | /users |
| app.rb:98:1:239:3 | UserAPI | app.rb:210:5:238:7 | call to get | GET | /nested_test/:token |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:246:3:249:5 | call to get | GET | /body_read_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:251:3:254:5 | call to get | GET | /body_string_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:256:3:259:5 | call to get | GET | /request_params_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:261:3:264:5 | call to get | GET | /request_get_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:266:3:269:5 | call to get | GET | /request_post_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:271:3:274:5 | call to get | GET | /request_cookies_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:276:3:279:5 | call to get | GET | /request_env_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:281:3:284:5 | call to get | GET | /request_query_string_test |
| app.rb:243:1:290:3 | RequestAccessorsAPI | app.rb:286:3:289:5 | call to get | GET | /request_path_info_test |
grapeParams
| app.rb:8:12:8:17 | call to params |
| app.rb:14:3:16:5 | call to params |
Expand All @@ -44,6 +54,15 @@ grapeHeaders
grapeRequest
| app.rb:25:12:25:18 | call to request |
| app.rb:170:21:170:27 | call to request |
| app.rb:247:17:247:23 | call to request |
| app.rb:252:17:252:23 | call to request |
| app.rb:257:19:257:25 | call to request |
| app.rb:262:18:262:24 | call to request |
| app.rb:267:19:267:25 | call to request |
| app.rb:272:19:272:25 | call to request |
| app.rb:277:16:277:22 | call to request |
| app.rb:282:10:282:16 | call to request |
| app.rb:287:12:287:18 | call to request |
grapeRouteParam
| app.rb:51:15:51:35 | call to route_param |
| app.rb:52:15:52:36 | call to route_param |
Expand All @@ -56,3 +75,23 @@ grapeCookies
| app.rb:80:16:80:22 | call to cookies |
| app.rb:160:5:162:7 | call to cookies |
| app.rb:169:19:169:25 | call to cookies |
grapeRequestBodyRead
| app.rb:25:12:25:28 | call to read |
| app.rb:170:21:170:37 | call to read |
| app.rb:247:17:247:33 | call to read |
grapeRequestBodyString
| app.rb:252:17:252:35 | call to string |
grapeRequestParams
| app.rb:257:19:257:32 | call to params |
grapeRequestGet
| app.rb:262:18:262:28 | call to GET |
grapeRequestPost
| app.rb:267:19:267:30 | call to POST |
grapeRequestCookies
| app.rb:272:19:272:33 | call to cookies |
grapeRequestEnv
| app.rb:277:16:277:26 | call to env |
grapeRequestQueryString
| app.rb:282:10:282:29 | call to query_string |
grapeRequestPathInfo
| app.rb:287:12:287:28 | call to path_info |
18 changes: 18 additions & 0 deletions ruby/ql/test/library-tests/frameworks/grape/Grape.ql
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,21 @@ query predicate grapeRequest(Grape::GrapeRequestSource request) { any() }
query predicate grapeRouteParam(Grape::GrapeRouteParamSource routeParam) { any() }

query predicate grapeCookies(Grape::GrapeCookiesSource cookies) { any() }

query predicate grapeRequestBodyRead(Grape::GrapeRequestBodyReadSource src) { any() }

query predicate grapeRequestBodyString(Grape::GrapeRequestBodyStringSource src) { any() }

query predicate grapeRequestParams(Grape::GrapeRequestParamsSource src) { any() }

query predicate grapeRequestGet(Grape::GrapeRequestGetSource src) { any() }

query predicate grapeRequestPost(Grape::GrapeRequestPostSource src) { any() }

query predicate grapeRequestCookies(Grape::GrapeRequestCookiesSource src) { any() }

query predicate grapeRequestEnv(Grape::GrapeRequestEnvSource src) { any() }

query predicate grapeRequestQueryString(Grape::GrapeRequestQueryStringSource src) { any() }

query predicate grapeRequestPathInfo(Grape::GrapeRequestPathInfoSource src) { any() }
Loading
Loading