Merge remote-tracking branch 'upstream/main' into 'rc/3.14'

This commit is contained in:
Arthur Baars
2024-06-28 19:50:35 +02:00
772 changed files with 16846 additions and 17035 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* A number of Python queries now support sinks defined using data extensions. The format of data extensions for Python has been documented.

View File

@@ -35,6 +35,7 @@ private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke
private import semmle.python.frameworks.Jmespath
private import semmle.python.frameworks.Joblib
private import semmle.python.frameworks.JsonPickle
private import semmle.python.frameworks.Ldap
private import semmle.python.frameworks.Ldap3
private import semmle.python.frameworks.Libtaxii
@@ -48,7 +49,9 @@ private import semmle.python.frameworks.Numpy
private import semmle.python.frameworks.Opml
private import semmle.python.frameworks.Oracledb
private import semmle.python.frameworks.Pandas
private import semmle.python.frameworks.Paramiko
private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Pexpect
private import semmle.python.frameworks.Phoenixdb
private import semmle.python.frameworks.Psycopg
private import semmle.python.frameworks.Psycopg2
@@ -71,6 +74,7 @@ private import semmle.python.frameworks.SqlAlchemy
private import semmle.python.frameworks.Starlette
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.Toml
private import semmle.python.frameworks.Torch
private import semmle.python.frameworks.Tornado
private import semmle.python.frameworks.Twisted
private import semmle.python.frameworks.Ujson

View File

@@ -564,7 +564,6 @@ predicate neverSkipInPathGraph(Node n) {
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
* a node of type `t1` to a node of type `t2`.
*/
pragma[inline]
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
@@ -576,8 +575,7 @@ predicate localMustFlowStep(Node nodeFrom, Node nodeTo) { none() }
*/
DataFlowType getNodeType(Node node) {
result = TAnyFlow() and
// Suppress unused variable warning
node = node
exists(node)
}
/** Gets a string representation of a type returned by `getErasedRepr`. */

View File

@@ -29,8 +29,8 @@ private module FabricV1 {
// -------------------------------------------------------------------------
// fabric.api
// -------------------------------------------------------------------------
/** Gets a reference to the `fabric.api` module. */
API::Node api() { result = fabric().getMember("api") }
/** Gets a reference to the `fabric.api` module. Also known as `fabric.operations` */
API::Node api() { result = fabric().getMember(["api", "operations"]) }
/** Provides models for the `fabric.api` module */
module Api {
@@ -71,7 +71,7 @@ private module FabricV1 {
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
* version 2.x.
*
* See http://docs.fabfile.org/en/2.5/getting-st arted.html.
* See http://docs.fabfile.org/en/2.5/getting-started.html.
*/
module FabricV2 {
/** Gets a reference to the `fabric` module. */
@@ -93,7 +93,9 @@ module FabricV2 {
* See https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.
*/
module ConnectionClass {
/** Gets a reference to the `fabric.connection.Connection` class. */
/**
* Gets a reference to the `fabric.connection.Connection` class.
*/
API::Node classRef() {
result = fabric().getMember("Connection")
or
@@ -109,40 +111,16 @@ module FabricV2 {
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Connection::instance()` to get references to instances of `fabric.connection.Connection`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
abstract class Instance extends API::Node {
override string toString() { result = this.(API::Node).toString() }
}
/** Gets a reference to an instance of `fabric.connection.Connection`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `fabric.connection.Connection`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Gets a reference to either `run`, `sudo`, or `local` method on a
* `fabric.connection.Connection` instance.
*
* See
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.run
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.sudo
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
* A reference to the `fabric.connection.Connection` class.
*/
private DataFlow::TypeTrackingNode instanceRunMethods(DataFlow::TypeTracker t) {
t.startInAttr(["run", "sudo", "local"]) and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = instanceRunMethods(t2).track(t2, t))
class ClassInstantiation extends Instance {
ClassInstantiation() { this = classRef().getReturn() }
}
/**
@@ -154,8 +132,8 @@ module FabricV2 {
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.sudo
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
*/
DataFlow::Node instanceRunMethods() {
instanceRunMethods(DataFlow::TypeTracker::end()).flowsTo(result)
API::CallNode instanceRunMethods() {
result = any(Instance is).getMember(["run", "sudo", "local"]).getACall()
}
}
}
@@ -168,19 +146,38 @@ module FabricV2 {
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
*/
private class FabricConnectionRunSudoLocalCall extends SystemCommandExecution::Range,
DataFlow::CallCfgNode
API::CallNode
{
FabricConnectionRunSudoLocalCall() {
this.getFunction() = Fabric::Connection::ConnectionClass::instanceRunMethods()
this = Fabric::Connection::ConnectionClass::instanceRunMethods()
}
override DataFlow::Node getCommand() {
result = [this.getArg(0), this.getArgByName("command")]
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
}
/**
* A `gateway` parameter of `fabric.connection.Connection` instance is considered as ssh proxy_command option and can execute command.
* See https://docs.fabfile.org/en/latest/api/connection.html#fabric.connection.Connection
*/
private class FabricConnectionProxyCommand extends SystemCommandExecution::Range, API::CallNode {
FabricConnectionProxyCommand() {
this = Fabric::Connection::ConnectionClass::classRef().getACall() and
// we want to make sure that the connection is established otherwise the command of proxy_command won't run.
exists(
this.getAMethodCall([
"run", "get", "sudo", "open_gateway", "open", "create_session", "forward_local",
"forward_remote", "put", "shell", "sftp"
])
)
}
override DataFlow::Node getCommand() { result = this.getParameter(4, "gateway").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
// -------------------------------------------------------------------------
// fabric.tasks
// -------------------------------------------------------------------------
@@ -193,14 +190,10 @@ module FabricV2 {
API::Node task() { result in [tasks().getMember("task"), fabric().getMember("task")] }
}
class FabricTaskFirstParamConnectionInstance extends Fabric::Connection::ConnectionClass::InstanceSource,
DataFlow::ParameterNode
class FabricTaskFirstParamConnectionInstance extends Fabric::Connection::ConnectionClass::Instance
{
FabricTaskFirstParamConnectionInstance() {
exists(Function func |
func.getADecorator() = Fabric::Tasks::task().getAValueReachableFromSource().asExpr() and
this.getParameter() = func.getArg(0)
)
this = Fabric::Tasks::task().getParameter(0).getParameter(0)
}
}
@@ -242,11 +235,14 @@ module FabricV2 {
API::Node subclassInstance() { result = any(ModeledSubclass m).getASubclass*().getReturn() }
/**
* Gets a reference to the `run` method on an instance of a subclass of `fabric.group.Group`.
* Gets a reference to the `run` and `sudo` methods on an instance of a subclass of `fabric.group.Group`.
*
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run
* See https://docs.fabfile.org/en/latest/api/group.html#fabric.group.Group.sudo
*/
API::Node subclassInstanceRunMethod() { result = subclassInstance().getMember("run") }
API::Node subclassInstanceRunMethod() {
result = subclassInstance().getMember(["run", "sudo"])
}
}
/**
@@ -254,14 +250,12 @@ module FabricV2 {
*
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run
*/
private class FabricGroupRunCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
private class FabricGroupRunCall extends SystemCommandExecution::Range, API::CallNode {
FabricGroupRunCall() {
this = Fabric::Group::GroupClass::subclassInstanceRunMethod().getACall()
}
override DataFlow::Node getCommand() {
result = [this.getArg(0), this.getArgByName("command")]
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
}

View File

@@ -0,0 +1,31 @@
/**
* Provides classes modeling security-relevant aspects of the `jsonpickle` PyPI package.
* See https://pypi.org/project/jsonpickle/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides models for the `jsonpickle` PyPI package.
* See https://pypi.org/project/jsonpickle/.
*/
private module Jsonpickle {
/**
* A Call to `jsonpickle.decode`.
* See https://jsonpickle.readthedocs.io/en/latest/api.html#jsonpickle.decode
*/
private class JsonpickleDecode extends Decoding::Range, API::CallNode {
JsonpickleDecode() { this = API::moduleImport("jsonpickle").getMember("decode").getACall() }
override predicate mayExecuteInput() { any() }
override DataFlow::Node getAnInput() { result = this.getParameter(0, "string").asSink() }
override DataFlow::Node getOutput() { result = this }
override string getFormat() { result = "pickle" }
}
}

View File

@@ -34,4 +34,121 @@ private module Pandas {
override string getFormat() { result = "pickle" }
}
/**
* Provides security related models for `pandas.DataFrame`.
* See https://pandas.pydata.org/docs/reference/frame.html
*/
module DataFrame {
/**
* A `pandas.DataFrame` Object.
*
* Extend this class to model new APIs.
* See https://pandas.pydata.org/docs/reference/frame.html
*/
abstract class DataFrame extends API::Node {
override string toString() { result = this.(API::Node).toString() }
}
/**
* A `pandas.DataFrame` instantiation.
* See https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html
*/
class DataFrameConstructor extends DataFrame {
DataFrameConstructor() {
this = API::moduleImport("pandas").getMember("DataFrame").getReturn()
}
}
/**
* The `pandas.read_*` functions that return a `pandas.DataFrame`.
* See https://pandas.pydata.org/docs/reference/io.html
*/
class InputRead extends DataFrame {
InputRead() {
this =
API::moduleImport("pandas")
.getMember([
"read_csv", "read_fwf", "read_pickle", "read_table", "read_clipboard",
"read_excel", "read_xml", "read_parquet", "read_orc", "read_spss",
"read_sql_table", "read_sql_query", "read_sql", "read_gbq", "read_stata"
])
.getReturn()
or
this = API::moduleImport("pandas").getMember("read_html").getReturn().getASubscript()
or
exists(API::Node readSas, API::CallNode readSasCall |
readSas = API::moduleImport("pandas").getMember("read_sas") and
this = readSas.getReturn() and
readSasCall = readSas.getACall()
|
// Returns DataFrame if iterator=False and chunksize=None, Also with default values it returns DataFrame.
(
not readSasCall.getParameter(5, "iterator").asSink().asExpr().(BooleanLiteral)
instanceof True
or
not exists(readSasCall.getParameter(5, "iterator").asSink())
) and
not exists(
readSasCall.getParameter(4, "chunksize").asSink().asExpr().(IntegerLiteral).getN()
)
)
}
}
/**
* The `pandas.DataFrame.*` methods that return a `pandas.DataFrame` object.
* See https://pandas.pydata.org/docs/reference/io.html
*/
class DataFrameMethods extends DataFrame {
DataFrameMethods() {
this =
any(DataFrame df)
.getMember([
"copy", "from_records", "from_dict", "from_spmatrix", "assign", "select_dtypes",
"set_flags", "astype", "infer_objects", "head", "xs", "get", "isin", "where",
"mask", "query", "add", "mul", "truediv", "mod", "pow", "dot", "radd", "rsub",
"rdiv", "rfloordiv", "rtruediv", "rpow", "lt", "gt", "le", "ne", "agg", "combine",
"apply", "aggregate", "transform", "all", "any", "clip", "corr", "cov", "cummax",
"cummin", "cumprod", "describe", "mode", "pct_change", "quantile", "rank",
"round", "sem", "add_prefix", "add_suffix", "at_time", "between_time", "drop",
"drop_duplicates", "filter", "first", "head", "idxmin", "last", "reindex",
"reindex_like", "reset_index", "sample", "set_axis", "tail", "take", "truncate",
"bfill", "dropna", "ffill", "fillna", "interpolate", "isna", "isnull", "notna",
"notnull", "pad", "replace", "droplevel", "pivot", "pivot_table",
"reorder_levels", "sort_values", "sort_index", "nlargest", "nsmallest",
"swaplevel", "stack", "unstack", "isnull", "notna", "notnull", "replace",
"droplevel", "pivot", "pivot_table", "reorder_levels", "sort_values",
"sort_index", "nlargest", "nsmallest", "swaplevel", "stack", "unstack", "melt",
"explode", "squeeze", "T", "transpose", "compare", "join", "from_spmatrix",
"shift", "asof", "merge", "from_dict", "tz_convert", "to_period", "asfreq",
"to_dense", "tz_localize", "box", "__dataframe__"
])
.getReturn()
}
}
}
/**
* A Call to `pandas.DataFrame.query` or `pandas.DataFrame.eval`.
* See https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html
* https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.eval.html
*/
class CodeExecutionCall extends CodeExecution::Range, API::CallNode {
CodeExecutionCall() {
this = any(DataFrame::DataFrame df).getMember(["query", "eval"]).getACall()
}
override DataFlow::Node getCode() { result = this.getParameter(0, "expr").asSink() }
}
/**
* A Call to `pandas.eval`.
* See https://pandas.pydata.org/docs/reference/api/pandas.eval.html
*/
class PandasEval extends CodeExecution::Range, API::CallNode {
PandasEval() { this = API::moduleImport("pandas").getMember("eval").getACall() }
override DataFlow::Node getCode() { result = this.getParameter(0, "expr").asSink() }
}
}

View File

@@ -0,0 +1,32 @@
/**
* Provides classes modeling security-relevant aspects of the `paramiko` PyPI package.
* See https://pypi.org/project/paramiko/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides models for the `paramiko` PyPI package.
* See https://pypi.org/project/paramiko/.
*/
private module Paramiko {
/**
* The first argument of `paramiko.ProxyCommand`.
*
* the `paramiko.ProxyCommand` is equivalent of `ssh -o ProxyCommand="CMD"`
* which runs `CMD` on the local system.
* See https://paramiko.pydata.org/docs/reference/api/paramiko.eval.html
*/
class ParamikoProxyCommand extends SystemCommandExecution::Range, API::CallNode {
ParamikoProxyCommand() {
this = API::moduleImport("paramiko").getMember("ProxyCommand").getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command_line").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
}

View File

@@ -0,0 +1,45 @@
/**
* Provides classes modeling security-relevant aspects of the `pexpect` PyPI package.
* See https://pypi.org/project/pexpect/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides models for the `pexpect` PyPI package.
* See https://pypi.org/project/pexpect/.
*/
private module Pexpect {
/**
* The calls to `pexpect.*` functions that execute commands
* See https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.spawn
* See https://pexpect.readthedocs.io/en/stable/api/pexpect.html#pexpect.run
*/
class PexpectCommandExec extends SystemCommandExecution::Range, API::CallNode {
PexpectCommandExec() {
this = API::moduleImport("pexpect").getMember(["run", "runu", "spawn", "spawnu"]).getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
/**
* A call to `pexpect.popen_spawn.PopenSpawn`
* See https://pexpect.readthedocs.io/en/stable/api/popen_spawn.html#pexpect.popen_spawn.PopenSpawn
*/
class PexpectPopenSpawn extends SystemCommandExecution::Range, API::CallNode {
PexpectPopenSpawn() {
this =
API::moduleImport("pexpect").getMember("popen_spawn").getMember("PopenSpawn").getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "cmd").asSink() }
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
}

View File

@@ -0,0 +1,72 @@
/**
* Provides classes modeling security-relevant aspects of the `torch` PyPI package.
* See https://pypi.org/project/torch/.
*/
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
/**
* Provides models for the `torch` PyPI package.
* See https://pypi.org/project/torch/.
*/
private module Torch {
/**
* A call to `torch.load`
* See https://pytorch.org/docs/stable/generated/torch.load.html#torch.load
*/
private class TorchLoadCall extends Decoding::Range, API::CallNode {
TorchLoadCall() { this = API::moduleImport("torch").getMember("load").getACall() }
override predicate mayExecuteInput() {
not exists(this.getParameter(2, "pickle_module").asSink()) or
this.getParameter(2, "pickle_module").asSink().asExpr() instanceof None
}
override DataFlow::Node getAnInput() { result = this.getParameter(0, "f").asSink() }
override DataFlow::Node getOutput() { result = this }
override string getFormat() { result = "pickle" }
}
/**
* A call to `torch.package.PackageImporter`
* See https://pytorch.org/docs/stable/package.html#torch.package.PackageImporter
*/
private class TorchPackageImporter extends Decoding::Range, API::CallNode {
TorchPackageImporter() {
this = API::moduleImport("torch").getMember("package").getMember("PackageImporter").getACall() and
exists(this.getAMethodCall("load_pickle"))
}
override predicate mayExecuteInput() { any() }
override DataFlow::Node getAnInput() {
result = this.getParameter(0, "file_or_buffer").asSink()
}
override DataFlow::Node getOutput() { result = this.getAMethodCall("load_pickle") }
override string getFormat() { result = "pickle" }
}
/**
* A call to `torch.jit.load`
* See https://pytorch.org/docs/stable/generated/torch.jit.load.html#torch.jit.load
*/
private class TorchJitLoad extends Decoding::Range, API::CallNode {
TorchJitLoad() {
this = API::moduleImport("torch").getMember("jit").getMember("load").getACall()
}
override predicate mayExecuteInput() { any() }
override DataFlow::Node getAnInput() { result = this.getParameter(0, "f").asSink() }
override DataFlow::Node getOutput() { result = this }
override string getFormat() { result = "pickle" }
}
}

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -43,6 +44,10 @@ module CodeInjection {
CodeExecutionAsSink() { this = any(CodeExecution e).getCode() }
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("code-injection").asSink() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -78,6 +79,10 @@ module CommandInjection {
}
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("command-injection").asSink() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -71,6 +72,10 @@ module LogInjection {
}
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("log-injection").asSink() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.ApiGraphs
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -137,4 +138,34 @@ module ServerSideRequestForgery {
)
}
}
/** A validation that a string does not contain certain characters, considered as a sanitizer. */
private class StringRestrictionSanitizerGuard extends Sanitizer {
StringRestrictionSanitizerGuard() {
this = DataFlow::BarrierGuard<stringRestriction/3>::getABarrierNode()
}
}
private predicate stringRestriction(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
exists(DataFlow::MethodCallNode call, DataFlow::Node strNode |
call.asCfgNode() = g and strNode.asCfgNode() = node
|
branch = true and
call.calls(strNode,
["isalnum", "isalpha", "isdecimal", "isdigit", "isidentifier", "isnumeric", "isspace"])
or
branch = true and
call = API::moduleImport("re").getMember(["match", "fullmatch"]).getACall() and
strNode = [call.getArg(1), call.getArgByName("string")]
or
branch = true and
call =
API::moduleImport("re")
.getMember("compile")
.getReturn()
.getMember(["match", "fullmatch"])
.getACall() and
strNode = [call.getArg(0), call.getArgByName("string")]
)
}
}

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -48,6 +49,10 @@ module UnsafeDeserialization {
}
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("unsafe-deserialization").asSink() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/

View File

@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.frameworks.data.ModelsAsData
/**
* Provides default sources, sinks and sanitizers for detecting
@@ -89,6 +90,10 @@ module UrlRedirect {
}
}
private class SinkFromModel extends Sink {
SinkFromModel() { this = ModelOutput::getASinkNode("url-redirection").asSink() }
}
/**
* The right side of a string-concat, considered as a sanitizer.
*/

View File

@@ -12,6 +12,29 @@
import python
import Variables.Definition
import semmle.python.ApiGraphs
private predicate is_pytest_fixture(Import imp, Variable name) {
exists(Alias a, API::Node pytest_fixture, API::Node decorator |
pytest_fixture = API::moduleImport("pytest").getMember("fixture") and
// The additional `.getReturn()` is to account for the difference between
// ```
// @pytest.fixture
// def foo():
// ...
// ```
// and
// ```
// @pytest.fixture(some, args, here)
// def foo():
// ...
// ```
decorator in [pytest_fixture, pytest_fixture.getReturn()] and
a = imp.getAName() and
a.getAsname().(Name).getVariable() = name and
a.getValue() = decorator.getReturn().getAValueReachableFromSource().asExpr()
)
}
predicate global_name_used(Module m, string name) {
exists(Name u, GlobalVariable v |
@@ -117,6 +140,7 @@ predicate unused_import(Import imp, Variable name) {
not all_not_understood(imp.getEnclosingModule()) and
not imported_module_used_in_doctest(imp) and
not imported_alias_used_in_typehint(imp, name) and
not is_pytest_fixture(imp, name) and
// Only consider import statements that actually point-to something (possibly an unknown module).
// If this is not the case, it's likely that the import statement never gets executed.
imp.getAName().getValue().pointsTo(_)

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Additional sanitizers have been added to the `py/full-ssrf` and `py/partial-ssrf` queries for methods that verify a string contains only a certain set of characters, such as `.isalnum()` as well as regular expression tests.

View File

@@ -1,17 +0,0 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
Processing an unvalidated user input can allow an attacker to inject arbitrary command in your local and remote servers when creating a ssh connection.
</p>
</overview>
<recommendation>
<p>
This vulnerability can be prevented by not allowing untrusted user input to be passed as ProxyCommand or exec_command.
</p>
</recommendation>
<example>
<p>In the example below, the ProxyCommand and exec_command are controlled by the user and hence leads to a vulnerability.</p>
<sample src="paramikoBad.py" />
</example>
</qhelp>

View File

@@ -1,59 +0,0 @@
/**
* @name RCE with user provided command with paramiko ssh client
* @description user provided command can lead to execute code on a external server that can be belong to other users or admins
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id py/paramiko-command-injection
* @tags security
* experimental
* external/cwe/cwe-074
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.ApiGraphs
private API::Node paramikoClient() {
result = API::moduleImport("paramiko").getMember("SSHClient").getReturn()
}
private module ParamikoConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
/**
* exec_command of `paramiko.SSHClient` class execute command on ssh target server
* the `paramiko.ProxyCommand` is equivalent of `ssh -o ProxyCommand="CMD"`
* and it run CMD on current system that running the ssh command
* the Sink related to proxy command is the `connect` method of `paramiko.SSHClient` class
*/
predicate isSink(DataFlow::Node sink) {
sink = paramikoClient().getMember("exec_command").getACall().getParameter(0, "command").asSink()
or
sink = paramikoClient().getMember("connect").getACall().getParameter(11, "sock").asSink()
}
/**
* this additional taint step help taint tracking to find the vulnerable `connect` method of `paramiko.SSHClient` class
*/
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(API::CallNode call |
call = API::moduleImport("paramiko").getMember("ProxyCommand").getACall() and
nodeFrom = call.getParameter(0, "command_line").asSink() and
nodeTo = call
)
}
}
/** Global taint-tracking for detecting "paramiko command injection" vulnerabilities. */
module ParamikoFlow = TaintTracking::Global<ParamikoConfig>;
import ParamikoFlow::PathGraph
from ParamikoFlow::PathNode source, ParamikoFlow::PathNode sink
where ParamikoFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
"a user-provided value"

View File

@@ -0,0 +1,22 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
Allowing users to execute arbitrary commands using an SSH connection on a remote server can lead to security issues unless you implement proper authorization.
</p>
<p>
Assume that you connect to a remote system via SSH connection from your main or local server that accepts user-controlled data and has interaction with users that you don't trust, passing these data to SSH API as a part of a command that will be executed on a secondary remote server can lead to security issues. You should consider proper authorization rules very carefully.
</p>
</overview>
<recommendation>
<p>
This vulnerability can be prevented by implementing proper authorization rules for untrusted user input that can be passed to your secondary servers.
</p>
</recommendation>
<example>
<p>In the example below, the exec_command is controlled by the user and hence leads to a vulnerability.</p>
<sample src="paramikoBad.py" />
<p>In the example below, the exec_command is controlled by the an Authorized user and hence it is safe.</p>
<sample src="paramikoGood.py" />
</example>
</qhelp>

View File

@@ -0,0 +1,21 @@
/**
* @name Command execution on a secondary remote server
* @description user provided command can lead to execute code on a external server that can be belong to other users or admins
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id py/paramiko-command-injection
* @tags security
* experimental
* external/cwe/cwe-074
*/
import python
import experimental.semmle.python.security.RemoteCommandExecution
import RemoteCommandExecutionFlow::PathGraph
from RemoteCommandExecutionFlow::PathNode source, RemoteCommandExecutionFlow::PathNode sink
where RemoteCommandExecutionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
"a user-provided value"

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env python
from flask import request, Flask
import paramiko
from paramiko import SSHClient
app = Flask(__name__)
paramiko_ssh_client = SSHClient()
paramiko_ssh_client.load_system_host_keys()
paramiko_ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
paramiko_ssh_client.connect(hostname="127.0.0.1", port="22", username="ssh_user_name", pkey="k", timeout=11, banner_timeout=200)
@app.route('/external_exec_command_1')
def withoutAuthorization():
user_cmd = request.args.get('command')
stdin, stdout, stderr = paramiko_ssh_client.exec_command(user_cmd)
return stdout
if __name__ == '__main__':
app.debug = False
app.run()

View File

@@ -12,23 +12,14 @@ paramiko_ssh_client.connect(hostname="127.0.0.1", port="22", username="ssh_user_
@app.route('/external_exec_command_1')
def bad1():
def withAuthorization():
user_cmd = request.args.get('command')
auth_jwt = request.args.get('Auth')
# validating jwt token first
# .... then continue to run the command
stdin, stdout, stderr = paramiko_ssh_client.exec_command(user_cmd)
return stdout
@app.route('/external_exec_command_2')
def bad2():
user_cmd = request.args.get('command')
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=user_cmd)
return stdout
@app.route('/proxycommand')
def bad2():
user_cmd = request.args.get('command')
stdin, stdout, stderr = paramiko_ssh_client.connect('hostname', username='user',password='yourpassword',sock=paramiko.ProxyCommand(user_cmd))
return stdout
if __name__ == '__main__':
app.debug = False

View File

@@ -15,6 +15,39 @@ private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
private import semmle.python.Concepts
/**
* A data-flow node that executes an operating system command,
* on a remote server likely by SSH connections.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `RemoteCommandExecution::Range` instead.
*/
class RemoteCommandExecution extends DataFlow::Node instanceof RemoteCommandExecution::Range {
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
/** Gets the argument that specifies the command to be executed. */
DataFlow::Node getCommand() { result = super.getCommand() }
}
/** Provides classes for modeling new remote server command execution APIs. */
module RemoteCommandExecution {
/**
* A data-flow node that executes an operating system command,
* on a remote server likely by SSH connections.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `RemoteCommandExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the command to be executed. */
abstract DataFlow::Node getCommand();
/** Holds if a shell interprets `arg`. */
predicate isShellInterpreted(DataFlow::Node arg) { none() }
}
}
/** Provides classes for modeling copying file related APIs. */
module CopyFile {
/**

View File

@@ -2,10 +2,16 @@
* Helper file that imports all framework modeling.
*/
private import experimental.semmle.python.frameworks.AsyncSsh
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.Flask
private import experimental.semmle.python.frameworks.Django
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.Netmiko
private import experimental.semmle.python.frameworks.Paramiko
private import experimental.semmle.python.frameworks.Pexpect
private import experimental.semmle.python.frameworks.Scrapli
private import experimental.semmle.python.frameworks.Twisted
private import experimental.semmle.python.frameworks.JWT
private import experimental.semmle.python.frameworks.Csv
private import experimental.semmle.python.libraries.PyJWT
@@ -14,5 +20,6 @@ private import experimental.semmle.python.libraries.Authlib
private import experimental.semmle.python.libraries.PythonJose
private import experimental.semmle.python.frameworks.CopyFile
private import experimental.semmle.python.frameworks.Sendgrid
private import experimental.semmle.python.frameworks.Ssh2
private import experimental.semmle.python.libraries.FlaskMail
private import experimental.semmle.python.libraries.SmtpLib

View File

@@ -0,0 +1,30 @@
/**
* Provides classes modeling security-relevant aspects of the `asyncssh` PyPI package.
* See https://pypi.org/project/asyncssh/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `asyncssh` PyPI package.
* See https://pypi.org/project/asyncssh/.
*/
private module Asyncssh {
/**
* Gets `asyncssh` package.
*/
private API::Node asyncssh() { result = API::moduleImport("asyncssh") }
/**
* A `run` method responsible for executing commands on remote secondary servers.
*/
class AsyncsshRun extends RemoteCommandExecution::Range, API::CallNode {
AsyncsshRun() { this = asyncssh().getMember("connect").getReturn().getMember("run").getACall() }
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
}
}

View File

@@ -0,0 +1,55 @@
/**
* Provides classes modeling security-relevant aspects of the `netmiko` PyPI package.
* See https://pypi.org/project/netmiko/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `netmiko` PyPI package.
* See https://pypi.org/project/netmiko/.
*/
private module Netmiko {
/**
* Gets `netmiko` package.
*/
private API::Node netmiko() { result = API::moduleImport("netmiko") }
/**
* Gets `netmiko.ConnectHandler` return value.
*/
private API::Node netmikoConnectHandler() {
result = netmiko().getMember("ConnectHandler").getReturn()
}
/**
* The `send_*` methods responsible for executing commands on remote secondary servers.
*/
class NetmikoSendCommand extends RemoteCommandExecution::Range, API::CallNode {
boolean isMultiline;
NetmikoSendCommand() {
this =
netmikoConnectHandler()
.getMember(["send_command", "send_command_expect", "send_command_timing"])
.getACall() and
isMultiline = false
or
this =
netmikoConnectHandler().getMember(["send_multiline", "send_multiline_timing"]).getACall() and
isMultiline = true
}
override DataFlow::Node getCommand() {
result = this.getParameter(0, "command_string").asSink() and
isMultiline = false
or
result = this.getParameter(0, "commands").asSink() and
isMultiline = true
}
}
}

View File

@@ -0,0 +1,35 @@
/**
* Provides classes modeling security-relevant aspects of the `paramiko` PyPI package.
* See https://pypi.org/project/paramiko/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `paramiko` PyPI package.
* See https://pypi.org/project/paramiko/.
*/
private module Paramiko {
/**
* Gets `paramiko` package.
*/
private API::Node paramiko() { result = API::moduleImport("paramiko") }
/**
* Gets `paramiko.SSHClient` return value.
*/
private API::Node paramikoClient() { result = paramiko().getMember("SSHClient").getReturn() }
/**
* The `exec_command` of `paramiko.SSHClient` class execute command on ssh target server
*/
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
ParamikoExecCommand() { this = paramikoClient().getMember("exec_command").getACall() }
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
}
}

View File

@@ -0,0 +1,33 @@
/**
* Provides classes modeling security-relevant aspects of the `pexpect` PyPI package.
* See https://pypi.org/project/pexpect/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `pexpect` PyPI package.
* See https://pypi.org/project/pexpect/.
*/
private module Pexpect {
/**
* The calls to `pexpect.pxssh.pxssh` functions that execute commands
* See https://pexpect.readthedocs.io/en/stable/api/pxssh.html
*/
class PexpectCommandExec extends RemoteCommandExecution::Range, API::CallNode {
PexpectCommandExec() {
this =
API::moduleImport("pexpect")
.getMember("pxssh")
.getMember("pxssh")
.getReturn()
.getMember(["send", "sendline"])
.getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "s").asSink() }
}
}

View File

@@ -0,0 +1,56 @@
/**
* Provides classes modeling security-relevant aspects of the `scrapli` PyPI package.
* See https://pypi.org/project/scrapli/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `scrapli` PyPI package.
* See https://pypi.org/project/scrapli/.
*/
private module Scrapli {
/**
* Gets `scrapli` package.
*/
private API::Node scrapli() { result = API::moduleImport("scrapli") }
/**
* Gets `scrapli.driver` package.
*/
private API::Node scrapliDriver() { result = scrapli().getMember("driver") }
/**
* Gets `scrapli.driver.core` package.
*/
private API::Node scrapliCore() { result = scrapliDriver().getMember("core") }
/**
* A `send_command` method responsible for executing commands on remote secondary servers.
*/
class ScrapliSendCommand extends RemoteCommandExecution::Range, API::CallNode {
ScrapliSendCommand() {
this =
scrapliCore()
.getMember([
"AsyncNXOSDriver", "AsyncJunosDriver", "AsyncEOSDriver", "AsyncIOSXEDriver",
"AsyncIOSXRDriver", "NXOSDriver", "JunosDriver", "EOSDriver", "IOSXEDriver",
"IOSXRDriver"
])
.getReturn()
.getMember("send_command")
.getACall()
or
this = scrapli().getMember("Scrapli").getReturn().getMember("send_command").getACall()
or
this =
scrapliDriver().getMember("GenericDriver").getReturn().getMember("send_command").getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
}
}

View File

@@ -0,0 +1,39 @@
/**
* Provides classes modeling security-relevant aspects of the `ssh2-python` PyPI package.
* See https://pypi.org/project/ssh2-python/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
/**
* Provides models for the `ssh2-python` PyPI package.
* See https://pypi.org/project/ssh2-python/.
*/
private module Ssh2 {
/**
* Gets `ssh2` package.
*/
private API::Node ssh2() { result = API::moduleImport("ssh2") }
/**
* Gets `ssh2.session.Session` return value.
*/
private API::Node ssh2Session() {
result = ssh2().getMember("session").getMember("Session").getReturn()
}
/**
* An `execute` method responsible for executing commands on remote secondary servers.
*/
class Ssh2Execute extends RemoteCommandExecution::Range, API::CallNode {
Ssh2Execute() {
this = ssh2Session().getMember("open_session").getReturn().getMember("execute").getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(0, "command").asSink() }
}
}

View File

@@ -0,0 +1,35 @@
/**
* Provides classes modeling security-relevant aspects of the `twisted` PyPI package.
* See https://twistedmatrix.com/.
*/
private import python
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
import experimental.semmle.python.Concepts
/**
* Provides models for the `twisted` PyPI package.
* See https://docs.twistedmatrix.com/en/stable/api/twisted.conch.endpoints.SSHCommandClientEndpoint.html
*/
private module Twisted {
/**
* The `newConnection` and `existingConnection` functions of `twisted.conch.endpoints.SSHCommandClientEndpoint` class execute command on ssh target server
*/
class ParamikoExecCommand extends RemoteCommandExecution::Range, API::CallNode {
ParamikoExecCommand() {
this =
API::moduleImport("twisted")
.getMember("conch")
.getMember("endpoints")
.getMember("SSHCommandClientEndpoint")
.getMember(["newConnection", "existingConnection"])
.getACall()
}
override DataFlow::Node getCommand() { result = this.getParameter(1, "command").asSink() }
}
}

View File

@@ -0,0 +1,16 @@
import python
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.internal.DataFlowPublic
import codeql.util.Unit
import experimental.semmle.python.Concepts
module RemoteCommandExecutionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink = any(RemoteCommandExecution rce).getCommand() }
}
/** Global taint-tracking for detecting "secondary server command injection" vulnerabilities. */
module RemoteCommandExecutionFlow = TaintTracking::Global<RemoteCommandExecutionConfig>;

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
from fastapi import FastAPI
import asyncssh
app = FastAPI()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("host", "port"))
session = Session()
session.handshake(sock)
session.userauth_password("user", "password")
@app.get("/bad1")
async def bad1(cmd: str):
async with asyncssh.connect('localhost') as conn:
result = await conn.run(cmd, check=True) # $ result=BAD getRemoteCommand=cmd
print(result.stdout, end='')
return {"success": "Dangerous"}

View File

@@ -0,0 +1,23 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.internal.DataFlowDispatch as DataFlowDispatch
import TestUtilities.InlineExpectationsTest
private import semmle.python.dataflow.new.internal.PrintNode
import experimental.semmle.python.Concepts
module SystemCommandExecutionTest implements TestSig {
string getARelevantTag() { result = "getRemoteCommand" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(RemoteCommandExecution sci, DataFlow::Node command |
command = sci.getCommand() and
location = command.getLocation() and
element = command.toString() and
value = prettyNodeForInlineTest(command) and
tag = "getRemoteCommand"
)
}
}
import MakeTest<SystemCommandExecutionTest>

View File

@@ -0,0 +1,3 @@
missingAnnotationOnSink
testFailures
failures

View File

@@ -0,0 +1,4 @@
import python
import TestUtilities.dataflow.DataflowQueryTest
import experimental.semmle.python.security.RemoteCommandExecution
import FromTaintTrackingConfig<RemoteCommandExecutionConfig>

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env python
from fastapi import FastAPI
from netmiko import ConnectHandler
app = FastAPI()
cisco_881 = {
'device_type': 'cisco_ios',
'host': '10.10.10.10',
'username': 'test',
'password': 'password',
'port': 8022, # optional, defaults to 22
'secret': 'secret', # optional, defaults to ''
}
@app.get("/bad1")
async def bad1(cmd: str):
net_connect = ConnectHandler(**cisco_881)
net_connect.send_command(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_command_expect(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_command_timing(command_string=cmd) # $ result=BAD getRemoteCommand=cmd
net_connect.send_multiline(commands=[[cmd, "expect"]]) # $ result=BAD getRemoteCommand=List
net_connect.send_multiline_timing(commands=cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
from fastapi import FastAPI
from pexpect import pxssh
ssh = pxssh.pxssh()
hostname = "localhost"
username = "username"
password = "password"
ssh.login(hostname, username, password)
app = FastAPI()
@app.get("/bad1")
async def bad1(cmd: str):
ssh.send(cmd) # $ result=BAD getRemoteCommand=cmd
ssh.prompt()
ssh.sendline(cmd) # $ result=BAD getRemoteCommand=cmd
ssh.prompt()
ssh.logout()
return {"success": stdout}

View File

@@ -0,0 +1,88 @@
edges
| AsyncSsh.py:15:16:15:18 | ControlFlowNode for cmd | AsyncSsh.py:17:33:17:35 | ControlFlowNode for cmd | provenance | |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:20:45:20:47 | ControlFlowNode for cmd | provenance | |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:21:52:21:54 | ControlFlowNode for cmd | provenance | |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:22:52:22:54 | ControlFlowNode for cmd | provenance | |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:23:41:23:57 | ControlFlowNode for List | provenance | |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:24:48:24:50 | ControlFlowNode for cmd | provenance | |
| Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | Pexpect.py:16:14:16:16 | ControlFlowNode for cmd | provenance | |
| Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | Pexpect.py:18:18:18:20 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:24:42:24:44 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:27:42:27:44 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:30:42:30:44 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:33:42:33:44 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:36:42:36:44 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:51:36:51:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:54:36:54:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:57:36:57:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:60:36:60:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:63:36:63:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:74:36:74:38 | ControlFlowNode for cmd | provenance | |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:84:36:84:38 | ControlFlowNode for cmd | provenance | |
| Twisted.py:13:16:13:18 | ControlFlowNode for cmd | Twisted.py:16:5:16:7 | ControlFlowNode for cmd | provenance | |
| Twisted.py:13:16:13:18 | ControlFlowNode for cmd | Twisted.py:24:9:24:11 | ControlFlowNode for cmd | provenance | |
| paramiko.py:15:16:15:18 | ControlFlowNode for cmd | paramiko.py:16:62:16:64 | ControlFlowNode for cmd | provenance | |
| paramiko.py:20:16:20:18 | ControlFlowNode for cmd | paramiko.py:21:70:21:72 | ControlFlowNode for cmd | provenance | |
| ssh2.py:15:16:15:18 | ControlFlowNode for cmd | ssh2.py:17:21:17:23 | ControlFlowNode for cmd | provenance | |
nodes
| AsyncSsh.py:15:16:15:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| AsyncSsh.py:17:33:17:35 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Netmiko.py:20:45:20:47 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Netmiko.py:21:52:21:54 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Netmiko.py:22:52:22:54 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Netmiko.py:23:41:23:57 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
| Netmiko.py:24:48:24:50 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Pexpect.py:16:14:16:16 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Pexpect.py:18:18:18:20 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:24:42:24:44 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:27:42:27:44 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:30:42:30:44 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:33:42:33:44 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:36:42:36:44 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:51:36:51:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:54:36:54:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:57:36:57:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:60:36:60:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:63:36:63:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:74:36:74:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Scrapli.py:84:36:84:38 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Twisted.py:13:16:13:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Twisted.py:16:5:16:7 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| Twisted.py:24:9:24:11 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:15:16:15:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:16:62:16:64 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:20:16:20:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| paramiko.py:21:70:21:72 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| ssh2.py:15:16:15:18 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
| ssh2.py:17:21:17:23 | ControlFlowNode for cmd | semmle.label | ControlFlowNode for cmd |
subpaths
#select
| AsyncSsh.py:17:33:17:35 | ControlFlowNode for cmd | AsyncSsh.py:15:16:15:18 | ControlFlowNode for cmd | AsyncSsh.py:17:33:17:35 | ControlFlowNode for cmd | This code execution depends on a $@. | AsyncSsh.py:15:16:15:18 | ControlFlowNode for cmd | a user-provided value |
| Netmiko.py:20:45:20:47 | ControlFlowNode for cmd | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:20:45:20:47 | ControlFlowNode for cmd | This code execution depends on a $@. | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | a user-provided value |
| Netmiko.py:21:52:21:54 | ControlFlowNode for cmd | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:21:52:21:54 | ControlFlowNode for cmd | This code execution depends on a $@. | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | a user-provided value |
| Netmiko.py:22:52:22:54 | ControlFlowNode for cmd | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:22:52:22:54 | ControlFlowNode for cmd | This code execution depends on a $@. | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | a user-provided value |
| Netmiko.py:23:41:23:57 | ControlFlowNode for List | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:23:41:23:57 | ControlFlowNode for List | This code execution depends on a $@. | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | a user-provided value |
| Netmiko.py:24:48:24:50 | ControlFlowNode for cmd | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | Netmiko.py:24:48:24:50 | ControlFlowNode for cmd | This code execution depends on a $@. | Netmiko.py:18:16:18:18 | ControlFlowNode for cmd | a user-provided value |
| Pexpect.py:16:14:16:16 | ControlFlowNode for cmd | Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | Pexpect.py:16:14:16:16 | ControlFlowNode for cmd | This code execution depends on a $@. | Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | a user-provided value |
| Pexpect.py:18:18:18:20 | ControlFlowNode for cmd | Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | Pexpect.py:18:18:18:20 | ControlFlowNode for cmd | This code execution depends on a $@. | Pexpect.py:15:16:15:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:24:42:24:44 | ControlFlowNode for cmd | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:24:42:24:44 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:27:42:27:44 | ControlFlowNode for cmd | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:27:42:27:44 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:30:42:30:44 | ControlFlowNode for cmd | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:30:42:30:44 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:33:42:33:44 | ControlFlowNode for cmd | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:33:42:33:44 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:36:42:36:44 | ControlFlowNode for cmd | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | Scrapli.py:36:42:36:44 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:51:36:51:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:51:36:51:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:54:36:54:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:54:36:54:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:57:36:57:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:57:36:57:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:60:36:60:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:60:36:60:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:63:36:63:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:63:36:63:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:74:36:74:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:74:36:74:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Scrapli.py:84:36:84:38 | ControlFlowNode for cmd | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | Scrapli.py:84:36:84:38 | ControlFlowNode for cmd | This code execution depends on a $@. | Scrapli.py:40:10:40:12 | ControlFlowNode for cmd | a user-provided value |
| Twisted.py:16:5:16:7 | ControlFlowNode for cmd | Twisted.py:13:16:13:18 | ControlFlowNode for cmd | Twisted.py:16:5:16:7 | ControlFlowNode for cmd | This code execution depends on a $@. | Twisted.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| Twisted.py:24:9:24:11 | ControlFlowNode for cmd | Twisted.py:13:16:13:18 | ControlFlowNode for cmd | Twisted.py:24:9:24:11 | ControlFlowNode for cmd | This code execution depends on a $@. | Twisted.py:13:16:13:18 | ControlFlowNode for cmd | a user-provided value |
| paramiko.py:16:62:16:64 | ControlFlowNode for cmd | paramiko.py:15:16:15:18 | ControlFlowNode for cmd | paramiko.py:16:62:16:64 | ControlFlowNode for cmd | This code execution depends on a $@. | paramiko.py:15:16:15:18 | ControlFlowNode for cmd | a user-provided value |
| paramiko.py:21:70:21:72 | ControlFlowNode for cmd | paramiko.py:20:16:20:18 | ControlFlowNode for cmd | paramiko.py:21:70:21:72 | ControlFlowNode for cmd | This code execution depends on a $@. | paramiko.py:20:16:20:18 | ControlFlowNode for cmd | a user-provided value |
| ssh2.py:17:21:17:23 | ControlFlowNode for cmd | ssh2.py:15:16:15:18 | ControlFlowNode for cmd | ssh2.py:17:21:17:23 | ControlFlowNode for cmd | This code execution depends on a $@. | ssh2.py:15:16:15:18 | ControlFlowNode for cmd | a user-provided value |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-074/remoteCommandExecution/RemoteCommandExecution.ql

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python
from fastapi import FastAPI
from scrapli import Scrapli
from scrapli.driver.core import AsyncNXOSDriver, AsyncJunosDriver, AsyncEOSDriver, AsyncIOSXEDriver, AsyncIOSXRDriver
from scrapli.driver.core import NXOSDriver, JunosDriver, EOSDriver, IOSXEDriver, IOSXRDriver
from scrapli.driver import GenericDriver
app = FastAPI()
@app.get("/bad1")
async def bad1(cmd: str):
dev_connect = {
"host": host,
"auth_username": user,
"auth_password": password,
"port": port,
"auth_strict_key": False,
"transport": "asyncssh",
}
driver = AsyncIOSXEDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncIOSXRDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncNXOSDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncEOSDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = AsyncJunosDriver
async with driver(**dev_connect) as conn:
output = await conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}
@app.get("/bad1")
def bad2(cmd: str):
dev_connect = {
"host": host,
"auth_username": user,
"auth_password": password,
"port": port,
"auth_strict_key": False,
"transport": "ssh2",
}
driver = NXOSDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = IOSXRDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = IOSXEDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = EOSDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
driver = JunosDriver
with driver(**dev_connect) as conn:
output = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
dev_connect = {
"host": "65.65.65.65",
"auth_username": "root",
"auth_private_key": "keyPath",
"auth_strict_key": False,
"transport": "ssh2",
"platform": "cisco_iosxe",
}
with Scrapli(**dev_connect) as conn:
result = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
dev_connect = {
"host": "65.65.65.65",
"auth_username": "root",
"auth_password": "password",
"auth_strict_key": False,
"transport": "ssh2",
}
with GenericDriver(**dev_connect) as conn:
result = conn.send_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python
from fastapi import FastAPI
from twisted.conch.endpoints import SSHCommandClientEndpoint
from twisted.internet.protocol import Factory
from twisted.internet import reactor
app = FastAPI()
@app.get("/bad1")
async def bad1(cmd: bytes):
endpoint = SSHCommandClientEndpoint.newConnection(
reactor,
cmd, # $ result=BAD getRemoteCommand=cmd
b"username",
b"ssh.example.com",
22,
password=b"password")
SSHCommandClientEndpoint.existingConnection(
endpoint,
cmd) # $ result=BAD getRemoteCommand=cmd
factory = Factory()
d = endpoint.connect(factory)
d.addCallback(lambda protocol: protocol.finished)
return {"success": "Dangerous"}

View File

@@ -12,16 +12,11 @@ app = FastAPI()
@app.get("/bad1")
async def read_item(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(cmd)
return {"success": stdout}
async def bad1(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}
@app.get("/bad2")
async def read_item(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=cmd)
return {"success": "OK"}
@app.get("/bad3")
async def read_item(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.connect('hostname', username='user',password='yourpassword',sock=paramiko.ProxyCommand(cmd))
return {"success": "OK"}
async def bad2(cmd: str):
stdin, stdout, stderr = paramiko_ssh_client.exec_command(command=cmd) # $ result=BAD getRemoteCommand=cmd
return {"success": "Dangerous"}

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
from fastapi import FastAPI
from socket import socket
from ssh2.session import Session
app = FastAPI()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("host", "port"))
session = Session()
session.handshake(sock)
session.userauth_password("user", "password")
@app.get("/bad1")
async def bad1(cmd: str):
channel = session.open_session()
channel.execute(cmd) # $ result=BAD getRemoteCommand=cmd
channel.wait_eof()
channel.close()
channel.wait_closed()
return {"success": "Dangerous"}

View File

@@ -1 +0,0 @@
experimental/Security/CWE-074/paramiko/paramiko.ql

View File

@@ -25,3 +25,15 @@ extensions:
- ["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo.MS_Class", "Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["json", "Member[MS_loads]", "Argument[0]", "ReturnValue", "taint"]
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
- ["foo.MS_Class", "foo", "Member[get_instance].ReturnValue"]
- ["foo.MS_Class!", "foo", "Member[get_class].ReturnValue"]
# Ideally this would be a consequence of the above line
- ["foo.MS_Class", "foo", "Member[get_class].ReturnValue.Instance"]
- ["foo.MS_Class", "foo.MS_Factory!", "Member[get_instance].ReturnValue"]
- ["foo.MS_Class", "foo.MS_Factory", "Member[make].ReturnValue"]
- ["foo.MS_Class", "foo.Impl.MS_Class_Impl", ""]

View File

@@ -25,3 +25,15 @@ extensions:
- ["foo.MS_Class", "Member[instance_method]", "Argument[0]", "ReturnValue.TupleElement[1]", "value"]
- ["foo.MS_Class", "Member[explicit_self]", "Argument[self:]", "ReturnValue", "value"]
- ["json", "Member[MS_loads]", "Argument[0]", "ReturnValue", "taint"]
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
- ["foo.MS_Class", "foo", "Member[get_instance].ReturnValue"]
- ["foo.MS_Class!", "foo", "Member[get_class].ReturnValue"]
# Ideally this would be a consequence of the above line
- ["foo.MS_Class", "foo", "Member[get_class].ReturnValue.Instance"]
- ["foo.MS_Class", "foo.MS_Factory!", "Member[get_instance].ReturnValue"]
- ["foo.MS_Class", "foo.MS_Factory", "Member[make].ReturnValue"]
- ["foo.MS_Class", "foo.Impl.MS_Class_Impl", ""]

View File

@@ -30,7 +30,7 @@ def SINK_F(x):
ensure_tainted = ensure_not_tainted = print
TAINTED_STRING = "TAINTED_STRING"
from foo import MS_identity, MS_apply_lambda, MS_reversed, MS_list_map, MS_append_to_list, MS_spread, MS_spread_all
from foo import MS_identity, MS_apply_lambda, MS_reversed, MS_list_map, MS_append_to_list, MS_spread, MS_spread_all, Impl
# Simple summary
via_identity = MS_identity(SOURCE)
@@ -122,7 +122,7 @@ a, b = MS_spread_all(SOURCE)
SINK(a) # $ flow="SOURCE, l:-1 -> a"
SINK(b) # $ flow="SOURCE, l:-2 -> b"
from foo import MS_Class, MS_Class_transitive
from foo import MS_Class, MS_Class_transitive, get_instance, get_class, MS_Factory
# Class summaries
class_via_positional = MS_Class(SOURCE)
@@ -175,6 +175,24 @@ SINK_F(MS_Class.explicit_self(SOURCE))
# Instead, `Argument[self:]` refers to a keyword argument named `self` (which you are allowed to do in Python)
SINK(c.explicit_self(self = SOURCE)) # $ flow="SOURCE -> c.explicit_self(..)"
instance = get_instance()
SINK(instance.instance_method(SOURCE)[1]) # $ flow="SOURCE -> instance.instance_method(..)[1]"
returned_class = get_class()
SINK(returned_class(SOURCE).config) # $ flow="SOURCE -> returned_class(..).config"
SINK(returned_class().instance_method(SOURCE)[1]) # $flow="SOURCE -> returned_class().instance_method(..)[1]"
fatory_instance = MS_Factory.get_instance()
SINK(fatory_instance.instance_method(SOURCE)[1]) # $ flow="SOURCE -> fatory_instance.instance_method(..)[1]"
factory = MS_Factory()
SINK(factory.make().instance_method(SOURCE)[1]) # $ flow="SOURCE -> factory.make().instance_method(..)[1]"
also_instance = Impl.MS_Class_Impl()
SINK(also_instance.instance_method(SOURCE)[1]) # $ flow="SOURCE -> also_instance.instance_method(..)[1]"
# Modeled flow-summary is not value preserving
from json import MS_loads as json_loads

View File

@@ -1 +1 @@
ERROR: Could not resolve type DataFlow::Add (Test.ql:7,6-19)
ERROR: could not resolve type DataFlow::Add (Test.ql:7,6-19)

View File

@@ -12,3 +12,8 @@ sudo("cmd1; cmd2") # $getCommand="cmd1; cmd2"
local(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
run(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
sudo(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
from fabric import operations
operations.local("cmd1; cmd2") # $getCommand="cmd1; cmd2"
operations.local(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"

View File

@@ -5,7 +5,6 @@ Loosely inspired by http://docs.fabfile.org/en/2.5/getting-started.html
from fabric import connection, Connection, group, SerialGroup, ThreadingGroup, tasks, task
################################################################################
# Connection
################################################################################
@@ -22,6 +21,32 @@ c.sudo(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
c2 = connection.Connection("web2")
c2.run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
# ssh proxy_command command injection with gateway parameter,
# we need to call some of the following functions to run the proxy command
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.run(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.get("afs")
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.sudo(command="cmd1; cmd2") # $getCommand="cmd1; cmd2"
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.open_gateway()
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.open()
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.create_session()
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.forward_local("80")
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.forward_remote("80")
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.put(local="local")
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.shell()
c = Connection("web1", gateway="cmd") # $getCommand="cmd"
c.sftp()
# no call to desired methods so it is safe
c = Connection("web1", gateway="cmd")
################################################################################
# SerialGroup
@@ -30,11 +55,11 @@ results = SerialGroup("web1", "web2", "mac1").run("cmd1; cmd2") # $getCommand="
pool = SerialGroup("web1", "web2", "web3")
pool.run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
pool.sudo("cmd1; cmd2") # $getCommand="cmd1; cmd2"
# fully qualified usage
group.SerialGroup("web1", "web2", "mac1").run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
################################################################################
# ThreadingGroup
################################################################################
@@ -42,6 +67,7 @@ results = ThreadingGroup("web1", "web2", "mac1").run("cmd1; cmd2") # $getComman
pool = ThreadingGroup("web1", "web2", "web3")
pool.run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
pool.sudo("cmd1; cmd2") # $getCommand="cmd1; cmd2"
# fully qualified usage
group.ThreadingGroup("web1", "web2", "mac1").run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
@@ -57,6 +83,7 @@ def foo(c):
# 'c' is a fabric.connection.Connection
c.run("cmd1; cmd2") # $getCommand="cmd1; cmd2"
# fully qualified usage
@tasks.task
def bar(c):

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,15 @@
import os
import jsonpickle
class Thing(object):
def __reduce__(self):
return os.system, ("curl 127.0.0.1:1234",)
obj = Thing()
pickledObj = jsonpickle.encode(obj)
objUnPickled = jsonpickle.decode(pickledObj, safe=True) # $ decodeInput=pickledObj decodeOutput=jsonpickle.decode(..) decodeFormat=pickle decodeMayExecuteInput
print(objUnPickled.name)

View File

@@ -0,0 +1,85 @@
import pandas as pd
df = pd.DataFrame({'temp_c': [17.0, 25.0]}, index=['Portland', 'Berkeley'])
df.sample().query("query") # $getCode="query"
df.mod().query("query") # $getCode="query"
pd.eval("pythonExpr", target=df) # $getCode="pythonExpr"
df = pd.read_csv("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df.copy().query("query") # $getCode="query"
df = pd.read_fwf("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_pickle("filepath") # $ decodeInput="filepath" decodeOutput=pd.read_pickle(..) decodeFormat=pickle decodeMayExecuteInput
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_table("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_clipboard("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_excel("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_html("filepath")
df[0].query("query") # $getCode="query"
df = pd.read_xml("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_parquet("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_orc("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_spss("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_sql_table("filepath", 'postgres:///db_name')
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_sql_query("filepath", 'postgres:///db_name')
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_sql("filepath", 'postgres:///db_name')
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_gbq("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_stata("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_sas("filepath")
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"
df = pd.read_sas("filepath", iterator=True, chunksize=1)
df.query("query")
df = pd.read_sas("filepath", iterator=False, chunksize=1)
df.query("query")
df = pd.read_sas("filepath", iterator=True, chunksize=None)
df.query("query")
df = pd.read_sas("filepath", iterator=False, chunksize=None)
df.query("query") # $getCode="query"
df.eval("query") # $getCode="query"

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
import paramiko
from paramiko import SSHClient
paramiko_ssh_client = SSHClient()
paramiko_ssh_client.load_system_host_keys()
paramiko_ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
paramiko_ssh_client.connect(hostname="127.0.0.1", port="22", username="ssh_user_name", pkey="k", timeout=11, banner_timeout=200)
cmd = "cmd"
paramiko_ssh_client.connect('hostname', username='user', password='yourpassword', sock=paramiko.ProxyCommand(cmd)) # $getCommand=cmd

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,9 @@
import pexpect
from pexpect import popen_spawn
cmd = "ls -la"
result = pexpect.run(cmd) # $ getCommand=cmd
result = pexpect.runu(cmd) # $ getCommand=cmd
result = pexpect.spawn(cmd) # $ getCommand=cmd
result = pexpect.spawnu(cmd) # $ getCommand=cmd
result = popen_spawn.PopenSpawn(cmd) # $ getCommand=cmd

View File

@@ -0,0 +1,2 @@
testFailures
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,25 @@
from io import BytesIO
import torch
def someSafeMethod():
pass
PicklePayload = BytesIO(b"payload")
torch.load(PicklePayload) # $ decodeInput=PicklePayload decodeOutput=torch.load(..) decodeFormat=pickle decodeMayExecuteInput
torch.load(PicklePayload, pickle_module=None) # $ decodeInput=PicklePayload decodeOutput=torch.load(..) decodeFormat=pickle decodeMayExecuteInput
torch.load(PicklePayload, pickle_module=someSafeMethod()) # $ decodeInput=PicklePayload decodeOutput=torch.load(..) decodeFormat=pickle
from torch.package import PackageImporter
importer = PackageImporter(PicklePayload) # $ decodeInput=PicklePayload PackageImporter(..) decodeFormat=pickle decodeMayExecuteInput
my_tensor = importer.load_pickle("my_resources", "tensor.pkl") # $ decodeOutput=importer.load_pickle(..)
importer = PackageImporter(PicklePayload)
from torch import jit
jit.load(PicklePayload) # $ decodeInput=PicklePayload decodeOutput=jit.load(..) decodeFormat=pickle decodeMayExecuteInput

View File

@@ -5,3 +5,4 @@
| imports_test.py:10:1:10:22 | Import | Import of 'top_level_cycle' is not used. |
| imports_test.py:27:1:27:25 | Import | Import of 'func2' is not used. |
| imports_test.py:34:1:34:14 | Import | Import of 'module2' is not used. |
| imports_test.py:116:1:116:41 | Import | Import of 'not_a_fixture' is not used. |

View File

@@ -111,3 +111,8 @@ import subexpression_return_type
def baz() -> Optional['subexpression_return_type']:
pass
from pytest_fixtures import not_a_fixture # BAD
from pytest_fixtures import fixture, wrapped_fixture # GOOD (pytest fixtures are used implicitly by pytest)
from pytest_fixtures import session_fixture, wrapped_autouse_fixture # GOOD (pytest fixtures are used implicitly by pytest)

View File

@@ -0,0 +1,34 @@
import pytest
@pytest.fixture
def fixture():
pass
def fixture_wrapper():
@pytest.fixture
def delegate():
pass
return delegate
@fixture_wrapper
def wrapped_fixture():
pass
@pytest.fixture(scope='session')
def session_fixture():
pass
def not_a_fixture():
pass
def another_fixture_wrapper():
@pytest.fixture(autouse=True)
def delegate():
pass
return delegate
@another_fixture_wrapper
def wrapped_autouse_fixture():
pass

View File

@@ -1,7 +1,7 @@
from flask import request
import requests
import re
def full_ssrf():
user_input = request.args['untrusted_input']
@@ -120,3 +120,57 @@ def partial_ssrf_6():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # NOT OK -- user only controlled fragment
def partial_ssrf_7():
user_input = request.args['untrusted_input']
if user_input.isalnum():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphanumerical characters
if user_input.isalpha():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphabetical characters
if user_input.isdecimal():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain decimal characters
if user_input.isdigit():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain digits
if user_input.isnumeric():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain numeric characters
if user_input.isspace():
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain whitespace characters
if re.fullmatch(r'[a-zA-Z0-9]+', user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphanumerical characters
if re.fullmatch(r'.*[a-zA-Z0-9]+.*', user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # NOT OK, but NOT FOUND - user input can contain arbitrary characters
if re.match(r'^[a-zA-Z0-9]+$', user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphanumerical characters
if re.match(r'[a-zA-Z0-9]+', user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # NOT OK, but NOT FOUND - user input can contain arbitrary character as a suffix.
reg = re.compile(r'^[a-zA-Z0-9]+$')
if reg.match(user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphanumerical characters
if reg.fullmatch(user_input):
url = f"https://example.com/foo#{user_input}"
requests.get(url) # OK - user input can only contain alphanumerical characters