зеркало из https://github.com/github/codeql.git
Merge pull request #4354 from RasmusWL/python-command-execution-modeling
Python: Better command execution modeling
This commit is contained in:
Коммит
fce76e2799
|
@ -27,7 +27,34 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
|
|||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(SystemCommandExecution e).getCommand()
|
||||
sink = any(SystemCommandExecution e).getCommand() and
|
||||
// Since the implementation of standard library functions such `os.popen` looks like
|
||||
// ```py
|
||||
// def popen(cmd, mode="r", buffering=-1):
|
||||
// ...
|
||||
// proc = subprocess.Popen(cmd, ...)
|
||||
// ```
|
||||
// any time we would report flow to the `os.popen` sink, we can ALSO report the flow
|
||||
// from the `cmd` parameter to the `subprocess.Popen` sink -- obviously we don't
|
||||
// want that.
|
||||
//
|
||||
// However, simply removing taint edges out of a sink is not a good enough solution,
|
||||
// since we would only flag one of the `os.system` calls in the following example
|
||||
// due to use-use flow
|
||||
// ```py
|
||||
// os.system(cmd)
|
||||
// os.system(cmd)
|
||||
// ```
|
||||
//
|
||||
// Best solution I could come up with is to exclude all sinks inside the `os` and
|
||||
// `subprocess` modules. This does have a downside: If we have overlooked a function
|
||||
// in any of these, that internally runs a command, we no longer give an alert :|
|
||||
//
|
||||
// This does not only affect `os.popen`, but also the helper functions in
|
||||
// `subprocess`. See:
|
||||
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/os.py#L974
|
||||
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
|
||||
not sink.getScope().getEnclosingModule().getName() in ["os", "subprocess"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,8 +11,11 @@ private import experimental.semmle.python.Concepts
|
|||
|
||||
/** Provides models for the Python standard library. */
|
||||
private module Stdlib {
|
||||
// ---------------------------------------------------------------------------
|
||||
// os
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `os` module. */
|
||||
DataFlow::Node os(DataFlow::TypeTracker t) {
|
||||
private DataFlow::Node os(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importModule("os")
|
||||
or
|
||||
|
@ -22,53 +25,60 @@ private module Stdlib {
|
|||
/** Gets a reference to the `os` module. */
|
||||
DataFlow::Node os() { result = os(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `os` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*
|
||||
* For example, using `attr_name = "system"` will get all uses of `os.system`.
|
||||
*/
|
||||
private DataFlow::Node os_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["system", "popen",
|
||||
// exec
|
||||
"execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe",
|
||||
// spawn
|
||||
"spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp", "spawnvpe",
|
||||
"posix_spawn", "posix_spawnp",
|
||||
// modules
|
||||
"path"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importMember("os", attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = DataFlow::importModule("os")
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `os_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
os_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate os_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(os_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `os` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*
|
||||
* For example, using `"system"` will get all uses of `os.system`.
|
||||
*/
|
||||
private DataFlow::Node os_attr(string attr_name) {
|
||||
result = os_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/** Provides models for the `os` module. */
|
||||
module os {
|
||||
/** Gets a reference to the `os.system` function. */
|
||||
DataFlow::Node system(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importMember("os", "system")
|
||||
or
|
||||
t.startInAttr("system") and
|
||||
result = os()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = os::system(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `os.system` function. */
|
||||
DataFlow::Node system() { result = os::system(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a reference to the `os.popen` function. */
|
||||
DataFlow::Node popen(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importMember("os", "popen")
|
||||
or
|
||||
t.startInAttr("popen") and
|
||||
result = os()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = os::popen(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `os.popen` function. */
|
||||
DataFlow::Node popen() { result = os::popen(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a reference to the `os.path` module. */
|
||||
private DataFlow::Node path(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result = DataFlow::importMember("os", "path")
|
||||
or
|
||||
result = DataFlow::importModule("os.path")
|
||||
)
|
||||
or
|
||||
t.startInAttr("path") and
|
||||
result = os()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = path(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `os.path` module. */
|
||||
DataFlow::Node path() { result = path(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node path() { result = os_attr("path") }
|
||||
|
||||
/** Provides models for the `os.path` module */
|
||||
module path {
|
||||
|
@ -83,7 +93,7 @@ private module Stdlib {
|
|||
exists(DataFlow::TypeTracker t2 | result = join(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `os.join` module. */
|
||||
/** Gets a reference to the `os.path.join` function. */
|
||||
DataFlow::Node join() { result = join(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +103,7 @@ private module Stdlib {
|
|||
* See https://docs.python.org/3/library/os.html#os.system
|
||||
*/
|
||||
private class OsSystemCall extends SystemCommandExecution::Range {
|
||||
OsSystemCall() { this.asCfgNode().(CallNode).getFunction() = os::system().asCfgNode() }
|
||||
OsSystemCall() { this.asCfgNode().(CallNode).getFunction() = os_attr("system").asCfgNode() }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = this.asCfgNode().(CallNode).getArg(0)
|
||||
|
@ -105,7 +115,57 @@ private module Stdlib {
|
|||
* See https://docs.python.org/3/library/os.html#os.popen
|
||||
*/
|
||||
private class OsPopenCall extends SystemCommandExecution::Range {
|
||||
OsPopenCall() { this.asCfgNode().(CallNode).getFunction() = os::popen().asCfgNode() }
|
||||
OsPopenCall() { this.asCfgNode().(CallNode).getFunction() = os_attr("popen").asCfgNode() }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = this.asCfgNode().(CallNode).getArg(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the `os.exec*` functions
|
||||
* See https://docs.python.org/3.8/library/os.html#os.execl
|
||||
*/
|
||||
private class OsExecCall extends SystemCommandExecution::Range {
|
||||
OsExecCall() {
|
||||
exists(string name |
|
||||
name in ["execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe"] and
|
||||
this.asCfgNode().(CallNode).getFunction() = os_attr(name).asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = this.asCfgNode().(CallNode).getArg(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the `os.spawn*` functions
|
||||
* See https://docs.python.org/3.8/library/os.html#os.spawnl
|
||||
*/
|
||||
private class OsSpawnCall extends SystemCommandExecution::Range {
|
||||
OsSpawnCall() {
|
||||
exists(string name |
|
||||
name in ["spawnl", "spawnle", "spawnlp", "spawnlpe", "spawnv", "spawnve", "spawnvp",
|
||||
"spawnvpe"] and
|
||||
this.asCfgNode().(CallNode).getFunction() = os_attr(name).asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = this.asCfgNode().(CallNode).getArg(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to any of the `os.posix_spawn*` functions
|
||||
* See https://docs.python.org/3.8/library/os.html#os.posix_spawn
|
||||
*/
|
||||
private class OsPosixSpawnCall extends SystemCommandExecution::Range {
|
||||
OsPosixSpawnCall() {
|
||||
this.asCfgNode().(CallNode).getFunction() =
|
||||
os_attr(["posix_spawn", "posix_spawnp"]).asCfgNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = this.asCfgNode().(CallNode).getArg(0)
|
||||
|
@ -123,4 +183,148 @@ private module Stdlib {
|
|||
// TODO: Handle pathlib (like we do for os.path.join)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// subprocess
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `subprocess` module. */
|
||||
private DataFlow::Node subprocess(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importModule("subprocess")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subprocess(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `subprocess` module. */
|
||||
DataFlow::Node subprocess() { result = subprocess(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `subprocess` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*
|
||||
* For example, using `attr_name = "Popen"` will get all uses of `subprocess.Popen`.
|
||||
*/
|
||||
private DataFlow::Node subprocess_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["Popen", "call", "check_call", "check_output", "run"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importMember("subprocess", attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = DataFlow::importModule("subprocess")
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `subprocess_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
subprocess_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate subprocess_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(subprocess_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `subprocess` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*
|
||||
* For example, using `attr_name = "Popen"` will get all uses of `subprocess.Popen`.
|
||||
*/
|
||||
private DataFlow::Node subprocess_attr(string attr_name) {
|
||||
result = subprocess_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `subprocess.Popen` or helper functions (call, check_call, check_output, run)
|
||||
* See https://docs.python.org/3.8/library/subprocess.html#subprocess.Popen
|
||||
*/
|
||||
private class SubprocessPopenCall extends SystemCommandExecution::Range {
|
||||
CallNode call;
|
||||
|
||||
SubprocessPopenCall() {
|
||||
call = this.asCfgNode() and
|
||||
exists(string name |
|
||||
name in ["Popen", "call", "check_call", "check_output", "run"] and
|
||||
call.getFunction() = subprocess_attr(name).asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the ControlFlowNode for the `args` argument, if any. */
|
||||
private ControlFlowNode get_args_arg() {
|
||||
result = call.getArg(0)
|
||||
or
|
||||
result = call.getArgByName("args")
|
||||
}
|
||||
|
||||
/** Gets the ControlFlowNode for the `shell` argument, if any. */
|
||||
private ControlFlowNode get_shell_arg() {
|
||||
result = call.getArg(8)
|
||||
or
|
||||
result = call.getArgByName("shell")
|
||||
}
|
||||
|
||||
private boolean get_shell_arg_value() {
|
||||
not exists(this.get_shell_arg()) and
|
||||
result = false
|
||||
or
|
||||
exists(ControlFlowNode shell_arg | shell_arg = this.get_shell_arg() |
|
||||
result = shell_arg.getNode().(ImmutableLiteral).booleanValue()
|
||||
or
|
||||
// TODO: Track the "shell" argument to determine possible values
|
||||
not shell_arg.getNode() instanceof ImmutableLiteral and
|
||||
(
|
||||
result = true
|
||||
or
|
||||
result = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the ControlFlowNode for the `executable` argument, if any. */
|
||||
private ControlFlowNode get_executable_arg() {
|
||||
result = call.getArg(2)
|
||||
or
|
||||
result = call.getArgByName("executable")
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
// TODO: Track arguments ("args" and "shell")
|
||||
// TODO: Handle using `args=["sh", "-c", <user-input>]`
|
||||
result.asCfgNode() = this.get_executable_arg()
|
||||
or
|
||||
exists(ControlFlowNode arg_args, boolean shell |
|
||||
arg_args = get_args_arg() and
|
||||
shell = get_shell_arg_value()
|
||||
|
|
||||
// When "executable" argument is set, and "shell" argument is `False`, the
|
||||
// "args" argument will only be used to set the program name and arguments to
|
||||
// the program, so we should not consider any of them as command execution.
|
||||
not (
|
||||
exists(this.get_executable_arg()) and
|
||||
shell = false
|
||||
) and
|
||||
(
|
||||
// When the "args" argument is an iterable, first element is the command to
|
||||
// run, so if we're able to, we only mark the first element as the command
|
||||
// (and not the arguments to the command).
|
||||
//
|
||||
result.asCfgNode() = arg_args.(SequenceNode).getElement(0)
|
||||
or
|
||||
// Either the "args" argument is not a sequence (which is valid) or we where
|
||||
// just not able to figure it out. Simply mark the "args" argument as the
|
||||
// command.
|
||||
//
|
||||
not arg_args instanceof SequenceNode and
|
||||
result.asCfgNode() = arg_args
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
import python
|
||||
import experimental.meta.ConceptsTest
|
|
@ -0,0 +1,141 @@
|
|||
# Note: Some of these commands will technically not allow an attacker to execute
|
||||
# arbitrary system commands, but only specify the program to be executed. The general
|
||||
# consensus was that even this is still a high security risk, so we also treat them as
|
||||
# system command executions.
|
||||
#
|
||||
# As an example, executing `subprocess.Popen(["rm -rf /"])` will result in
|
||||
# `FileNotFoundError: [Errno 2] No such file or directory: 'rm -rf /'`
|
||||
|
||||
########################################
|
||||
|
||||
|
||||
import os
|
||||
|
||||
# can't use a string literal with spaces in the tags of an InlineExpectationsTest, so using variables :|
|
||||
|
||||
os.popen("cmd1; cmd2") # $getCommand="cmd1; cmd2"
|
||||
os.system("cmd1; cmd2") # $getCommand="cmd1; cmd2"
|
||||
|
||||
|
||||
def os_members():
|
||||
# hmm, it's kinda annoying to check that we handle this import correctly for
|
||||
# everything. It's quite useful since I messed it up initially and didn't have a
|
||||
# test for it, but in the long run it's just cumbersome to duplicate all the tests
|
||||
# :|
|
||||
from os import popen, system
|
||||
|
||||
popen("cmd1; cmd2") # $getCommand="cmd1; cmd2"
|
||||
system("cmd1; cmd2") # $getCommand="cmd1; cmd2"
|
||||
|
||||
|
||||
########################################
|
||||
# https://docs.python.org/3.8/library/os.html#os.execl
|
||||
#
|
||||
# VS Code extension will ignore rest of program if encountering one of these, which we
|
||||
# don't want. We could use `if False`, but just to be 100% sure we don't do anything too
|
||||
# clever in our analysis that discards that code, I used `if UNKNOWN` instead
|
||||
if UNKNOWN:
|
||||
env = {"FOO": "foo"}
|
||||
os.execl("executable", "<progname>", "arg0") # $getCommand="executable"
|
||||
os.execle("executable", "<progname>", "arg0", env) # $getCommand="executable"
|
||||
os.execlp("executable", "<progname>", "arg0") # $getCommand="executable"
|
||||
os.execlpe("executable", "<progname>", "arg0", env) # $getCommand="executable"
|
||||
os.execv("executable", ["<progname>", "arg0"]) # $getCommand="executable"
|
||||
os.execve("executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
os.execvp("executable", ["<progname>", "arg0"]) # $getCommand="executable"
|
||||
os.execvpe("executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
|
||||
|
||||
########################################
|
||||
# https://docs.python.org/3.8/library/os.html#os.spawnl
|
||||
env = {"FOO": "foo"}
|
||||
os.spawnl(os.P_WAIT, "executable", "<progname>", "arg0") # $getCommand="executable"
|
||||
os.spawnle(os.P_WAIT, "executable", "<progname>", "arg0", env) # $getCommand="executable"
|
||||
os.spawnlp(os.P_WAIT, "executable", "<progname>", "arg0") # $getCommand="executable"
|
||||
os.spawnlpe(os.P_WAIT, "executable", "<progname>", "arg0", env) # $getCommand="executable"
|
||||
os.spawnv(os.P_WAIT, "executable", ["<progname>", "arg0"]) # $getCommand="executable"
|
||||
os.spawnve(os.P_WAIT, "executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
os.spawnvp(os.P_WAIT, "executable", ["<progname>", "arg0"]) # $getCommand="executable"
|
||||
os.spawnvpe(os.P_WAIT, "executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
|
||||
# Added in Python 3.8
|
||||
os.posix_spawn("executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
os.posix_spawnp("executable", ["<progname>", "arg0"], env) # $getCommand="executable"
|
||||
|
||||
########################################
|
||||
|
||||
import subprocess
|
||||
|
||||
subprocess.Popen("cmd1; cmd2", shell=True) # $getCommand="cmd1; cmd2"
|
||||
subprocess.Popen("cmd1; cmd2", shell="truthy string") # $getCommand="cmd1; cmd2"
|
||||
subprocess.Popen(["cmd1; cmd2", "shell-arg"], shell=True) # $getCommand="cmd1; cmd2"
|
||||
subprocess.Popen("cmd1; cmd2", shell=True, executable="/bin/bash") # $getCommand="cmd1; cmd2" $getCommand="/bin/bash"
|
||||
|
||||
subprocess.Popen("executable") # $getCommand="executable"
|
||||
subprocess.Popen(["executable", "arg0"]) # $getCommand="executable"
|
||||
subprocess.Popen("<progname>", executable="executable") # $getCommand="executable"
|
||||
subprocess.Popen(["<progname>", "arg0"], executable="executable") # $getCommand="executable"
|
||||
|
||||
# call/check_call/check_output/run all work like Popen from a command execution point of view
|
||||
subprocess.call(["executable", "arg0"]) # $getCommand="executable"
|
||||
subprocess.check_call(["executable", "arg0"]) # $getCommand="executable"
|
||||
subprocess.check_output(["executable", "arg0"]) # $getCommand="executable"
|
||||
subprocess.run(["executable", "arg0"]) # $getCommand="executable"
|
||||
|
||||
|
||||
########################################
|
||||
# actively using known shell as the executable
|
||||
|
||||
subprocess.Popen(["/bin/sh", "-c", "vuln"]) # $getCommand="/bin/sh" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["/bin/bash", "-c", "vuln"]) # $getCommand="/bin/bash" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["/bin/dash", "-c", "vuln"]) # $getCommand="/bin/dash" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["/bin/zsh", "-c", "vuln"]) # $getCommand="/bin/zsh" $f-:getCommand="vuln"
|
||||
|
||||
subprocess.Popen(["sh", "-c", "vuln"]) # $getCommand="sh" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["bash", "-c", "vuln"]) # $getCommand="bash" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["dash", "-c", "vuln"]) # $getCommand="dash" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["zsh", "-c", "vuln"]) # $getCommand="zsh" $f-:getCommand="vuln"
|
||||
|
||||
# Check that we don't consider ANY argument a command injection sink
|
||||
subprocess.Popen(["sh", "/bin/python"]) # $getCommand="sh"
|
||||
|
||||
subprocess.Popen(["cmd.exe", "/c", "vuln"]) # $getCommand="cmd.exe" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["cmd.exe", "/C", "vuln"]) # $getCommand="cmd.exe" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["cmd", "/c", "vuln"]) # $getCommand="cmd" $f-:getCommand="vuln"
|
||||
subprocess.Popen(["cmd", "/C", "vuln"]) # $getCommand="cmd" $f-:getCommand="vuln"
|
||||
|
||||
subprocess.Popen(["<progname>", "-c", "vuln"], executable="/bin/bash") # $getCommand="/bin/bash" $f-:getCommand="vuln"
|
||||
|
||||
if UNKNOWN:
|
||||
os.execl("/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" $f-:getCommand="vuln"
|
||||
|
||||
os.spawnl(os.P_WAIT, "/bin/sh", "<progname>", "-c", "vuln") # $getCommand="/bin/sh" $f-:getCommand="vuln"
|
||||
|
||||
|
||||
########################################
|
||||
# Passing arguments by reference
|
||||
|
||||
args = ["/bin/sh", "-c", "vuln"]
|
||||
subprocess.Popen(args) # $getCommand=args
|
||||
|
||||
args = "<progname>"
|
||||
use_shell = False
|
||||
exe = "executable"
|
||||
subprocess.Popen(args, shell=use_shell, executable=exe) # $f+:getCommand=args $getCommand=exe
|
||||
|
||||
|
||||
################################################################################
|
||||
# Taint related
|
||||
|
||||
import shlex
|
||||
|
||||
cmd = shlex.join(["echo", tainted])
|
||||
args = shlex.split(tainted)
|
||||
|
||||
# will handle tainted = 'foo; rm -rf /'
|
||||
safe_cmd = "ls {}".format(shlex.quote(tainted))
|
||||
|
||||
# not how you are supposed to use shlex.quote
|
||||
wrong_use = shlex.quote("ls {}".format(tainted))
|
||||
# still dangerous, for example
|
||||
cmd = "sh -c " + wrong_use
|
|
@ -0,0 +1,34 @@
|
|||
import python
|
||||
import experimental.dataflow.DataFlow
|
||||
import experimental.semmle.python.Concepts
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
string value_from_expr(Expr e) {
|
||||
// TODO: This one is starting to look like `repr` predicate from TestTaintLib
|
||||
result =
|
||||
e.(StrConst).getPrefix() + e.(StrConst).getText() +
|
||||
e.(StrConst).getPrefix().regexpReplaceAll("[a-zA-Z]+", "")
|
||||
or
|
||||
result = e.(Name).getId()
|
||||
or
|
||||
not e instanceof StrConst and
|
||||
not e instanceof Name and
|
||||
result = e.toString()
|
||||
}
|
||||
|
||||
class SystemCommandExecutionTest extends InlineExpectationsTest {
|
||||
SystemCommandExecutionTest() { this = "SystemCommandExecutionTest" }
|
||||
|
||||
override string getARelevantTag() { result = "getCommand" }
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
exists(SystemCommandExecution sce, DataFlow::Node command |
|
||||
exists(location.getFile().getRelativePath()) and
|
||||
command = sce.getCommand() and
|
||||
location = command.getLocation() and
|
||||
element = command.toString() and
|
||||
value = value_from_expr(command.asExpr()) and
|
||||
tag = "getCommand"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,42 @@
|
|||
edges
|
||||
| command_injection.py:10:13:10:24 | ControlFlowNode for Attribute | command_injection.py:12:15:12:27 | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:17:13:17:24 | ControlFlowNode for Attribute | command_injection.py:19:22:19:34 | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:24:11:24:22 | ControlFlowNode for Attribute | command_injection.py:25:23:25:25 | ControlFlowNode for cmd |
|
||||
| command_injection.py:30:13:30:24 | ControlFlowNode for Attribute | command_injection.py:32:14:32:26 | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | command_injection.py:39:15:39:21 | ControlFlowNode for command |
|
||||
| command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | command_injection.py:40:15:40:21 | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:53:15:53:21 | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:54:14:54:20 | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:55:21:55:27 | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:56:27:56:33 | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:57:20:57:26 | ControlFlowNode for command |
|
||||
nodes
|
||||
| command_injection.py:10:13:10:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:12:15:12:27 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:17:13:17:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:19:22:19:34 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:24:11:24:22 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:25:23:25:25 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
|
||||
| command_injection.py:30:13:30:24 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:32:14:32:26 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
|
||||
| command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:39:15:39:21 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:40:15:40:21 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| command_injection.py:53:15:53:21 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:54:14:54:20 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:55:21:55:27 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:56:27:56:33 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
| command_injection.py:57:20:57:26 | ControlFlowNode for command | semmle.label | ControlFlowNode for command |
|
||||
#select
|
||||
| command_injection.py:12:15:12:27 | ControlFlowNode for BinaryExpr | command_injection.py:10:13:10:24 | ControlFlowNode for Attribute | command_injection.py:12:15:12:27 | ControlFlowNode for BinaryExpr | This command depends on $@. | command_injection.py:10:13:10:24 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:19:22:19:34 | ControlFlowNode for BinaryExpr | command_injection.py:17:13:17:24 | ControlFlowNode for Attribute | command_injection.py:19:22:19:34 | ControlFlowNode for BinaryExpr | This command depends on $@. | command_injection.py:17:13:17:24 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:25:23:25:25 | ControlFlowNode for cmd | command_injection.py:24:11:24:22 | ControlFlowNode for Attribute | command_injection.py:25:23:25:25 | ControlFlowNode for cmd | This command depends on $@. | command_injection.py:24:11:24:22 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:32:14:32:26 | ControlFlowNode for BinaryExpr | command_injection.py:30:13:30:24 | ControlFlowNode for Attribute | command_injection.py:32:14:32:26 | ControlFlowNode for BinaryExpr | This command depends on $@. | command_injection.py:30:13:30:24 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:39:15:39:21 | ControlFlowNode for command | command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | command_injection.py:39:15:39:21 | ControlFlowNode for command | This command depends on $@. | command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:40:15:40:21 | ControlFlowNode for command | command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | command_injection.py:40:15:40:21 | ControlFlowNode for command | This command depends on $@. | command_injection.py:36:15:36:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:53:15:53:21 | ControlFlowNode for command | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:53:15:53:21 | ControlFlowNode for command | This command depends on $@. | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:54:14:54:20 | ControlFlowNode for command | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:54:14:54:20 | ControlFlowNode for command | This command depends on $@. | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:55:21:55:27 | ControlFlowNode for command | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:55:21:55:27 | ControlFlowNode for command | This command depends on $@. | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:56:27:56:33 | ControlFlowNode for command | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:56:27:56:33 | ControlFlowNode for command | This command depends on $@. | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
| command_injection.py:57:20:57:26 | ControlFlowNode for command | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | command_injection.py:57:20:57:26 | ControlFlowNode for command | This command depends on $@. | command_injection.py:52:15:52:26 | ControlFlowNode for Attribute | a user-provided value |
|
||||
|
|
|
@ -16,7 +16,7 @@ def command_injection1():
|
|||
def command_injection2():
|
||||
files = request.args.get('files', '')
|
||||
# Don't let files be `; rm -rf /`
|
||||
subprocess.Popen(["ls", files], shell = True)
|
||||
subprocess.Popen("ls " + files, shell=True)
|
||||
|
||||
|
||||
@app.route("/command3")
|
||||
|
@ -31,5 +31,31 @@ def others():
|
|||
# Don't let files be `; rm -rf /`
|
||||
os.popen("ls " + files)
|
||||
|
||||
@app.route("/multiple")
|
||||
def multiple():
|
||||
command = request.args.get('command', '')
|
||||
# We should mark flow to both calls here, which conflicts with removing flow out of
|
||||
# a sink due to use-use flow.
|
||||
os.system(command)
|
||||
os.system(command)
|
||||
|
||||
|
||||
@app.route("/not-into-sink-impl")
|
||||
def not_into_sink_impl():
|
||||
"""When there is flow to a sink such as `os.popen(cmd)`, we don't want to highlight that there is also
|
||||
flow through the actual `popen` function to the internal call to `subprocess.Popen` -- we would usually
|
||||
see that flow since we extract the `os.py` file from the standard library.
|
||||
|
||||
os.popen implementation: https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/os.py#L974
|
||||
subprocess.call implementation: https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
|
||||
"""
|
||||
command = request.args.get('command', '')
|
||||
os.system(command)
|
||||
os.popen(command)
|
||||
subprocess.call(command)
|
||||
subprocess.check_call(command)
|
||||
subprocess.run(command)
|
||||
|
||||
|
||||
# TODO: popen2 module for Python 2 only https://devdocs.io/python~2.7/library/popen2
|
||||
# (deprecated since Python 2.6, but still functional in Python 2.7.17)
|
||||
|
|
|
@ -7,10 +7,10 @@ edges
|
|||
| command_injection.py:12:23:12:27 | externally controlled string | command_injection.py:12:15:12:27 | externally controlled string |
|
||||
| command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:17:13:17:41 | externally controlled string |
|
||||
| command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:17:13:17:41 | externally controlled string |
|
||||
| command_injection.py:17:13:17:41 | externally controlled string | command_injection.py:19:29:19:33 | externally controlled string |
|
||||
| command_injection.py:17:13:17:41 | externally controlled string | command_injection.py:19:29:19:33 | externally controlled string |
|
||||
| command_injection.py:19:29:19:33 | externally controlled string | command_injection.py:19:22:19:34 | sequence of externally controlled string |
|
||||
| command_injection.py:19:29:19:33 | externally controlled string | command_injection.py:19:22:19:34 | sequence of externally controlled string |
|
||||
| command_injection.py:17:13:17:41 | externally controlled string | command_injection.py:19:30:19:34 | externally controlled string |
|
||||
| command_injection.py:17:13:17:41 | externally controlled string | command_injection.py:19:30:19:34 | externally controlled string |
|
||||
| command_injection.py:19:30:19:34 | externally controlled string | command_injection.py:19:22:19:34 | externally controlled string |
|
||||
| command_injection.py:19:30:19:34 | externally controlled string | command_injection.py:19:22:19:34 | externally controlled string |
|
||||
| command_injection.py:24:11:24:22 | dict of externally controlled string | command_injection.py:24:11:24:37 | externally controlled string |
|
||||
| command_injection.py:24:11:24:22 | dict of externally controlled string | command_injection.py:24:11:24:37 | externally controlled string |
|
||||
| command_injection.py:24:11:24:37 | externally controlled string | command_injection.py:25:23:25:25 | externally controlled string |
|
||||
|
@ -25,6 +25,6 @@ edges
|
|||
| command_injection.py:32:22:32:26 | externally controlled string | command_injection.py:32:14:32:26 | externally controlled string |
|
||||
#select
|
||||
| command_injection.py:12:15:12:27 | BinaryExpr | command_injection.py:10:13:10:24 | dict of externally controlled string | command_injection.py:12:15:12:27 | externally controlled string | This command depends on $@. | command_injection.py:10:13:10:24 | Attribute | a user-provided value |
|
||||
| command_injection.py:19:22:19:34 | List | command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:19:22:19:34 | sequence of externally controlled string | This command depends on $@. | command_injection.py:17:13:17:24 | Attribute | a user-provided value |
|
||||
| command_injection.py:19:22:19:34 | BinaryExpr | command_injection.py:17:13:17:24 | dict of externally controlled string | command_injection.py:19:22:19:34 | externally controlled string | This command depends on $@. | command_injection.py:17:13:17:24 | Attribute | a user-provided value |
|
||||
| command_injection.py:25:22:25:36 | List | command_injection.py:24:11:24:22 | dict of externally controlled string | command_injection.py:25:22:25:36 | first item in sequence of externally controlled string | This command depends on $@. | command_injection.py:24:11:24:22 | Attribute | a user-provided value |
|
||||
| command_injection.py:32:14:32:26 | BinaryExpr | command_injection.py:30:13:30:24 | dict of externally controlled string | command_injection.py:32:14:32:26 | externally controlled string | This command depends on $@. | command_injection.py:30:13:30:24 | Attribute | a user-provided value |
|
||||
|
|
|
@ -16,7 +16,7 @@ def command_injection1():
|
|||
def command_injection2():
|
||||
files = request.args.get('files', '')
|
||||
# Don't let files be `; rm -rf /`
|
||||
subprocess.Popen(["ls", files], shell = True)
|
||||
subprocess.Popen("ls " + files, shell=True)
|
||||
|
||||
|
||||
@app.route("/command3")
|
||||
|
|
Загрузка…
Ссылка в новой задаче