mirror of
https://github.com/github/codeql.git
synced 2026-04-26 17:25:19 +02:00
Merge remote-tracking branch 'upstream/main' into 'rc/3.14'
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
31
python/ql/lib/semmle/python/frameworks/JsonPickle.qll
Normal file
31
python/ql/lib/semmle/python/frameworks/JsonPickle.qll
Normal 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" }
|
||||
}
|
||||
}
|
||||
@@ -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
python/ql/lib/semmle/python/frameworks/Paramiko.qll
Normal file
32
python/ql/lib/semmle/python/frameworks/Paramiko.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
45
python/ql/lib/semmle/python/frameworks/Pexpect.qll
Normal file
45
python/ql/lib/semmle/python/frameworks/Pexpect.qll
Normal 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() }
|
||||
}
|
||||
}
|
||||
72
python/ql/lib/semmle/python/frameworks/Torch.qll
Normal file
72
python/ql/lib/semmle/python/frameworks/Torch.qll
Normal 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" }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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")]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user