Skip to content

Commit fe995dd

Browse files
committed
Ruby: ActiveRecord::Connection.execute SQL sink
1 parent 025cd34 commit fe995dd

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName)
3131
methodName = objectInstanceMethodName()
3232
}
3333

34+
private API::Node activeRecordClassApiNode() {
35+
result =
36+
// class Foo < ActiveRecord::Base
37+
// class Bar < Foo
38+
[
39+
API::getTopLevelMember("ActiveRecord").getMember("Base"),
40+
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
41+
// treat it separately in case the `ApplicationRecord` definition is not in the database.
42+
API::getTopLevelMember("ApplicationRecord")
43+
].getASubclass()
44+
}
45+
3446
/**
3547
* A `ClassDeclaration` for a class that inherits from `ActiveRecord::Base`. For example,
3648
*
@@ -45,15 +57,8 @@ private predicate isBuiltInMethodForActiveRecordModelInstance(string methodName)
4557
*/
4658
class ActiveRecordModelClass extends ClassDeclaration {
4759
ActiveRecordModelClass() {
48-
// class Foo < ActiveRecord::Base
49-
// class Bar < Foo
5060
this.getSuperclassExpr() =
51-
[
52-
API::getTopLevelMember("ActiveRecord").getMember("Base"),
53-
// In Rails applications `ApplicationRecord` typically extends `ActiveRecord::Base`, but we
54-
// treat it separately in case the `ApplicationRecord` definition is not in the database.
55-
API::getTopLevelMember("ApplicationRecord")
56-
].getASubclass().getAValueReachableFromSource().asExpr().getExpr()
61+
activeRecordClassApiNode().getAValueReachableFromSource().asExpr().getExpr()
5762
}
5863

5964
// Gets the class declaration for this class and all of its super classes
@@ -208,11 +213,22 @@ class ActiveRecordSqlExecutionRange extends SqlExecution::Range {
208213
exists(PotentiallyUnsafeSqlExecutingMethodCall mc |
209214
this.asExpr().getNode() = mc.getSqlFragmentSinkArgument()
210215
)
216+
or
217+
exists(DataFlow::CallNode executeCall |
218+
executeCall.getReceiver() = activeRecordConnectionInstance() and
219+
executeCall.getMethodName() = "execute" and
220+
this = executeCall.getArgument(0) and
221+
unsafeSqlExpr(this.asExpr().getExpr())
222+
)
211223
}
212224

213225
override DataFlow::Node getSql() { result = this }
214226
}
215227

228+
private DataFlow::Node activeRecordConnectionInstance() {
229+
result = activeRecordClassApiNode().getAMethodCall("connection")
230+
}
231+
216232
// TODO: model `ActiveRecord` sanitizers
217233
// https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html
218234
/**

ruby/ql/test/query-tests/security/cwe-089/ActiveRecordInjection.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,4 +172,9 @@ def index
172172
def permitted_params
173173
params.require(:my_key).permit(:id, :user_id, :my_type)
174174
end
175+
176+
def show
177+
ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}")
178+
Regression.connection.execute("SELECT * FROM users WHERE id = #{permitted_params[:user_id]}")
179+
end
175180
end

ruby/ql/test/query-tests/security/cwe-089/SqlInjection.expected

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ edges
4141
| ActiveRecordInjection.rb:173:5:173:10 | call to params : | ActiveRecordInjection.rb:173:5:173:27 | call to require : |
4242
| ActiveRecordInjection.rb:173:5:173:27 | call to require : | ActiveRecordInjection.rb:173:5:173:59 | call to permit : |
4343
| ActiveRecordInjection.rb:173:5:173:59 | call to permit : | ActiveRecordInjection.rb:166:17:166:32 | call to permitted_params : |
44+
| ActiveRecordInjection.rb:173:5:173:59 | call to permit : | ActiveRecordInjection.rb:177:77:177:92 | call to permitted_params : |
45+
| ActiveRecordInjection.rb:173:5:173:59 | call to permit : | ActiveRecordInjection.rb:178:69:178:84 | call to permitted_params : |
46+
| ActiveRecordInjection.rb:177:77:177:92 | call to permitted_params : | ActiveRecordInjection.rb:177:77:177:102 | ...[...] : |
47+
| ActiveRecordInjection.rb:177:77:177:102 | ...[...] : | ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." |
48+
| ActiveRecordInjection.rb:178:69:178:84 | call to permitted_params : | ActiveRecordInjection.rb:178:69:178:94 | ...[...] : |
49+
| ActiveRecordInjection.rb:178:69:178:94 | ...[...] : | ActiveRecordInjection.rb:178:35:178:96 | "SELECT * FROM users WHERE id ..." |
4450
| ArelInjection.rb:4:12:4:17 | call to params : | ArelInjection.rb:4:12:4:29 | ...[...] : |
4551
| ArelInjection.rb:4:12:4:29 | ...[...] : | ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." |
4652
nodes
@@ -106,6 +112,12 @@ nodes
106112
| ActiveRecordInjection.rb:173:5:173:10 | call to params : | semmle.label | call to params : |
107113
| ActiveRecordInjection.rb:173:5:173:27 | call to require : | semmle.label | call to require : |
108114
| ActiveRecordInjection.rb:173:5:173:59 | call to permit : | semmle.label | call to permit : |
115+
| ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." | semmle.label | "SELECT * FROM users WHERE id ..." |
116+
| ActiveRecordInjection.rb:177:77:177:92 | call to permitted_params : | semmle.label | call to permitted_params : |
117+
| ActiveRecordInjection.rb:177:77:177:102 | ...[...] : | semmle.label | ...[...] : |
118+
| ActiveRecordInjection.rb:178:35:178:96 | "SELECT * FROM users WHERE id ..." | semmle.label | "SELECT * FROM users WHERE id ..." |
119+
| ActiveRecordInjection.rb:178:69:178:84 | call to permitted_params : | semmle.label | call to permitted_params : |
120+
| ActiveRecordInjection.rb:178:69:178:94 | ...[...] : | semmle.label | ...[...] : |
109121
| ArelInjection.rb:4:12:4:17 | call to params : | semmle.label | call to params : |
110122
| ArelInjection.rb:4:12:4:29 | ...[...] : | semmle.label | ...[...] : |
111123
| ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | semmle.label | "SELECT * FROM users WHERE nam..." |
@@ -132,4 +144,6 @@ subpaths
132144
| ActiveRecordInjection.rb:108:20:108:32 | ... + ... | ActiveRecordInjection.rb:102:10:102:15 | call to params : | ActiveRecordInjection.rb:108:20:108:32 | ... + ... | This SQL query depends on a $@. | ActiveRecordInjection.rb:102:10:102:15 | call to params | user-provided value |
133145
| ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | ActiveRecordInjection.rb:155:59:155:64 | call to params : | ActiveRecordInjection.rb:155:27:155:76 | "this is an unsafe annotation:..." | This SQL query depends on a $@. | ActiveRecordInjection.rb:155:59:155:64 | call to params | user-provided value |
134146
| ActiveRecordInjection.rb:168:37:168:41 | query | ActiveRecordInjection.rb:173:5:173:10 | call to params : | ActiveRecordInjection.rb:168:37:168:41 | query | This SQL query depends on a $@. | ActiveRecordInjection.rb:173:5:173:10 | call to params | user-provided value |
147+
| ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." | ActiveRecordInjection.rb:173:5:173:10 | call to params : | ActiveRecordInjection.rb:177:43:177:104 | "SELECT * FROM users WHERE id ..." | This SQL query depends on a $@. | ActiveRecordInjection.rb:173:5:173:10 | call to params | user-provided value |
148+
| ActiveRecordInjection.rb:178:35:178:96 | "SELECT * FROM users WHERE id ..." | ActiveRecordInjection.rb:173:5:173:10 | call to params : | ActiveRecordInjection.rb:178:35:178:96 | "SELECT * FROM users WHERE id ..." | This SQL query depends on a $@. | ActiveRecordInjection.rb:173:5:173:10 | call to params | user-provided value |
135149
| ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | ArelInjection.rb:4:12:4:17 | call to params : | ArelInjection.rb:6:20:6:61 | "SELECT * FROM users WHERE nam..." | This SQL query depends on a $@. | ArelInjection.rb:4:12:4:17 | call to params | user-provided value |

0 commit comments

Comments
 (0)