mirror of
https://github.com/github/codeql.git
synced 2026-04-26 17:25:19 +02:00
Python: Model fabric/invoke command injection sinks
This commit is contained in:
@@ -123,3 +123,107 @@ class OsCommandFirstArgument extends CommandSink {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- //
|
||||
// Modeling of the 'invoke' package and 'fabric' package (v 2.x)
|
||||
//
|
||||
// Since fabric build so closely upon invoke, we model them together to avoid
|
||||
// duplication
|
||||
// -------------------------------------------------------------------------- //
|
||||
|
||||
/** A taint sink that is potentially vulnerable to malicious shell commands.
|
||||
* The `vuln` in `invoke.run(vuln, ...)` and similar calls.
|
||||
*/
|
||||
class InvokeRun extends CommandSink {
|
||||
InvokeRun() {
|
||||
this = Value::named("invoke.run").(FunctionValue).getArgumentForCall(_, 0)
|
||||
or
|
||||
this = Value::named("invoke.sudo").(FunctionValue).getArgumentForCall(_, 0)
|
||||
}
|
||||
|
||||
override string toString() { result = "InvokeRun" }
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof ExternalStringKind
|
||||
}
|
||||
}
|
||||
|
||||
/** Internal TaintKind to track the invoke.Context instance passed to functions
|
||||
* marked with @invoke.task
|
||||
*/
|
||||
private class InvokeContextArg extends TaintKind {
|
||||
InvokeContextArg() { this = "InvokeContextArg" }
|
||||
}
|
||||
|
||||
/** Internal TaintSource to track the context passed to functions marked with @invoke.task */
|
||||
private class InvokeContextArgSource extends TaintSource {
|
||||
InvokeContextArgSource() {
|
||||
exists(Function f, Expr decorator |
|
||||
count(f.getADecorator()) = 1 and
|
||||
(
|
||||
decorator = f.getADecorator() and not decorator instanceof Call
|
||||
or
|
||||
decorator = f.getADecorator().(Call).getFunc()
|
||||
|
||||
) and
|
||||
(
|
||||
decorator.pointsTo(Value::named("invoke.task"))
|
||||
or
|
||||
decorator.pointsTo(Value::named("fabric.task"))
|
||||
)
|
||||
|
|
||||
this.(ControlFlowNode).getNode() = f.getArg(0)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof InvokeContextArg
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint sink that is potentially vulnerable to malicious shell commands.
|
||||
* The `vuln` in `invoke.Context().run(vuln, ...)` and similar calls.
|
||||
*/
|
||||
class InvokeContextRun extends CommandSink {
|
||||
InvokeContextRun() {
|
||||
exists(CallNode call |
|
||||
any(InvokeContextArg k).taints(call.getFunction().(AttrNode).getObject("run"))
|
||||
or
|
||||
call = Value::named("invoke.Context").(ClassValue).lookup("run").getACall()
|
||||
or
|
||||
// fabric.connection.Connection is a subtype of invoke.context.Context
|
||||
// since fabric.Connection.run has a decorator, it doesn't work with FunctionValue :|
|
||||
// and `Value::named("fabric.Connection").(ClassValue).lookup("run").getACall()` returned no results,
|
||||
// so here is the hacky solution that works :\
|
||||
call.getFunction().(AttrNode).getObject("run").pointsTo().getClass() = Value::named("fabric.Connection")
|
||||
|
|
||||
this = call.getArg(0)
|
||||
or
|
||||
this = call.getArgByName("command")
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() { result = "InvokeContextRun" }
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof ExternalStringKind
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint sink that is potentially vulnerable to malicious shell commands.
|
||||
* The `vuln` in `fabric.Group().run(vuln, ...)` and similar calls.
|
||||
*/
|
||||
class FabricGroupRun extends CommandSink {
|
||||
FabricGroupRun() {
|
||||
exists(ClassValue cls |
|
||||
cls.getASuperType() = Value::named("fabric.Group") and
|
||||
this = cls.lookup("run").(FunctionValue).getArgumentForCall(_, 1)
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() { result = "FabricGroupRun" }
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof ExternalStringKind
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
| fabric_test.py:10:16:10:25 | InvokeContextRun | externally controlled string |
|
||||
| fabric_test.py:12:15:12:36 | InvokeContextRun | externally controlled string |
|
||||
| fabric_test.py:16:45:16:54 | FabricGroupRun | externally controlled string |
|
||||
| fabric_test.py:21:10:21:13 | FabricGroupRun | externally controlled string |
|
||||
| fabric_test.py:31:14:31:41 | InvokeContextRun | externally controlled string |
|
||||
| fabric_test.py:33:15:33:64 | InvokeContextRun | externally controlled string |
|
||||
| invoke_test.py:8:12:8:21 | InvokeRun | externally controlled string |
|
||||
| invoke_test.py:9:20:9:40 | InvokeRun | externally controlled string |
|
||||
| invoke_test.py:12:17:12:24 | InvokeRun | externally controlled string |
|
||||
| invoke_test.py:13:25:13:32 | InvokeRun | externally controlled string |
|
||||
| invoke_test.py:17:11:17:40 | InvokeContextRun | externally controlled string |
|
||||
| invoke_test.py:21:11:21:32 | InvokeContextRun | externally controlled string |
|
||||
| invoke_test.py:27:11:27:25 | InvokeContextRun | externally controlled string |
|
||||
| invoke_test.py:32:11:32:25 | InvokeContextRun | externally controlled string |
|
||||
@@ -0,0 +1,7 @@
|
||||
import python
|
||||
import semmle.python.security.injection.Command
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from CommandSink sink, TaintKind kind
|
||||
where sink.sinks(kind)
|
||||
select sink, kind
|
||||
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2020 Jeff Forcier.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,33 @@
|
||||
"""tests for the 'fabric' package (v2.x)
|
||||
|
||||
Most of these examples are taken from the fabric documentation: http://docs.fabfile.org/en/2.5/getting-started.html
|
||||
See fabric-LICENSE for its' license.
|
||||
"""
|
||||
|
||||
from fabric import Connection
|
||||
|
||||
c = Connection('web1')
|
||||
result = c.run('uname -s')
|
||||
|
||||
c.run(command='echo run with kwargs')
|
||||
|
||||
|
||||
from fabric import SerialGroup as Group
|
||||
results = Group('web1', 'web2', 'mac1').run('uname -s')
|
||||
|
||||
|
||||
from fabric import SerialGroup as Group
|
||||
pool = Group('web1', 'web2', 'web3')
|
||||
pool.run('ls')
|
||||
|
||||
|
||||
|
||||
# using the 'fab' command-line tool
|
||||
|
||||
from fabric import task
|
||||
|
||||
@task
|
||||
def upload_and_unpack(c):
|
||||
if c.run('test -f /opt/mydata/myfile', warn=True).failed:
|
||||
c.put('myfiles.tgz', '/opt/mydata')
|
||||
c.run('tar -C /opt/mydata -xzvf /opt/mydata/myfiles.tgz')
|
||||
@@ -0,0 +1,32 @@
|
||||
"""tests for the 'invoke' package
|
||||
|
||||
see https://www.pyinvoke.org/
|
||||
"""
|
||||
|
||||
import invoke
|
||||
|
||||
invoke.run('echo run')
|
||||
invoke.run(command='echo run with kwarg')
|
||||
|
||||
def with_sudo():
|
||||
invoke.sudo('whoami')
|
||||
invoke.sudo(command='whoami')
|
||||
|
||||
def manual_context():
|
||||
c = invoke.Context()
|
||||
c.run('echo run from manual context')
|
||||
manual_context()
|
||||
|
||||
def foo_helper(c):
|
||||
c.run('echo from foo_helper')
|
||||
|
||||
# for use with the 'invoke' command-line tool
|
||||
@invoke.task
|
||||
def foo(c):
|
||||
# 'c' is a invoke.context.Context
|
||||
c.run('echo task foo')
|
||||
foo_helper(c)
|
||||
|
||||
@invoke.task()
|
||||
def bar(c):
|
||||
c.run('echo task bar')
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/
|
||||
@@ -0,0 +1,3 @@
|
||||
from .connection import Connection
|
||||
from .group import Group, SerialGroup, ThreadingGroup
|
||||
from .tasks import task
|
||||
15
python/ql/test/query-tests/Security/lib/fabric/connection.py
Normal file
15
python/ql/test/query-tests/Security/lib/fabric/connection.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from invoke import Context
|
||||
|
||||
@decorator
|
||||
def opens(method, self, *args, **kwargs):
|
||||
self.open()
|
||||
return method(self, *args, **kwargs)
|
||||
|
||||
class Connection(Context):
|
||||
|
||||
def open(self):
|
||||
pass
|
||||
|
||||
@opens
|
||||
def run(self, command, **kwargs):
|
||||
pass
|
||||
11
python/ql/test/query-tests/Security/lib/fabric/group.py
Normal file
11
python/ql/test/query-tests/Security/lib/fabric/group.py
Normal file
@@ -0,0 +1,11 @@
|
||||
class Group(list):
|
||||
def run(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
class SerialGroup(Group):
|
||||
def run(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
class ThreadingGroup(Group):
|
||||
def run(self, *args, **kwargs):
|
||||
pass
|
||||
2
python/ql/test/query-tests/Security/lib/fabric/tasks.py
Normal file
2
python/ql/test/query-tests/Security/lib/fabric/tasks.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def task(*args, **kwargs):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
from .context import Context
|
||||
from .tasks import task
|
||||
|
||||
def run(command, **kwargs):
|
||||
pass
|
||||
|
||||
def sudo(command, **kwargs):
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
class Context(object):
|
||||
def run(self, command, **kwargs):
|
||||
pass
|
||||
2
python/ql/test/query-tests/Security/lib/invoke/tasks.py
Normal file
2
python/ql/test/query-tests/Security/lib/invoke/tasks.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def task(*args, **kwargs):
|
||||
pass
|
||||
Reference in New Issue
Block a user