Skip to content

Commit 4f9518a

Browse files
authored
Merge pull request #293 from github/hmac-code-injection
Add query for Code Injection
2 parents f347505 + 41608ef commit 4f9518a

File tree

13 files changed

+293
-0
lines changed

13 files changed

+293
-0
lines changed

ql/lib/codeql/ruby/Concepts.qll

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ class SystemCommandExecution extends DataFlow::Node instanceof SystemCommandExec
461461
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
462462
}
463463

464+
/** Provides a class for modeling new operating system command APIs. */
464465
module SystemCommandExecution {
465466
/**
466467
* A data flow node that executes an operating system command, for instance by spawning a new
@@ -477,3 +478,28 @@ module SystemCommandExecution {
477478
predicate isShellInterpreted(DataFlow::Node arg) { none() }
478479
}
479480
}
481+
482+
/**
483+
* A data-flow node that dynamically executes Ruby code.
484+
*
485+
* Extend this class to refine existing API models. If you want to model new APIs,
486+
* extend `CodeExecution::Range` instead.
487+
*/
488+
class CodeExecution extends DataFlow::Node instanceof CodeExecution::Range {
489+
/** Gets the argument that specifies the code to be executed. */
490+
DataFlow::Node getCode() { result = super.getCode() }
491+
}
492+
493+
/** Provides a class for modeling new dynamic code execution APIs. */
494+
module CodeExecution {
495+
/**
496+
* A data-flow node that dynamically executes Ruby code.
497+
*
498+
* Extend this class to model new APIs. If you want to refine existing API models,
499+
* extend `CodeExecution` instead.
500+
*/
501+
abstract class Range extends DataFlow::Node {
502+
/** Gets the argument that specifies the code to be executed. */
503+
abstract DataFlow::Node getCode();
504+
}
505+
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,39 @@ class Open3PipelineCall extends SystemCommandExecution::Range {
239239
arg.asExpr().getExpr() = methodCall.getAnArgument()
240240
}
241241
}
242+
243+
/**
244+
* A call to `Kernel.eval`, which executes its argument as Ruby code.
245+
* ```ruby
246+
* a = 1
247+
* Kernel.eval("a = 2")
248+
* a # => 2
249+
* ```
250+
*/
251+
class EvalCallCodeExecution extends CodeExecution::Range {
252+
KernelMethodCall methodCall;
253+
254+
EvalCallCodeExecution() {
255+
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "eval"
256+
}
257+
258+
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
259+
}
260+
261+
/**
262+
* A call to `Kernel#send`, which executes its arguments as a Ruby method call.
263+
* ```ruby
264+
* arr = []
265+
* arr.send("push", 1)
266+
* arr # => [1]
267+
* ```
268+
*/
269+
class SendCallCodeExecution extends CodeExecution::Range {
270+
KernelMethodCall methodCall;
271+
272+
SendCallCodeExecution() {
273+
this.asExpr().getExpr() = methodCall and methodCall.getMethodName() = "send"
274+
}
275+
276+
override DataFlow::Node getCode() { result.asExpr().getExpr() = methodCall.getAnArgument() }
277+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
private import ruby
2+
private import codeql.ruby.DataFlow
3+
private import codeql.ruby.Concepts
4+
private import codeql.ruby.Frameworks
5+
private import codeql.ruby.dataflow.RemoteFlowSources
6+
private import codeql.ruby.dataflow.BarrierGuards
7+
8+
/**
9+
* Provides default sources, sinks and sanitizers for detecting
10+
* "Code injection" vulnerabilities, as well as extension points for
11+
* adding your own.
12+
*/
13+
module CodeInjection {
14+
/**
15+
* A data flow source for "Code injection" vulnerabilities.
16+
*/
17+
abstract class Source extends DataFlow::Node { }
18+
19+
/**
20+
* A data flow sink for "Code injection" vulnerabilities.
21+
*/
22+
abstract class Sink extends DataFlow::Node { }
23+
24+
/**
25+
* A sanitizer guard for "Code injection" vulnerabilities.
26+
*/
27+
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
28+
29+
/**
30+
* A source of remote user input, considered as a flow source.
31+
*/
32+
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
33+
34+
/**
35+
* A call that evaluates its arguments as Ruby code, considered as a flow sink.
36+
*/
37+
class CodeExecutionAsSink extends Sink {
38+
CodeExecutionAsSink() { this = any(CodeExecution c).getCode() }
39+
}
40+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Provides a taint-tracking configuration for detecting "Code injection" vulnerabilities.
3+
*
4+
* Note, for performance reasons: only import this file if `Configuration` is needed,
5+
* otherwise `CodeInjectionCustomizations` should be imported instead.
6+
*/
7+
8+
import codeql.ruby.DataFlow::DataFlow::PathGraph
9+
import codeql.ruby.DataFlow
10+
import codeql.ruby.TaintTracking
11+
import CodeInjectionCustomizations::CodeInjection
12+
import codeql.ruby.dataflow.BarrierGuards
13+
14+
/**
15+
* A taint-tracking configuration for detecting "Code injection" vulnerabilities.
16+
*/
17+
class Configuration extends TaintTracking::Configuration {
18+
Configuration() { this = "CodeInjection" }
19+
20+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
21+
22+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
23+
24+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
25+
guard instanceof SanitizerGuard or
26+
guard instanceof StringConstCompare or
27+
guard instanceof StringConstArrayInclusionCall
28+
}
29+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
Directly evaluating user input (for example, an HTTP request parameter) as code without first
9+
sanitizing the input allows an attacker arbitrary code execution. This can occur when user
10+
input is passed to code that interprets it as an expression to be
11+
evaluated, using methods such as <code>Kernel.eval</code> or <code>Kernel.send</code>.
12+
</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>
17+
Avoid including user input in any expression that may be dynamically evaluated. If user input must
18+
be included, use context-specific escaping before including it.
19+
It is important that the correct escaping is used for the type of evaluation that will occur.
20+
</p>
21+
</recommendation>
22+
23+
<example>
24+
<p>
25+
The following example shows two functions setting a name from a request.
26+
The first function uses <code>eval</code> to execute the <code>set_name</code> method.
27+
This is dangerous as it can allow a malicious user to execute arbitrary code on the server.
28+
For example, the user could supply the value <code>"' + exec('rm -rf') + '"</code>
29+
to destroy the server's file system.
30+
The second function calls the <code>set_name</code> method directly and is thus safe.
31+
32+
</p>
33+
34+
<sample src="examples/code_injection.rb" />
35+
</example>
36+
37+
<references>
38+
<li>
39+
OWASP:
40+
<a href="https://www.owasp.org/index.php/Code_Injection">Code Injection</a>.
41+
</li>
42+
<li>
43+
Wikipedia: <a href="https://en.wikipedia.org/wiki/Code_injection">Code Injection</a>.
44+
</li>
45+
</references>
46+
</qhelp>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @name Code injection
3+
* @description Interpreting unsanitized user input as code allows a malicious user to perform arbitrary
4+
* code execution.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 9.3
8+
* @sub-severity high
9+
* @precision high
10+
* @id rb/code-injection
11+
* @tags security
12+
* external/owasp/owasp-a1
13+
* external/cwe/cwe-094
14+
* external/cwe/cwe-095
15+
* external/cwe/cwe-116
16+
*/
17+
18+
import ruby
19+
import codeql.ruby.security.CodeInjectionQuery
20+
import DataFlow::PathGraph
21+
22+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
23+
where
24+
config.hasFlowPath(source, sink) and
25+
sourceNode = source.getNode()
26+
select sink.getNode(), source, sink, "This code execution depends on $@.", sourceNode,
27+
"a user-provided value"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class UsersController < ActionController::Base
2+
# BAD - Allow user to define code to be run.
3+
def create_bad
4+
first_name = params[:first_name]
5+
eval("set_name(#{first_name})")
6+
end
7+
8+
# GOOD - Call code directly
9+
def create_good
10+
first_name = params[:first_name]
11+
set_name(first_name)
12+
end
13+
14+
def set_name(name)
15+
@name = name
16+
end
17+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Uses of eval and send
2+
3+
eval("raise \"error\"")
4+
send("raise", "error")
5+
6+
a = []
7+
a.send("raise", "error")
8+
9+
class Foo
10+
def eval(x)
11+
x + 1
12+
end
13+
14+
def send(*args)
15+
2
16+
end
17+
18+
def run
19+
eval("exit 1")
20+
end
21+
end
22+
23+
Foo.new.send("exit", 1)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@ open3PipelineCallExecutions
5858
| CommandExecution.rb:63:1:63:40 | call to pipeline_w |
5959
| CommandExecution.rb:64:1:64:44 | call to pipeline_start |
6060
| CommandExecution.rb:65:1:65:38 | call to pipeline |
61+
evalCallCodeExecutions
62+
| Eval.rb:3:1:3:23 | call to eval |
63+
sendCallCodeExecutions
64+
| Eval.rb:4:1:4:22 | call to send |
65+
| Eval.rb:7:1:7:24 | call to send |

ql/test/library-tests/frameworks/StandardLibrary.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ query predicate kernelSpawnCallExecutions(KernelSpawnCall c) { any() }
1313
query predicate open3CallExecutions(Open3Call c) { any() }
1414

1515
query predicate open3PipelineCallExecutions(Open3PipelineCall c) { any() }
16+
17+
query predicate evalCallCodeExecutions(EvalCallCodeExecution e) { any() }
18+
19+
query predicate sendCallCodeExecutions(SendCallCodeExecution e) { any() }

0 commit comments

Comments
 (0)