Skip to content

Commit f61161e

Browse files
authored
Merge pull request #321 from github/hmac-more-eval
Identify more instances of code injection
2 parents 7f103b9 + 8c0c08e commit f61161e

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

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

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ class KernelMethodCall extends MethodCall {
1212
KernelMethodCall() {
1313
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
1414
or
15-
// we assume that if there's no obvious target for this method call
16-
// and the method name matches a Kernel method, then it is a Kernel method call.
17-
// TODO: ApiGraphs should ideally handle this case
18-
not exists(this.(Call).getATarget()) and
15+
this instanceof UnknownMethodCall and
1916
(
2017
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
2118
or
@@ -58,6 +55,45 @@ private predicate isPrivateKernelMethod(string method) {
5855
]
5956
}
6057

58+
/**
59+
* Instance methods on `BasicObject`, which are available to all classes.
60+
*/
61+
class BasicObjectInstanceMethodCall extends UnknownMethodCall {
62+
BasicObjectInstanceMethodCall() {
63+
this.getMethodName() in [
64+
"equal?", "instance_eval", "instance_exec", "method_missing", "singleton_method_added",
65+
"singleton_method_removed", "singleton_method_undefined"
66+
]
67+
}
68+
}
69+
70+
/**
71+
* Instance methods on `Object`, which are available to all classes except `BasicObject`.
72+
*/
73+
class ObjectInstanceMethodCall extends UnknownMethodCall {
74+
ObjectInstanceMethodCall() {
75+
this.getMethodName() in [
76+
"!~", "<=>", "===", "=~", "callable_methods", "define_singleton_method", "display",
77+
"do_until", "do_while", "dup", "enum_for", "eql?", "extend", "f", "freeze", "h", "hash",
78+
"inspect", "instance_of?", "instance_variable_defined?", "instance_variable_get",
79+
"instance_variable_set", "instance_variables", "is_a?", "itself", "kind_of?",
80+
"matching_methods", "method", "method_missing", "methods", "nil?", "object_id",
81+
"private_methods", "protected_methods", "public_method", "public_methods", "public_send",
82+
"remove_instance_variable", "respond_to?", "respond_to_missing?", "send",
83+
"shortest_abbreviation", "singleton_class", "singleton_method", "singleton_methods",
84+
"taint", "tainted?", "to_enum", "to_s", "trust", "untaint", "untrust", "untrusted?"
85+
]
86+
}
87+
}
88+
89+
/**
90+
* Method calls which have no known target.
91+
* These will typically be calls to methods inherited from a superclass.
92+
*/
93+
class UnknownMethodCall extends MethodCall {
94+
UnknownMethodCall() { not exists(this.(Call).getATarget()) }
95+
}
96+
6197
/**
6298
* A system command executed via subshell literal syntax.
6399
* E.g.
@@ -275,3 +311,42 @@ class SendCallCodeExecution extends CodeExecution::Range {
275311

276312
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
277313
}
314+
315+
/**
316+
* A call to `BasicObject#instance_eval`, which executes its argument as Ruby code.
317+
*/
318+
class InstanceEvalCallCodeExecution extends CodeExecution::Range {
319+
BasicObjectInstanceMethodCall methodCall;
320+
321+
InstanceEvalCallCodeExecution() {
322+
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "instance_eval"
323+
}
324+
325+
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
326+
}
327+
328+
/**
329+
* A call to `Module#class_eval`, which executes its argument as Ruby code.
330+
*/
331+
class ClassEvalCallCodeExecution extends CodeExecution::Range {
332+
UnknownMethodCall methodCall;
333+
334+
ClassEvalCallCodeExecution() {
335+
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "class_eval"
336+
}
337+
338+
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
339+
}
340+
341+
/**
342+
* A call to `Module#module_eval`, which executes its argument as Ruby code.
343+
*/
344+
class ModuleEvalCallCodeExecution extends CodeExecution::Range {
345+
UnknownMethodCall methodCall;
346+
347+
ModuleEvalCallCodeExecution() {
348+
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "module_eval"
349+
}
350+
351+
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
352+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
edges
22
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code |
3+
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:15:20:15:23 | code |
4+
| CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:21:18:24 | code |
35
nodes
46
| CodeInjection.rb:3:12:3:17 | call to params : | semmle.label | call to params : |
57
| CodeInjection.rb:6:10:6:13 | code | semmle.label | code |
68
| CodeInjection.rb:9:10:9:15 | call to params | semmle.label | call to params |
9+
| CodeInjection.rb:15:20:15:23 | code | semmle.label | code |
10+
| CodeInjection.rb:18:21:18:24 | code | semmle.label | code |
711
subpaths
812
#select
913
| CodeInjection.rb:6:10:6:13 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:6:10:6:13 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
1014
| CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | CodeInjection.rb:9:10:9:15 | call to params | This code execution depends on $@. | CodeInjection.rb:9:10:9:15 | call to params | a user-provided value |
15+
| CodeInjection.rb:15:20:15:23 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:15:20:15:23 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |
16+
| CodeInjection.rb:18:21:18:24 | code | CodeInjection.rb:3:12:3:17 | call to params : | CodeInjection.rb:18:21:18:24 | code | This code execution depends on $@. | CodeInjection.rb:3:12:3:17 | call to params | a user-provided value |

ql/test/query-tests/security/cwe-094/CodeInjection.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ def create
1010

1111
# GOOD
1212
Foo.new.bar(code)
13+
14+
# BAD
15+
Foo.class_eval(code)
16+
17+
# BAD
18+
Foo.module_eval(code)
19+
20+
# GOOD
21+
Bar.class_eval(code)
1322
end
1423

1524
def update
@@ -27,3 +36,9 @@ def bar(x)
2736
eval(x)
2837
end
2938
end
39+
40+
class Bar
41+
def self.class_eval(x)
42+
true
43+
end
44+
end

0 commit comments

Comments
 (0)