Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: New command execution sinks #15715

Merged
merged 50 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3d7db0e
add panas code execution sinks, add proper tests
am0o0 Feb 23, 2024
076faa3
add pyTorch :) code execution sinks, add proper tests
am0o0 Feb 24, 2024
d234a53
update Fabric models, add new sink to Fabric, add proper test cases
am0o0 Feb 24, 2024
70282f9
convert paramiko query to SecondaryServerCmdInjection query, Add inli…
am0o0 Feb 24, 2024
385c3ba
continue to convert paramiko query to a more general query,
am0o0 Feb 24, 2024
95c9a3f
add ssh client libraries, add SecondaryServerCmdInjectionCustomizations
am0o0 Feb 25, 2024
3e6b4a1
finalize Secondary server command injection queries and tests.
am0o0 Feb 25, 2024
ab21990
add jsonpickle and pexpect libs in case of unsafe decoding and second…
am0o0 Feb 25, 2024
7dd1389
add twisted SSH client as secondary server command injection sinks, a…
am0o0 Feb 25, 2024
4321c5c
update Twisted document link
am0o0 Feb 25, 2024
a636c47
minor test cases change: remove unused dict
am0o0 Feb 25, 2024
b20b733
better structure for pandas DataFrame, it is now much better readable…
am0o0 Feb 27, 2024
cffdc5b
add panas code execution sinks, add proper tests
am0o0 Feb 23, 2024
2708e57
add pyTorch :) code execution sinks, add proper tests
am0o0 Feb 24, 2024
6520e2f
update Fabric models, add new sink to Fabric, add proper test cases
am0o0 Feb 24, 2024
5fea71e
convert paramiko query to SecondaryServerCmdInjection query, Add inli…
am0o0 Feb 24, 2024
4df73f9
continue to convert paramiko query to a more general query,
am0o0 Feb 24, 2024
ead2474
add ssh client libraries, add SecondaryServerCmdInjectionCustomizations
am0o0 Feb 25, 2024
7e93102
finalize Secondary server command injection queries and tests.
am0o0 Feb 25, 2024
0a765cc
add jsonpickle and pexpect libs in case of unsafe decoding and second…
am0o0 Feb 25, 2024
c4a38d0
add twisted SSH client as secondary server command injection sinks, a…
am0o0 Feb 25, 2024
6b9cc1a
update Twisted document link
am0o0 Feb 25, 2024
8b93e81
minor test cases change: remove unused dict
am0o0 Feb 25, 2024
4a2ab49
better structure for pandas DataFrame, it is now much better readable…
am0o0 Feb 27, 2024
9b4ea88
Merge branch 'am0o0-python-codeExec' of https://github.com/amammad/co…
am0o0 May 9, 2024
0043d93
Merge branch 'github:main' into am0o0-python-codeExec
am0o0 May 9, 2024
3a52cd1
Merge branch 'am0o0-python-codeExec' of https://github.com/amammad/co…
am0o0 May 9, 2024
f93d4a0
fix Fabric query library
am0o0 May 9, 2024
0e80e86
fix actions reviews
am0o0 May 10, 2024
9435a62
revert vscode settings.json file
am0o0 May 10, 2024
a87d27b
revert vscode settings.json file
am0o0 May 10, 2024
90da071
fix tests, chore on Find.ql
am0o0 May 10, 2024
fb3d34c
format Torch.qll
am0o0 May 13, 2024
37d3318
revert classRef deletion, fix secondaryserverCmdInjection expected te…
am0o0 May 13, 2024
c7adb32
simply replace duplicate class references with `classRef()` in Fabric…
am0o0 May 14, 2024
fcd2bd6
fabic.qll: remove test predicate and apply review changes
am0o0 May 29, 2024
b1242e4
Pandas.qll: remove unnecessary exists, fix class naming
am0o0 May 29, 2024
8c3994b
Paramiko.qll: improve docs
am0o0 May 29, 2024
66cba89
Torch.qll: use better alternative instead of exists
am0o0 May 29, 2024
5299c4a
fix the qhelp of secondary server cmd injectino
am0o0 May 29, 2024
1714866
Ssh2.qll: fix a typo
am0o0 May 29, 2024
fd9e6f4
fix the docs of secondary server cmd injection
am0o0 May 29, 2024
52a8091
SecondaryCommandInjection to RemoteCommandExecution, change RemoteCo…
am0o0 May 29, 2024
b9edcb7
rename secondary to remote :), complete the previous commit changes
am0o0 May 29, 2024
1f11246
update id of the query file
am0o0 May 29, 2024
8a7fdfa
fix conflict
am0o0 Jun 18, 2024
cb39ae7
revert .vscode/settings.json
am0o0 Jun 18, 2024
1f99559
Revert "update id of the query file"
am0o0 Jun 18, 2024
ccb923a
fix formatting
am0o0 Jun 18, 2024
eb1999f
revert .vscode/settings.json :((
am0o0 Jun 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/ql/lib/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
94 changes: 44 additions & 50 deletions python/ql/lib/semmle/python/frameworks/Fabric.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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
Expand All @@ -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() }
}

/** 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))
abstract class Instance extends API::Node {
override string toString() { result = this.(API::Node).toString() }
}

/** 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() }
}

/**
Expand All @@ -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()
}
}
}
Expand All @@ -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 {
Fixed Show fixed Hide fixed
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
// -------------------------------------------------------------------------
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -242,26 +235,27 @@ 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"])
}
}

/**
* A call to `run` 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
*/
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() }
}
Expand Down
31 changes: 31 additions & 0 deletions python/ql/lib/semmle/python/frameworks/JsonPickle.qll
Original file line number Diff line number Diff line change
@@ -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" }
}
}
117 changes: 117 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Pandas.qll
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}
}
32 changes: 32 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Paramiko.qll
Original file line number Diff line number Diff line change
@@ -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() }
}
}
Loading
Loading