mirror of
https://github.com/github/codeql.git
synced 2026-04-24 16:25:15 +02:00
Merge pull request #280 from github/hmac-cli-injection
Add CLI Injection query
This commit is contained in:
@@ -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() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
245
ql/lib/codeql/ruby/frameworks/StandardLibrary.qll
Normal file
245
ql/lib/codeql/ruby/frameworks/StandardLibrary.qll
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
32
ql/lib/codeql/ruby/security/CommandInjectionQuery.qll
Normal file
32
ql/lib/codeql/ruby/security/CommandInjectionQuery.qll
Normal 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
|
||||
}
|
||||
}
|
||||
44
ql/src/queries/security/cwe-078/CommandInjection.qhelp
Normal file
44
ql/src/queries/security/cwe-078/CommandInjection.qhelp
Normal 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>
|
||||
25
ql/src/queries/security/cwe-078/CommandInjection.ql
Normal file
25
ql/src/queries/security/cwe-078/CommandInjection.ql
Normal 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()
|
||||
@@ -0,0 +1,6 @@
|
||||
class UsersController < ActionController::Base
|
||||
def create
|
||||
command = params[:command]
|
||||
system(command) # BAD
|
||||
end
|
||||
end
|
||||
90
ql/test/library-tests/frameworks/CommandExecution.rb
Normal file
90
ql/test/library-tests/frameworks/CommandExecution.rb
Normal 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")
|
||||
60
ql/test/library-tests/frameworks/StandardLibrary.expected
Normal file
60
ql/test/library-tests/frameworks/StandardLibrary.expected
Normal 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 |
|
||||
15
ql/test/library-tests/frameworks/StandardLibrary.ql
Normal file
15
ql/test/library-tests/frameworks/StandardLibrary.ql
Normal 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() }
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
queries/security/cwe-078/CommandInjection.ql
|
||||
52
ql/test/query-tests/security/cwe-078/CommandInjection.rb
Normal file
52
ql/test/query-tests/security/cwe-078/CommandInjection.rb
Normal 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
|
||||
Reference in New Issue
Block a user