Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bf43a77

Browse files
committed
Include some more types of expressions as possible active record SQL sink arguments
1 parent ea21c59 commit bf43a77

3 files changed

Lines changed: 42 additions & 37 deletions

File tree

ql/src/codeql_ruby/frameworks/ActiveRecord.qll

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ class ActiveRecordModelClassMethodCall extends MethodCall {
5353
}
5454
}
5555

56+
private predicate methodCanTakeSqlFragmentAsFirstArg(string methodName) {
57+
methodName =
58+
[
59+
"delete_all", "destroy_all", "exists?", "find_by", "find_by_sql", "from", "group", "having",
60+
"joins", "lock", "not", "order", "pluck", "where"
61+
]
62+
}
63+
5664
class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMethodCall {
5765
// The name of the method invoked
5866
private string methodName;
@@ -61,10 +69,6 @@ class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMeth
6169
// The SQL fragment argument itself
6270
private Expr sqlFragmentExpr;
6371

64-
// TODO: This is slightly too restricted, we only look for StringlikeLiterals
65-
// as arguments, but we could instead have a variable read, a string
66-
// concatenation, certain arrays, etc. and still have a potentially
67-
// vulnerable call
6872
// TODO: `find` with `lock:` option also takes an SQL fragment
6973
PotentiallyUnsafeSqlExecutingMethodCall() {
7074
methodName = this.getMethodName() and
@@ -73,37 +77,20 @@ class PotentiallyUnsafeSqlExecutingMethodCall extends ActiveRecordModelClassMeth
7377
methodName = "calculate" and sqlFragmentArgumentIndex = 1
7478
or
7579
sqlFragmentArgumentIndex = 0 and
76-
(
77-
methodName = "delete_all"
78-
or
79-
methodName = "destroy_all"
80-
or
81-
methodName = "exists?"
82-
or
83-
methodName = "find_by"
84-
or
85-
methodName = "find_by_sql"
86-
or
87-
methodName = "from"
88-
or
89-
methodName = "group"
90-
or
91-
methodName = "having"
92-
or
93-
methodName = "joins"
94-
or
95-
methodName = "lock"
96-
or
97-
methodName = "not"
98-
or
99-
methodName = "order"
100-
or
101-
methodName = "pluck"
102-
or
103-
methodName = "where"
104-
)
80+
methodCanTakeSqlFragmentAsFirstArg(methodName)
10581
) and
106-
sqlFragmentExpr instanceof StringlikeLiteral
82+
(
83+
// select only literals containing an interpolated value...
84+
exists(StringInterpolationComponent interpolated |
85+
interpolated = sqlFragmentExpr.(StringlikeLiteral).getComponent(_)
86+
)
87+
or
88+
// ...or string concatenations...
89+
sqlFragmentExpr instanceof AddExpr
90+
or
91+
// ...or variable reads
92+
sqlFragmentExpr instanceof VariableReadAccess
93+
)
10794
}
10895

10996
Expr getSqlFragmentSinkArgument() { result = sqlFragmentExpr }

ql/test/library-tests/frameworks/ActiveRecord.expected

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@ activeRecordModelClasses
55
activeRecordSqlExecutionRanges
66
| ActiveRecordInjection.rb:22:21:22:41 | "id = #{...}" |
77
| ActiveRecordInjection.rb:28:16:28:21 | <<-SQL |
8-
| ActiveRecordInjection.rb:32:21:32:26 | :users |
98
| ActiveRecordInjection.rb:32:35:32:60 | "user.id = #{...}" |
9+
| ActiveRecordInjection.rb:45:21:45:33 | ... + ... |
1010
activeRecordModelClassMethodCalls
1111
| ActiveRecordInjection.rb:19:5:19:45 | call to calculate |
1212
| ActiveRecordInjection.rb:22:5:22:42 | call to delete_all |
1313
| ActiveRecordInjection.rb:25:5:25:45 | call to destroy_all |
1414
| ActiveRecordInjection.rb:28:5:28:35 | call to where |
1515
| ActiveRecordInjection.rb:32:5:32:27 | call to joins |
1616
| ActiveRecordInjection.rb:32:5:32:61 | call to where |
17+
| ActiveRecordInjection.rb:45:5:45:34 | call to delete_all |
1718
potentiallyUnsafeSqlExecutingMethodCall
1819
| ActiveRecordInjection.rb:22:5:22:42 | call to delete_all |
1920
| ActiveRecordInjection.rb:28:5:28:35 | call to where |
20-
| ActiveRecordInjection.rb:32:5:32:27 | call to joins |
2121
| ActiveRecordInjection.rb:32:5:32:61 | call to where |
22+
| ActiveRecordInjection.rb:45:5:45:34 | call to delete_all |

ql/test/library-tests/frameworks/ActiveRecordInjection.rb

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class User < ApplicationRecord
99
class Admin < User
1010
end
1111

12-
class FooController < ApplicationController
12+
class FooController < ActionController::Base
1313

1414
MAX_USER_ID = 100_000
1515

@@ -32,3 +32,20 @@ def some_request_handler
3232
UserGroup.joins(:users).where("user.id = #{params[:id]}")
3333
end
3434
end
35+
36+
37+
class BarController < ApplicationController
38+
39+
def some_other_request_handler
40+
ps = params
41+
# TODO: we don't pick up on this indirect params field reference
42+
uid = ps[:id]
43+
44+
# DELETE FROM "users" WHERE (id = #{uid})
45+
User.delete_all("id = " + uid)
46+
end
47+
48+
end
49+
50+
class BazController < BarController
51+
end

0 commit comments

Comments
 (0)