Skip to content

Commit 98d371a

Browse files
author
Alvaro Muñoz
authored
Merge pull request #9 from GitHubSecurityLab/ruby_packs
Initial Ruby QLPacks
2 parents c832453 + e7e510b commit 98d371a

27 files changed

+528
-3
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
strategy:
1313
fail-fast: false
1414
matrix:
15-
language: [ 'csharp', 'go', 'java', 'python' ]
15+
language: [ 'csharp', 'go', 'java', 'python', 'ruby' ]
1616

1717
steps:
1818
- uses: actions/checkout@v3

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
language: ["csharp", "go", "java", "python"]
20+
language: ["csharp", "go", "java", "python", "ruby"]
2121

2222
steps:
2323
- uses: actions/checkout@v3
@@ -54,7 +54,7 @@ jobs:
5454
strategy:
5555
fail-fast: false
5656
matrix:
57-
language: ["csharp", "go", "java", "python"]
57+
language: ["csharp", "go", "java", "python", "ruby"]
5858

5959
steps:
6060
- uses: actions/checkout@v3

codeql-workspace.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ provide:
33
- go/**/qlpack.yml
44
- java/**/qlpack.yml
55
- python/**/qlpack.yml
6+
- ruby/**/qlpack.yml
67

ruby/.DS_Store

6 KB
Binary file not shown.

ruby/lib/applications/.gitkeep

Whitespace-only changes.

ruby/lib/codeql-pack.lock.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
lockVersion: 1.0.0
3+
dependencies:
4+
codeql/controlflow:
5+
version: 0.0.3
6+
codeql/dataflow:
7+
version: 0.0.3
8+
codeql/mad:
9+
version: 0.1.4
10+
codeql/regex:
11+
version: 0.1.4
12+
codeql/ruby-all:
13+
version: 0.7.4
14+
codeql/ssa:
15+
version: 0.1.4
16+
codeql/tutorial:
17+
version: 0.1.4
18+
codeql/util:
19+
version: 0.1.4
20+
compiled: false

ruby/lib/frameworks/Rack.qll

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Additional sources to model the Rack request object.
3+
* (Rack provides a minimal, modular, and adaptable interface for developing web applications in Ruby.)
4+
* Ruby web frameworks such as Sinatra make use of the Rack interface.
5+
* Please note: Ruby on Rails exposes an a similar request interface like the rack request interface via ActionDispatch.
6+
* -> This means this Rack sources also expand the number of Rails sources as a by-product.
7+
* (https://api.rubyonrails.org/classes/ActionDispatch/Request.html)
8+
*/
9+
10+
private import codeql.ruby.AST
11+
private import codeql.ruby.Concepts
12+
13+
module Rack {
14+
/**
15+
* A call to the `request` method to retrieve Rack request object.
16+
*/
17+
class RequestCall extends MethodCall {
18+
RequestCall() { this.getMethodName() = "request" }
19+
}
20+
21+
/**
22+
* A call to the `request.body` method to retrieve the body of the Rack request object.
23+
*/
24+
class RequestBodyCall extends MethodCall {
25+
RequestBodyCall() {
26+
exists(MethodCall request |
27+
request instanceof RequestCall and
28+
this.(MethodCall).getReceiver() = request and
29+
this.getMethodName() = "body"
30+
)
31+
}
32+
}
33+
34+
/**
35+
* `request.body.read` source
36+
*/
37+
class RequestBodyReadSource extends Http::Server::RequestInputAccess::Range {
38+
RequestBodyReadSource() {
39+
exists(MethodCall body, MethodCall read |
40+
body instanceof RequestBodyCall and
41+
read.(MethodCall).getReceiver() = body and
42+
read.getMethodName() = "read" and
43+
this.asExpr().getExpr() = read
44+
)
45+
}
46+
47+
override string getSourceType() { result = "request.body.read" }
48+
49+
override Http::Server::RequestInputKind getKind() { result = "body" }
50+
}
51+
52+
/**
53+
* `request.body.string` source
54+
*/
55+
class RequestBodyStringSource extends Http::Server::RequestInputAccess::Range {
56+
RequestBodyStringSource() {
57+
exists(MethodCall body, MethodCall stringcall |
58+
body instanceof RequestBodyCall and
59+
stringcall.(MethodCall).getReceiver() = body and
60+
stringcall.getMethodName() = "string" and
61+
this.asExpr().getExpr() = stringcall
62+
)
63+
}
64+
65+
override string getSourceType() { result = "request.body.string" }
66+
67+
override Http::Server::RequestInputKind getKind() { result = "body" }
68+
}
69+
70+
/**
71+
* `request.query_string` source
72+
*/
73+
class RequestQueryStringSource extends Http::Server::RequestInputAccess::Range {
74+
RequestQueryStringSource() {
75+
exists(MethodCall request, MethodCall queryString |
76+
request instanceof RequestCall and
77+
queryString.(MethodCall).getReceiver() = request and
78+
queryString.getMethodName() = "query_string" and
79+
this.asExpr().getExpr() = queryString
80+
)
81+
}
82+
83+
override string getSourceType() { result = "request.query_string" }
84+
85+
override Http::Server::RequestInputKind getKind() { result = "parameter" }
86+
}
87+
88+
/**
89+
* A call to the `request.params` method to retrieve the combined GET/POST params of the Rack request object.
90+
*/
91+
class RequestParamsCall extends MethodCall {
92+
RequestParamsCall() {
93+
exists(MethodCall request |
94+
request instanceof RequestCall and
95+
this.(MethodCall).getReceiver() = request and
96+
this.getMethodName() = "params"
97+
)
98+
}
99+
}
100+
101+
/**
102+
* `request.params[foo]` source
103+
*/
104+
class RequestParamsSource extends Http::Server::RequestInputAccess::Range {
105+
RequestParamsSource() {
106+
exists(MethodCall params, ElementReference elementRef |
107+
params instanceof RequestParamsCall and
108+
elementRef.(ElementReference).getReceiver() = params and
109+
this.asExpr().getExpr() = elementRef
110+
)
111+
}
112+
113+
override string getSourceType() { result = "request.params[foo]" }
114+
115+
override Http::Server::RequestInputKind getKind() { result = "parameter" }
116+
}
117+
118+
/**
119+
* `request[foo]` source
120+
*/
121+
class RequestParamsDirectSource extends Http::Server::RequestInputAccess::Range {
122+
RequestParamsDirectSource() {
123+
exists(MethodCall request, ElementReference elementRef |
124+
request instanceof RequestCall and
125+
elementRef.(ElementReference).getReceiver() = request and
126+
this.asExpr().getExpr() = elementRef
127+
)
128+
}
129+
130+
override string getSourceType() { result = "request[foo]" }
131+
132+
override Http::Server::RequestInputKind getKind() { result = "parameter" }
133+
}
134+
135+
/**
136+
* A call to the `request.GET` method to retrieve the query params of the Rack request object.
137+
*/
138+
class RequestGETCall extends MethodCall {
139+
RequestGETCall() {
140+
exists(MethodCall request |
141+
request instanceof RequestCall and
142+
this.(MethodCall).getReceiver() = request and
143+
this.getMethodName() = "GET"
144+
)
145+
}
146+
}
147+
148+
/**
149+
* `request.GET[foo]` source
150+
*/
151+
class RequestGETSource extends Http::Server::RequestInputAccess::Range {
152+
RequestGETSource() {
153+
exists(MethodCall get, ElementReference elementRef |
154+
get instanceof RequestGETCall and
155+
elementRef.(ElementReference).getReceiver() = get and
156+
this.asExpr().getExpr() = elementRef
157+
)
158+
}
159+
160+
override string getSourceType() { result = "request.GET[foo]" }
161+
162+
override Http::Server::RequestInputKind getKind() { result = "parameter" }
163+
}
164+
165+
/**
166+
* A call to the `request.POST` method to retrieve the POST params of the Rack request object.
167+
*/
168+
class RequestPOSTCall extends MethodCall {
169+
RequestPOSTCall() {
170+
exists(MethodCall request |
171+
request instanceof RequestCall and
172+
this.(MethodCall).getReceiver() = request and
173+
this.getMethodName() = "POST"
174+
)
175+
}
176+
}
177+
178+
/**
179+
* `request.POST[foo]` source
180+
*/
181+
class RequestPOSTSource extends Http::Server::RequestInputAccess::Range {
182+
RequestPOSTSource() {
183+
exists(MethodCall post, ElementReference elementRef |
184+
post instanceof RequestPOSTCall and
185+
elementRef.(ElementReference).getReceiver() = post and
186+
this.asExpr().getExpr() = elementRef
187+
)
188+
}
189+
190+
override string getSourceType() { result = "request.POST[foo]" }
191+
192+
override Http::Server::RequestInputKind getKind() { result = "parameter" }
193+
}
194+
}
195+
// TODO: complete sources e.g. headers (incl cookies, user-agent) (https://www.rubydoc.info/gems/rack/Rack/Request)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Additional sinks that are not yet covered by CodeQL's rails sinks.
3+
* Ruby on Rails adds methods to the Object class.
4+
* In the case of the `try/try!` methods this looks like this (see try.rb):
5+
*
6+
* class Object
7+
* include ActiveSupport::Tryable
8+
*
9+
* ref: https://api.rubyonrails.org/classes/Object.html
10+
*/
11+
12+
private import codeql.ruby.AST
13+
private import codeql.ruby.Concepts
14+
private import codeql.ruby.DataFlow
15+
16+
module RailsAddonSinks {
17+
18+
class RailsTryCodeExecution extends CodeExecution::Range, DataFlow::CallNode {
19+
RailsTryCodeExecution() { this.getMethodName() = ["try", "try!"] }
20+
21+
override DataFlow::Node getCode() { result = this.getArgument(0) }
22+
}
23+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Additional sources that are not yet covered by CodeQL's rails sources (and not included by the Rack sources).
3+
* https://api.rubyonrails.org/classes/ActionDispatch/Request.html
4+
*/
5+
6+
private import codeql.ruby.AST
7+
private import codeql.ruby.ApiGraphs
8+
private import codeql.ruby.Concepts
9+
private import codeql.ruby.DataFlow
10+
private import codeql.ruby.frameworks.ActionController
11+
12+
module RailsAddonSources {
13+
14+
/**
15+
* A call to `request` in a Rails ActionController controller class.
16+
* The class ActionControllerRequest was copied from the experimental WeakParams query.
17+
*/
18+
class ActionControllerRequest extends DataFlow::Node {
19+
ActionControllerRequest() {
20+
exists(DataFlow::CallNode c |
21+
c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and
22+
c.getMethodName() = "request"
23+
|
24+
c.flowsTo(this)
25+
)
26+
}
27+
}
28+
29+
//TODO: this needs expansion (e.g., include request.body separately for Rails)
30+
class RawPostMethodCall extends DataFlow::CallNode {
31+
RawPostMethodCall() {
32+
this.getReceiver() instanceof ActionControllerRequest and
33+
this.getMethodName() = "raw_post"
34+
}
35+
}
36+
37+
class RawPostMethodCallSource extends Http::Server::RequestInputAccess::Range {
38+
RawPostMethodCallSource() {
39+
exists(DataFlow::CallNode rawPost |
40+
rawPost instanceof RawPostMethodCall and
41+
this = rawPost
42+
)
43+
}
44+
45+
override string getSourceType() { result = "request.raw_post (Rails)" }
46+
override Http::Server::RequestInputKind getKind() { result = "body" }
47+
}
48+
}

ruby/lib/github/.gitkeep

Whitespace-only changes.

ruby/lib/qlpack.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
library: true
2+
name: githubsecuritylab/codeql-ruby-libs
3+
version: 0.0.1
4+
dependencies:
5+
codeql/ruby-all: '*'

ruby/src/CVEs/.gitkeep

Whitespace-only changes.

ruby/src/audit/explore/.DS_Store

6 KB
Binary file not shown.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* @name External dependencies
3+
* @description Count the number of dependencies that a Java project has on external packages.
4+
* @kind treemap
5+
* @id githubsecuritylab/external-dependencies
6+
* @metricType externalDependency
7+
* @tags audit
8+
*/
9+
10+
import codeql.ruby.AST
11+
12+
from MethodCall c
13+
where
14+
c.getLocation().getFile().getBaseName() = "Gemfile" and
15+
c.getMethodName() = "gem"
16+
select c.getArgument(0), 1

ruby/src/audit/explore/Files.ql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @name Files
3+
* @description List of all files in the repository
4+
* @kind table
5+
* @id githubsecuritylab/files
6+
* @tags audit
7+
*/
8+
9+
import ruby
10+
11+
from File f
12+
where f.getExtension() = "rb" and not f.getRelativePath().matches("%/test/%")
13+
select f.getRelativePath()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @name Attack Surface
3+
* @description Application attack surface
4+
* @kind table
5+
* @id githubsecuritylab/attack-surface
6+
* @tags audit
7+
*/
8+
9+
import ruby
10+
import codeql.ruby.dataflow.RemoteFlowSources
11+
12+
from RemoteFlowSource source, Location l
13+
where
14+
not source.getLocation().getFile().getRelativePath().matches("%/test/%") and
15+
l = source.getLocation()
16+
select source, source.getSourceType(), l.getFile().getRelativePath(), l.getStartLine(),
17+
l.getEndLine(), l.getStartColumn(), l.getEndColumn()

0 commit comments

Comments
 (0)