Skip to content

Commit ce1d0d2

Browse files
authored
Merge pull request github#15780 from p-/p--method-injection
Ruby: sinks for code injection via calls to `method`
2 parents 038afc4 + 4adc373 commit ce1d0d2

File tree

5 files changed

+42
-2
lines changed

5 files changed

+42
-2
lines changed

ruby/ql/lib/codeql/ruby/frameworks/core/Kernel.qll

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ module Kernel {
4444
*/
4545
private predicate isPublicKernelMethod(string method) {
4646
method in [
47-
"class", "clone", "frozen?", "tap", "then", "yield_self", "send", "public_send", "__send__"
47+
"class", "clone", "frozen?", "tap", "then", "yield_self", "send", "public_send", "__send__",
48+
"method", "public_method", "singleton_method"
4849
]
4950
}
5051

@@ -176,6 +177,24 @@ module Kernel {
176177
override predicate runsArbitraryCode() { none() }
177178
}
178179

180+
/**
181+
* A call to `method`, `public_method` or `singleton_method` which returns a method object.
182+
* To actually execute the method, the `call` method needs to be called on the object.
183+
* ```ruby
184+
* m = method("exit")
185+
* m.call()
186+
* ```
187+
*/
188+
class MethodCallCodeExecution extends CodeExecution::Range, KernelMethodCall {
189+
MethodCallCodeExecution() {
190+
this.getMethodName() = ["method", "public_method", "singleton_method"]
191+
}
192+
193+
override DataFlow::Node getCode() { result = this.getArgument(0) }
194+
195+
override predicate runsArbitraryCode() { none() }
196+
}
197+
179198
private class TapSummary extends SimpleSummarizedCallable {
180199
TapSummary() { this = "tap" }
181200

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Calls to `Object#method`, `Object#public_method` and `Object#singleton_method` with untrusted data are now recognised as sinks for code injection.

ruby/ql/test/library-tests/frameworks/core/Kernel.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ sendCallCodeExecutions
4242
| Eval.rb:7:1:7:19 | call to send | Eval.rb:7:8:7:13 | "push" |
4343
| Kernel.rb:2:1:2:22 | call to send | Kernel.rb:2:6:2:12 | "raise" |
4444
| Kernel.rb:5:1:5:19 | call to send | Kernel.rb:5:8:5:13 | "push" |
45+
methodCallCodeExecutions
46+
| Kernel.rb:92:1:92:14 | call to method | Kernel.rb:92:8:92:13 | "exit" |
47+
| Kernel.rb:93:1:93:21 | call to public_method | Kernel.rb:93:15:93:20 | "exit" |
48+
| Kernel.rb:94:1:94:23 | call to singleton_method | Kernel.rb:94:18:94:22 | "foo" |
49+
| Kernel.rb:96:1:96:18 | call to method | Kernel.rb:96:12:96:17 | "exit" |
50+
| Kernel.rb:97:1:97:25 | call to public_method | Kernel.rb:97:19:97:24 | "exit" |
51+
| Kernel.rb:98:1:98:27 | call to singleton_method | Kernel.rb:98:22:98:26 | "foo" |
4552
evalCallCodeExecutions
4653
| Eval.rb:3:1:3:43 | call to eval | Eval.rb:3:6:3:22 | "raise \\"error\\"" |
4754
| Kernel.rb:1:1:1:43 | call to eval | Kernel.rb:1:6:1:22 | "raise \\"error\\"" |

ruby/ql/test/library-tests/frameworks/core/Kernel.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ query predicate kernelSpawnCallExecutions(KernelSpawnCall c) { any() }
99

1010
query DataFlow::Node sendCallCodeExecutions(SendCallCodeExecution e) { result = e.getCode() }
1111

12+
query DataFlow::Node methodCallCodeExecutions(MethodCallCodeExecution e) { result = e.getCode() }
13+
1214
query DataFlow::Node evalCallCodeExecutions(EvalCallCodeExecution e) { result = e.getCode() }

ruby/ql/test/library-tests/frameworks/core/Kernel.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,12 @@ def run
8787
end
8888
end
8989

90-
UnknownModule.system("ls")
90+
UnknownModule.system("ls")
91+
92+
method("exit").call
93+
public_method("exit").call
94+
singleton_method("foo").call
95+
96+
Foo.method("exit").call
97+
Foo.public_method("exit").call
98+
Foo.singleton_method("foo").call

0 commit comments

Comments
 (0)