Merge pull request #280 from github/hmac-cli-injection

Add CLI Injection query
This commit is contained in:
Harry Maclean
2021-09-20 08:54:01 +01:00
committed by GitHub
14 changed files with 688 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ private import codeql.ruby.CFG
private import codeql.ruby.DataFlow
private import codeql.ruby.Frameworks
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.ApiGraphs
/**
* A data-flow node that executes SQL statements.
@@ -312,3 +313,32 @@ module HTTP {
}
}
}
/**
* A data flow node that executes an operating system command,
* for instance by spawning a new process.
*/
class SystemCommandExecution extends DataFlow::Node instanceof SystemCommandExecution::Range {
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
/** Gets an argument to this execution that specifies the command or an argument to it. */
DataFlow::Node getAnArgument() { result = super.getAnArgument() }
}
module SystemCommandExecution {
/**
* A data flow node that executes an operating system command, for instance by spawning a new
* process.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `SystemCommandExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets an argument to this execution that specifies the command or an argument to it. */
abstract DataFlow::Node getAnArgument();
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
}

View File

@@ -5,3 +5,4 @@
private import codeql.ruby.frameworks.ActionController
private import codeql.ruby.frameworks.ActiveRecord
private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.StandardLibrary

View File

@@ -0,0 +1,245 @@
private import codeql.ruby.AST
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.dataflow.internal.DataFlowImplCommon
/**
* The `Kernel` module is included by the `Object` class, so its methods are available
* in every Ruby object. In addition, its module methods can be called by
* providing a specific receiver as in `Kernel.exit`.
*/
class KernelMethodCall extends MethodCall {
KernelMethodCall() {
this = API::getTopLevelMember("Kernel").getAMethodCall(_).asExpr().getExpr()
or
// we assume that if there's no obvious target for this method call
// and the method name matches a Kernel method, then it is a Kernel method call.
// TODO: ApiGraphs should ideally handle this case
not exists(DataFlowCallable method, DataFlowCall call |
viableCallable(call) = method and call.getExpr() = this
) and
(
this.getReceiver() instanceof Self and isPrivateKernelMethod(this.getMethodName())
or
isPublicKernelMethod(this.getMethodName())
)
}
}
/**
* Public methods in the `Kernel` module. These can be invoked on any object via the usual dot syntax.
* ```ruby
* arr = []
* arr.send("push", 5) # => [5]
* ```
*/
private predicate isPublicKernelMethod(string method) {
method in ["class", "clone", "frozen?", "tap", "then", "yield_self", "send"]
}
/**
* Private methods in the `Kernel` module.
* These can be be invoked on `self`, on `Kernel`, or using a low-level primitive like `send` or `instance_eval`.
* ```ruby
* puts "hello world"
* Kernel.puts "hello world"
* 5.instance_eval { puts "hello world" }
* 5.send("puts", "hello world")
* ```
*/
private predicate isPrivateKernelMethod(string method) {
method in [
"Array", "Complex", "Float", "Hash", "Integer", "Rational", "String", "__callee__", "__dir__",
"__method__", "`", "abort", "at_exit", "autoload", "autoload?", "binding", "block_given?",
"callcc", "caller", "caller_locations", "catch", "chomp", "chop", "eval", "exec", "exit",
"exit!", "fail", "fork", "format", "gets", "global_variables", "gsub", "iterator?", "lambda",
"load", "local_variables", "loop", "open", "p", "pp", "print", "printf", "proc", "putc",
"puts", "raise", "rand", "readline", "readlines", "require", "require_relative", "select",
"set_trace_func", "sleep", "spawn", "sprintf", "srand", "sub", "syscall", "system", "test",
"throw", "trace_var", "trap", "untrace_var", "warn"
]
}
/**
* A system command executed via subshell literal syntax.
* E.g.
* ```ruby
* `cat foo.txt`
* %x(cat foo.txt)
* %x[cat foo.txt]
* %x{cat foo.txt}
* %x/cat foo.txt/
* ```
*/
class SubshellLiteralExecution extends SystemCommandExecution::Range {
SubshellLiteral literal;
SubshellLiteralExecution() { this.asExpr().getExpr() = literal }
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = literal.getComponent(_) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getAnArgument() }
}
/**
* A system command executed via shell heredoc syntax.
* E.g.
* ```ruby
* <<`EOF`
* cat foo.text
* EOF
* ```
*/
class SubshellHeredocExecution extends SystemCommandExecution::Range {
HereDoc heredoc;
SubshellHeredocExecution() { this.asExpr().getExpr() = heredoc and heredoc.isSubShell() }
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = heredoc.getComponent(_) }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getAnArgument() }
}
/**
* A system command executed via the `Kernel.system` method.
* `Kernel.system` accepts three argument forms:
* - A single string. If it contains no shell meta characters, keywords or
* builtins, it is executed directly in a subprocess.
* Otherwise, it is executed in a subshell.
* ```ruby
* system("cat foo.txt | tail")
* ```
* - A command and one or more arguments.
* The command is executed in a subprocess.
* ```ruby
* system("cat", "foo.txt")
* ```
* - An array containing the command name and argv[0], followed by zero or more arguments.
* The command is executed in a subprocess.
* ```ruby
* system(["cat", "cat"], "foo.txt")
* ```
* In addition, `Kernel.system` accepts an optional environment hash as the
* first argument and an optional options hash as the last argument.
* We don't yet distinguish between these arguments and the command arguments.
* ```ruby
* system({"FOO" => "BAR"}, "cat foo.txt | tail", {unsetenv_others: true})
* ```
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-system
*/
class KernelSystemCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
KernelSystemCall() {
methodCall.getMethodName() = "system" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.system invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
}
}
/**
* A system command executed via the `Kernel.exec` method.
* `Kernel.exec` takes the same argument forms as `Kernel.system`. See `KernelSystemCall` for details.
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-exec
*/
class KernelExecCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
KernelExecCall() {
methodCall.getMethodName() = "exec" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.exec invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
}
}
/**
* A system command executed via the `Kernel.spawn` method.
* `Kernel.spawn` takes the same argument forms as `Kernel.system`.
* See `KernelSystemCall` for details.
* Ruby documentation: https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-spawn
* TODO: document and handle the env and option arguments.
* ```
* spawn([env,] command... [,options]) → pid
* ```
*/
class KernelSpawnCall extends SystemCommandExecution::Range {
KernelMethodCall methodCall;
KernelSpawnCall() {
methodCall.getMethodName() = "spawn" and
this.asExpr().getExpr() = methodCall
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override predicate isShellInterpreted(DataFlow::Node arg) {
// Kernel.spawn invokes a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
}
}
/**
* A system command executed via one of the `Open3` methods.
* These methods take the same argument forms as `Kernel.system`.
* See `KernelSystemCall` for details.
*/
class Open3Call extends SystemCommandExecution::Range {
MethodCall methodCall;
Open3Call() {
this.asExpr().getExpr() = methodCall and
this =
API::getTopLevelMember("Open3")
.getAMethodCall(["popen3", "popen2", "popen2e", "capture3", "capture2", "capture2e"])
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override predicate isShellInterpreted(DataFlow::Node arg) {
// These Open3 methods invoke a subshell if you provide a single string as argument
methodCall.getNumberOfArguments() = 1 and arg.asExpr().getExpr() = methodCall.getAnArgument()
}
}
/**
* A pipeline of system commands constructed via one of the `Open3` methods.
* These methods accept a variable argument list of commands.
* Commands can be in any form supported by `Kernel.system`. See `KernelSystemCall` for details.
* ```ruby
* Open3.pipeline("cat foo.txt", "tail")
* Open3.pipeline(["cat", "foo.txt"], "tail")
* Open3.pipeline([{}, "cat", "foo.txt"], "tail")
* Open3.pipeline([["cat", "cat"], "foo.txt"], "tail")
*/
class Open3PipelineCall extends SystemCommandExecution::Range {
MethodCall methodCall;
Open3PipelineCall() {
this.asExpr().getExpr() = methodCall and
this =
API::getTopLevelMember("Open3")
.getAMethodCall(["pipeline_rw", "pipeline_r", "pipeline_w", "pipeline_start", "pipeline"])
}
override DataFlow::Node getAnArgument() { result.asExpr().getExpr() = methodCall.getAnArgument() }
override predicate isShellInterpreted(DataFlow::Node arg) {
// A command in the pipeline is executed in a subshell if it is given as a single string argument.
arg.asExpr().getExpr() instanceof StringlikeLiteral and
arg.asExpr().getExpr() = methodCall.getAnArgument()
}
}

View File

@@ -0,0 +1,54 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* command-injection vulnerabilities, as well as extension points for
* adding your own.
*/
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.RemoteFlowSources
private import codeql.ruby.Concepts
private import codeql.ruby.Frameworks
private import codeql.ruby.ApiGraphs
module CommandInjection {
/**
* A data flow source for command-injection vulnerabilities.
*/
abstract class Source extends DataFlow::Node {
/** Gets a string that describes the type of this remote flow source. */
abstract string getSourceType();
}
/**
* A data flow sink for command-injection vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for command-injection vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/** A source of remote user input, considered as a flow source for command injection. */
class RemoteFlowSourceAsSource extends Source {
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
override string getSourceType() { result = "a user-provided value" }
}
/**
* A command argument to a function that initiates an operating system command.
*/
class SystemCommandExecutionSink extends Sink {
SystemCommandExecutionSink() { exists(SystemCommandExecution c | c.isShellInterpreted(this)) }
}
/**
* A call to `Shellwords.escape` or `Shellwords.shellescape` sanitizes its input.
*/
class ShellwordsEscapeAsSanitizer extends Sanitizer {
ShellwordsEscapeAsSanitizer() {
this = API::getTopLevelMember("Shellwords").getAMethodCall(["escape", "shellescape"])
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* Provides a taint tracking configuration for reasoning about
* command-injection vulnerabilities (CWE-078).
*
* Note, for performance reasons: only import this file if
* `CommandInjection::Configuration` is needed, otherwise
* `CommandInjectionCustomizations` should be imported instead.
*/
import ruby
import codeql.ruby.TaintTracking
import CommandInjectionCustomizations::CommandInjection
import codeql.ruby.DataFlow
import codeql.ruby.dataflow.BarrierGuards
/**
* A taint-tracking configuration for reasoning about command-injection vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CommandInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof StringConstCompare or
guard instanceof StringConstArrayInclusionCall
}
}

View File

@@ -0,0 +1,44 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Code that passes user input directly to
<code>Kernel.system</code>, <code>Kernel.exec</code>, or some other library
routine that executes a command, allows the user to execute malicious
code.</p>
</overview>
<recommendation>
<p>If possible, use hard-coded string literals to specify the command to run
or library to load. Instead of passing the user input directly to the
process or library function, examine the user input and then choose
among hard-coded string literals.</p>
<p>If the applicable libraries or commands cannot be determined at
compile time, then add code to verify that the user input string is
safe before using it.</p>
</recommendation>
<example>
<p>The following example shows code that takes a shell script that can be changed
maliciously by a user, and passes it straight to <code>Kernel.system</code>
without examining it first.</p>
<sample src="examples/command_injection.rb" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
<!-- LocalWords: CWE untrusted unsanitized Runtime
-->
</references>
</qhelp>

View File

@@ -0,0 +1,25 @@
/**
* @name Uncontrolled command line
* @description Using externally controlled strings in a command line may allow a malicious
* user to change the meaning of the command.
* @kind path-problem
* @problem.severity error
* @security-severity 9.8
* @precision high
* @id rb/command-line-injection
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import ruby
import codeql.ruby.security.CommandInjectionQuery
import DataFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Source sourceNode
where
config.hasFlowPath(source, sink) and
sourceNode = source.getNode()
select sink.getNode(), source, sink, "This command depends on $@.", sourceNode,
sourceNode.getSourceType()

View File

@@ -0,0 +1,6 @@
class UsersController < ActionController::Base
def create
command = params[:command]
system(command) # BAD
end
end

View File

@@ -0,0 +1,90 @@
`echo foo`
%x(echo foo)
%x{echo foo}
%x[echo foo]
%x/echo foo/
system("echo foo")
system("echo", "foo")
system(["echo", "echo"], "foo")
system({"FOO" => "BAR"}, "echo foo")
system({"FOO" => "BAR"}, "echo", "foo")
system({"FOO" => "BAR"}, ["echo", "echo"], "foo")
system("echo foo", unsetenv_others: true)
system("echo", "foo", unsetenv_others: true)
system(["echo", "echo"], "foo", unsetenv_others: true)
system({"FOO" => "BAR"}, "echo foo", unsetenv_others: true)
system({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true)
system({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true)
exec("echo foo")
exec("echo", "foo")
exec(["echo", "echo"], "foo")
exec({"FOO" => "BAR"}, "echo foo")
exec({"FOO" => "BAR"}, "echo", "foo")
exec({"FOO" => "BAR"}, ["echo", "echo"], "foo")
exec("echo foo", unsetenv_others: true)
exec("echo", "foo", unsetenv_others: true)
exec(["echo", "echo"], "foo", unsetenv_others: true)
exec({"FOO" => "BAR"}, "echo foo", unsetenv_others: true)
exec({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true)
exec({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true)
spawn("echo foo")
spawn("echo", "foo")
spawn(["echo", "echo"], "foo")
spawn({"FOO" => "BAR"}, "echo foo")
spawn({"FOO" => "BAR"}, "echo", "foo")
spawn({"FOO" => "BAR"}, ["echo", "echo"], "foo")
spawn("echo foo", unsetenv_others: true)
spawn("echo", "foo", unsetenv_others: true)
spawn(["echo", "echo"], "foo", unsetenv_others: true)
spawn({"FOO" => "BAR"}, "echo foo", unsetenv_others: true)
spawn({"FOO" => "BAR"}, "echo", "foo", unsetenv_others: true)
spawn({"FOO" => "BAR"}, ["echo", "echo"], "foo", unsetenv_others: true)
Open3.popen3("echo foo")
Open3.popen2("echo foo")
Open3.popen2e("echo foo")
Open3.capture3("echo foo")
Open3.capture2("echo foo")
Open3.capture2e("echo foo")
Open3.pipeline_rw("echo foo", "grep bar")
Open3.pipeline_r("echo foo", "grep bar")
Open3.pipeline_w("echo foo", "grep bar")
Open3.pipeline_start("echo foo", "grep bar")
Open3.pipeline("echo foo", "grep bar")
<<`EOF`
echo foo
EOF
module MockSystem
def system(*args)
args
end
def self.system(*args)
args
end
end
class Foo
include MockSystem
def run
system("ls")
MockSystem.system("ls")
end
end
UnknownModule.system("ls")

View File

@@ -0,0 +1,60 @@
subshellLiteralExecutions
| CommandExecution.rb:1:1:1:10 | `echo foo` |
| CommandExecution.rb:2:1:2:12 | `echo foo` |
| CommandExecution.rb:3:1:3:12 | `echo foo` |
| CommandExecution.rb:4:1:4:12 | `echo foo` |
| CommandExecution.rb:5:1:5:12 | `echo foo` |
subshellHeredocExecutions
| CommandExecution.rb:67:1:67:7 | <<`EOF` |
kernelSystemCallExecutions
| CommandExecution.rb:7:1:7:18 | call to system |
| CommandExecution.rb:8:1:8:21 | call to system |
| CommandExecution.rb:9:1:9:31 | call to system |
| CommandExecution.rb:11:1:11:36 | call to system |
| CommandExecution.rb:12:1:12:39 | call to system |
| CommandExecution.rb:13:1:13:49 | call to system |
| CommandExecution.rb:15:1:15:41 | call to system |
| CommandExecution.rb:16:1:16:44 | call to system |
| CommandExecution.rb:17:1:17:54 | call to system |
| CommandExecution.rb:19:1:19:59 | call to system |
| CommandExecution.rb:20:1:20:62 | call to system |
| CommandExecution.rb:21:1:21:72 | call to system |
kernelExecCallExecutions
| CommandExecution.rb:23:1:23:16 | call to exec |
| CommandExecution.rb:24:1:24:19 | call to exec |
| CommandExecution.rb:25:1:25:29 | call to exec |
| CommandExecution.rb:27:1:27:34 | call to exec |
| CommandExecution.rb:28:1:28:37 | call to exec |
| CommandExecution.rb:29:1:29:47 | call to exec |
| CommandExecution.rb:31:1:31:39 | call to exec |
| CommandExecution.rb:32:1:32:42 | call to exec |
| CommandExecution.rb:33:1:33:52 | call to exec |
| CommandExecution.rb:35:1:35:57 | call to exec |
| CommandExecution.rb:36:1:36:60 | call to exec |
| CommandExecution.rb:37:1:37:70 | call to exec |
kernelSpawnCallExecutions
| CommandExecution.rb:39:1:39:17 | call to spawn |
| CommandExecution.rb:40:1:40:20 | call to spawn |
| CommandExecution.rb:41:1:41:30 | call to spawn |
| CommandExecution.rb:43:1:43:35 | call to spawn |
| CommandExecution.rb:44:1:44:38 | call to spawn |
| CommandExecution.rb:45:1:45:48 | call to spawn |
| CommandExecution.rb:47:1:47:40 | call to spawn |
| CommandExecution.rb:48:1:48:43 | call to spawn |
| CommandExecution.rb:49:1:49:53 | call to spawn |
| CommandExecution.rb:51:1:51:58 | call to spawn |
| CommandExecution.rb:52:1:52:61 | call to spawn |
| CommandExecution.rb:53:1:53:71 | call to spawn |
open3CallExecutions
| CommandExecution.rb:55:1:55:24 | call to popen3 |
| CommandExecution.rb:56:1:56:24 | call to popen2 |
| CommandExecution.rb:57:1:57:25 | call to popen2e |
| CommandExecution.rb:58:1:58:26 | call to capture3 |
| CommandExecution.rb:59:1:59:26 | call to capture2 |
| CommandExecution.rb:60:1:60:27 | call to capture2e |
open3PipelineCallExecutions
| CommandExecution.rb:61:1:61:41 | call to pipeline_rw |
| CommandExecution.rb:62:1:62:40 | call to pipeline_r |
| CommandExecution.rb:63:1:63:40 | call to pipeline_w |
| CommandExecution.rb:64:1:64:44 | call to pipeline_start |
| CommandExecution.rb:65:1:65:38 | call to pipeline |

View File

@@ -0,0 +1,15 @@
import codeql.ruby.frameworks.StandardLibrary
query predicate subshellLiteralExecutions(SubshellLiteralExecution e) { any() }
query predicate subshellHeredocExecutions(SubshellHeredocExecution e) { any() }
query predicate kernelSystemCallExecutions(KernelSystemCall c) { any() }
query predicate kernelExecCallExecutions(KernelExecCall c) { any() }
query predicate kernelSpawnCallExecutions(KernelSpawnCall c) { any() }
query predicate open3CallExecutions(Open3Call c) { any() }
query predicate open3PipelineCallExecutions(Open3PipelineCall c) { any() }

View File

@@ -0,0 +1,33 @@
edges
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:8:16:8:18 | cmd |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:10:14:10:16 | cmd |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:11:17:11:22 | #{...} |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:13:9:13:14 | #{...} |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:29:19:29:24 | #{...} |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" |
| CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" |
| CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" |
nodes
| CommandInjection.rb:6:15:6:20 | call to params : | semmle.label | call to params : |
| CommandInjection.rb:7:10:7:15 | #{...} | semmle.label | #{...} |
| CommandInjection.rb:8:16:8:18 | cmd | semmle.label | cmd |
| CommandInjection.rb:10:14:10:16 | cmd | semmle.label | cmd |
| CommandInjection.rb:11:17:11:22 | #{...} | semmle.label | #{...} |
| CommandInjection.rb:13:9:13:14 | #{...} | semmle.label | #{...} |
| CommandInjection.rb:29:19:29:24 | #{...} | semmle.label | #{...} |
| CommandInjection.rb:33:24:33:36 | "echo #{...}" | semmle.label | "echo #{...}" |
| CommandInjection.rb:34:39:34:51 | "grep #{...}" | semmle.label | "grep #{...}" |
| CommandInjection.rb:46:15:46:20 | call to params : | semmle.label | call to params : |
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | semmle.label | "echo #{...}" |
subpaths
#select
| CommandInjection.rb:7:10:7:15 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:7:10:7:15 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:8:16:8:18 | cmd | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:8:16:8:18 | cmd | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:10:14:10:16 | cmd | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:10:14:10:16 | cmd | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:11:17:11:22 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:11:17:11:22 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:13:9:13:14 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:13:9:13:14 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:29:19:29:24 | #{...} | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:29:19:29:24 | #{...} | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:33:24:33:36 | "echo #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:33:24:33:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:34:39:34:51 | "grep #{...}" | CommandInjection.rb:6:15:6:20 | call to params : | CommandInjection.rb:34:39:34:51 | "grep #{...}" | This command depends on $@. | CommandInjection.rb:6:15:6:20 | call to params | a user-provided value |
| CommandInjection.rb:50:24:50:36 | "echo #{...}" | CommandInjection.rb:46:15:46:20 | call to params : | CommandInjection.rb:50:24:50:36 | "echo #{...}" | This command depends on $@. | CommandInjection.rb:46:15:46:20 | call to params | a user-provided value |

View File

@@ -0,0 +1 @@
queries/security/cwe-078/CommandInjection.ql

View File

@@ -0,0 +1,52 @@
require "shellwords"
require "open3"
class UsersController < ActionController::Base
def create
cmd = params[:cmd]
`#{cmd}`
system(cmd)
system("echo", cmd) # OK, because cmd is not shell interpreted
exec(cmd)
%x(echo #{cmd})
result = <<`EOF`
#{cmd}
EOF
safe_cmd_1 = Shellwords.escape(cmd)
`echo #{safe_cmd_1}`
safe_cmd_2 = Shellwords.shellescape(cmd)
`echo #{safe_cmd_2}`
if cmd == "some constant"
`echo #{cmd}`
end
if %w(foo bar).include? cmd
`echo #{cmd}`
else
`echo #{cmd}`
end
# Open3 methods
Open3.capture2("echo #{cmd}")
Open3.pipeline("cat foo.txt", "grep #{cmd}")
Open3.pipeline(["echo", cmd], "tail") # OK, because cmd is not shell interpreted
end
def show
`ls`
system("ls")
exec("ls")
%x(ls)
end
def index
cmd = params[:key]
if %w(foo bar).include? cmd
`echo #{cmd}`
end
Open3.capture2("echo #{cmd}")
end
end