mirror of
https://github.com/github/codeql.git
synced 2025-12-20 10:46:30 +01:00
Merge branch 'main' of github.com:github/codeql into python-port-sql-injection
This commit is contained in:
@@ -292,7 +292,7 @@ private Node update(Node node) {
|
||||
* `y`. There is a dataflow step from `**{"y": 1, "a": 3}` to `[**d]` to transfer the content and
|
||||
* a clearing of content at key `y` for node `[**d]`, since that value has been unpacked.
|
||||
*/
|
||||
private module ArgumentPassing {
|
||||
module ArgumentPassing {
|
||||
/**
|
||||
* Holds if `call` represents a `DataFlowCall` to a `DataFlowCallable` represented by `callable`.
|
||||
*
|
||||
@@ -798,6 +798,29 @@ predicate jumpStep(Node nodeFrom, Node nodeTo) {
|
||||
or
|
||||
// Module variable write
|
||||
nodeFrom = nodeTo.(ModuleVariableNode).getAWrite()
|
||||
or
|
||||
// Read of module attribute:
|
||||
exists(AttrRead r, ModuleValue mv |
|
||||
r.getObject().asCfgNode().pointsTo(mv) and
|
||||
module_export(mv.getScope(), r.getAttributeName(), nodeFrom) and
|
||||
nodeTo = r
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an
|
||||
* overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does
|
||||
* not include `name`).
|
||||
*/
|
||||
private predicate module_export(Module m, string name, CfgNode defn) {
|
||||
exists(EssaVariable v |
|
||||
v.getName() = name and
|
||||
v.getAUse() = m.getANormalExit()
|
||||
|
|
||||
defn.getNode() = v.getDefinition().(AssignmentDefinition).getValue()
|
||||
or
|
||||
defn.getNode() = v.getDefinition().(ArgumentRefinement).getArgument()
|
||||
)
|
||||
}
|
||||
|
||||
//--------
|
||||
|
||||
@@ -49,6 +49,7 @@ newtype TNode =
|
||||
TKwOverflowNode(CallNode call, CallableValue callable) {
|
||||
exists(getKeywordOverflowArg(call, callable, _))
|
||||
or
|
||||
ArgumentPassing::connects(call, callable) and
|
||||
exists(call.getNode().getKwargs()) and
|
||||
callable.getScope().hasKwArg()
|
||||
} or
|
||||
|
||||
@@ -192,8 +192,16 @@ module HTTP {
|
||||
* extend `RouteSetup` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument used to set the URL pattern. */
|
||||
abstract DataFlow::Node getUrlPatternArg();
|
||||
|
||||
/** Gets the URL pattern for this route, if it can be statically determined. */
|
||||
abstract string getUrlPattern();
|
||||
string getUrlPattern() {
|
||||
exists(StrConst str |
|
||||
DataFlow::localFlow(DataFlow::exprNode(str), this.getUrlPatternArg()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a function that will handle incoming requests for this route, if any. */
|
||||
abstract Function getARouteHandler();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
private import experimental.semmle.python.frameworks.Dill
|
||||
private import experimental.semmle.python.frameworks.Django
|
||||
private import experimental.semmle.python.frameworks.Fabric
|
||||
private import experimental.semmle.python.frameworks.Flask
|
||||
private import experimental.semmle.python.frameworks.Invoke
|
||||
private import experimental.semmle.python.frameworks.Stdlib
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `django` package.
|
||||
* Provides classes modeling security-relevant aspects of the `django` PyPI package.
|
||||
* See https://www.djangoproject.com/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import experimental.dataflow.DataFlow
|
||||
private import experimental.dataflow.RemoteFlowSources
|
||||
private import experimental.dataflow.TaintTracking
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.regex
|
||||
|
||||
/**
|
||||
* Provides models for the `django` PyPI package.
|
||||
@@ -31,7 +34,7 @@ private module Django {
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node django_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["db"] and
|
||||
attr_name in ["db", "urls", "http"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django" + "." + attr_name)
|
||||
@@ -295,71 +298,485 @@ private module Django {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `django.db.connection.cursor.execute` function.
|
||||
*
|
||||
* See
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#executing-custom-sql-directly
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#connections-and-cursors
|
||||
*/
|
||||
private class DbConnectionExecute extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
DbConnectionExecute() { node.getFunction() = django::db::execute().asCfgNode() }
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
result.asCfgNode() in [node.getArg(0), node.getArgByName("sql")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `annotate` function on a model using a `RawSQL` argument.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#annotate
|
||||
*/
|
||||
private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
ControlFlowNode sql;
|
||||
|
||||
ObjectsAnnotate() {
|
||||
node.getFunction() = django::db::models::objects_attr("annotate").asCfgNode() and
|
||||
django::db::models::expressions::RawSQL::classInstance(sql).asCfgNode() in [node.getArg(_),
|
||||
node.getArgByName(_)]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result.asCfgNode() = sql }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `raw` function on a model.
|
||||
*
|
||||
* See
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#django.db.models.Manager.raw
|
||||
* - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#raw
|
||||
*/
|
||||
private class ObjectsRaw extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ObjectsRaw() { node.getFunction() = django::db::models::objects_attr("raw").asCfgNode() }
|
||||
|
||||
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `extra` function on a model.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#extra
|
||||
*/
|
||||
private class ObjectsExtra extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ObjectsExtra() { node.getFunction() = django::db::models::objects_attr("extra").asCfgNode() }
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
result.asCfgNode() =
|
||||
[node.getArg([0, 1, 3, 4]), node.getArgByName(["select", "where", "tables", "order_by"])]
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// django.urls
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `django.urls` module. */
|
||||
DataFlow::Node urls() { result = django_attr("urls") }
|
||||
|
||||
/** Provides models for the `django.urls` module */
|
||||
module urls {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `urls` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node urls_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["path", "re_path"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django.urls" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = DataFlow::importNode("django.urls")
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = django::urls()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `urls_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
urls_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate urls_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(urls_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `urls` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node urls_attr(string attr_name) {
|
||||
result = urls_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `django.urls.path` function.
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/urls/#path
|
||||
*/
|
||||
DataFlow::Node path() { result = urls_attr("path") }
|
||||
|
||||
/**
|
||||
* Gets a reference to the `django.urls.re_path` function.
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/urls/#re_path
|
||||
*/
|
||||
DataFlow::Node re_path() { result = urls_attr("re_path") }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// django.http
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `django.http` module. */
|
||||
DataFlow::Node http() { result = django_attr("http") }
|
||||
|
||||
/** Provides models for the `django.http` module */
|
||||
module http {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `django.http` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node http_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["request", "HttpRequest"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django.http" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = django::http()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `http_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
http_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate http_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(http_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `django.http` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node http_attr(string attr_name) {
|
||||
result = http_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// django.http.request
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `django.http.request` module. */
|
||||
DataFlow::Node request() { result = http_attr("request") }
|
||||
|
||||
/** Provides models for the `django.http.request` module. */
|
||||
module request {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `django.http.request` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node request_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["HttpRequest"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("django.http.request" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = django::http::request()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `request_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
request_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate request_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(request_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `django.http.request` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node request_attr(string attr_name) {
|
||||
result = request_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `django.http.request.HttpRequest` class
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects
|
||||
*/
|
||||
module HttpRequest {
|
||||
/** Gets a reference to the `django.http.request.HttpRequest` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = request_attr("HttpRequest")
|
||||
or
|
||||
// handle django.http.HttpRequest alias
|
||||
t.start() and
|
||||
result = http_attr("HttpRequest")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.request.HttpRequest` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* A source of an instance of `django.http.request.HttpRequest`.
|
||||
*
|
||||
* This can include instantiation of the class, return value from function
|
||||
* calls, or a special parameter that will be set when functions are call by external
|
||||
* library.
|
||||
*
|
||||
* Use `django::http::request::HttpRequest::instance()` predicate to get
|
||||
* references to instances of `django.http.request.HttpRequest`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** Gets a reference to an instance of `django.http.request.HttpRequest`. */
|
||||
private DataFlow::Node 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 `django.http.request.HttpRequest`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// routing modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* The idea is that this function should be used as a route handler when setting up a
|
||||
* route, but currently it just tracks all functions, since we can't do type-tracking
|
||||
* backwards yet (TODO).
|
||||
*/
|
||||
private DataFlow::Node djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker t, Function func) {
|
||||
t.start() and
|
||||
result = DataFlow::exprNode(func.getDefinition())
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = djangoRouteHandlerFunctionTracker(t2, func).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `django.db.connection.cursor.execute` function.
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* See
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#executing-custom-sql-directly
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#connections-and-cursors
|
||||
* The idea is that this function should be used as a route handler when setting up a
|
||||
* route, but currently it just tracks all functions, since we can't do type-tracking
|
||||
* backwards yet (TODO).
|
||||
*/
|
||||
private class DbConnectionExecute extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
private DataFlow::Node djangoRouteHandlerFunctionTracker(Function func) {
|
||||
result = djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker::end(), func)
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is used as a django route handler.
|
||||
*/
|
||||
private class DjangoRouteHandler extends Function {
|
||||
DjangoRouteHandler() { exists(djangoRouteHandlerFunctionTracker(this)) }
|
||||
|
||||
/** Gets the index of the request parameter. */
|
||||
int getRequestParamIndex() {
|
||||
not this.isMethod() and
|
||||
result = 0
|
||||
or
|
||||
this.isMethod() and
|
||||
result = 1
|
||||
}
|
||||
|
||||
/** Gets the request parameter. */
|
||||
Parameter getRequestParam() { result = this.getArg(this.getRequestParamIndex()) }
|
||||
}
|
||||
|
||||
abstract private class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
|
||||
abstract override DjangoRouteHandler getARouteHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the regex that is used by django to find routed parameters when using `django.urls.path`.
|
||||
*
|
||||
* Taken from https://github.com/django/django/blob/7d1bf29977bb368d7c28e7c6eb146db3b3009ae7/django/urls/resolvers.py#L199
|
||||
*/
|
||||
private string pathRoutedParameterRegex() {
|
||||
result = "<(?:(?<converter>[^>:]+):)?(?<parameter>\\w+)>"
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `django.urls.path`.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/urls/#path
|
||||
*/
|
||||
private class DjangoUrlsPathCall extends DjangoRouteSetup {
|
||||
override CallNode node;
|
||||
|
||||
DbConnectionExecute() { node.getFunction() = django::db::execute().asCfgNode() }
|
||||
DjangoUrlsPathCall() { node.getFunction() = django::urls::path().asCfgNode() }
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
result.asCfgNode() in [node.getArg(0), node.getArgByName("sql")]
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("route")]
|
||||
}
|
||||
|
||||
override DjangoRouteHandler getARouteHandler() {
|
||||
exists(DataFlow::Node viewArg |
|
||||
viewArg.asCfgNode() in [node.getArg(1), node.getArgByName("view")] and
|
||||
djangoRouteHandlerFunctionTracker(result) = viewArg
|
||||
)
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
// If we don't know the URL pattern, we simply mark all parameters as a routed
|
||||
// parameter. This should give us more RemoteFlowSources but could also lead to
|
||||
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
|
||||
exists(DjangoRouteHandler routeHandler | routeHandler = this.getARouteHandler() |
|
||||
not exists(this.getUrlPattern()) and
|
||||
result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and
|
||||
not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i))
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
result = this.getARouteHandler().getArgByName(name) and
|
||||
exists(string match |
|
||||
match = this.getUrlPattern().regexpFind(pathRoutedParameterRegex(), _, _) and
|
||||
name = match.regexpCapture(pathRoutedParameterRegex(), 2)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `annotate` function on a model using a `RawSQL` argument.
|
||||
* A regex that is used in a call to `django.urls.re_path`.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#annotate
|
||||
* Needs this subclass to be considered a RegexString.
|
||||
*/
|
||||
private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
ControlFlowNode sql;
|
||||
private class DjangoUrlsRePathRegex extends RegexString {
|
||||
DjangoUrlsRePathCall rePathCall;
|
||||
|
||||
ObjectsAnnotate() {
|
||||
node.getFunction() = django::db::models::objects_attr("annotate").asCfgNode() and
|
||||
django::db::models::expressions::RawSQL::classInstance(sql).asCfgNode() in [node.getArg(_),
|
||||
node.getArgByName(_)]
|
||||
DjangoUrlsRePathRegex() {
|
||||
this instanceof StrConst and
|
||||
DataFlow::localFlow(DataFlow::exprNode(this), rePathCall.getUrlPatternArg())
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result.asCfgNode() = sql }
|
||||
DjangoUrlsRePathCall getRePathCall() { result = rePathCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `raw` function on a model.
|
||||
* A call to `django.urls.re_path`.
|
||||
*
|
||||
* See
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/db/sql/#django.db.models.Manager.raw
|
||||
* - https://docs.djangoproject.com/en/3.1/ref/models/querysets/#raw
|
||||
* See https://docs.djangoproject.com/en/3.0/ref/urls/#re_path
|
||||
*/
|
||||
private class ObjectsRaw extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
private class DjangoUrlsRePathCall extends DjangoRouteSetup {
|
||||
override CallNode node;
|
||||
|
||||
ObjectsRaw() { node.getFunction() = django::db::models::objects_attr("raw").asCfgNode() }
|
||||
DjangoUrlsRePathCall() { node.getFunction() = django::urls::re_path().asCfgNode() }
|
||||
|
||||
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("route")]
|
||||
}
|
||||
|
||||
override DjangoRouteHandler getARouteHandler() {
|
||||
exists(DataFlow::Node viewArg |
|
||||
viewArg.asCfgNode() in [node.getArg(1), node.getArgByName("view")] and
|
||||
djangoRouteHandlerFunctionTracker(result) = viewArg
|
||||
)
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
// If we don't know the URL pattern, we simply mark all parameters as a routed
|
||||
// parameter. This should give us more RemoteFlowSources but could also lead to
|
||||
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
|
||||
exists(DjangoRouteHandler routeHandler | routeHandler = this.getARouteHandler() |
|
||||
not exists(this.getUrlPattern()) and
|
||||
result in [routeHandler.getArg(_), routeHandler.getArgByName(_)] and
|
||||
not result = any(int i | i <= routeHandler.getRequestParamIndex() | routeHandler.getArg(i))
|
||||
)
|
||||
or
|
||||
exists(DjangoRouteHandler routeHandler, DjangoUrlsRePathRegex regex |
|
||||
routeHandler = this.getARouteHandler() and
|
||||
regex.getRePathCall() = this
|
||||
|
|
||||
// either using named capture groups (passed as keyword arguments) or using
|
||||
// unnamed capture groups (passed as positional arguments)
|
||||
not exists(regex.getGroupName(_, _)) and
|
||||
// first group will have group number 1
|
||||
result =
|
||||
routeHandler.getArg(routeHandler.getRequestParamIndex() + regex.getGroupNumber(_, _))
|
||||
or
|
||||
result = routeHandler.getArgByName(regex.getGroupName(_, _))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to the `extra` function on a model.
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/models/querysets/#extra
|
||||
*/
|
||||
private class ObjectsExtra extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
// ---------------------------------------------------------------------------
|
||||
// HttpRequest taint modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
class DjangoRouteHandlerRequestParam extends django::http::request::HttpRequest::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
DjangoRouteHandlerRequestParam() {
|
||||
this.getParameter() = any(DjangoRouteSetup setup).getARouteHandler().getRequestParam()
|
||||
}
|
||||
|
||||
ObjectsExtra() { node.getFunction() = django::db::models::objects_attr("extra").asCfgNode() }
|
||||
override string getSourceType() { result = "django.http.request.HttpRequest" }
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
result.asCfgNode() =
|
||||
[node.getArg([0, 1, 3, 4]), node.getArgByName(["select", "where", "tables", "order_by"])]
|
||||
private class DjangoHttpRequstAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
nodeFrom = django::http::request::HttpRequest::instance() and
|
||||
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
|
||||
read.getAttributeName() in ["body",
|
||||
// str / bytes
|
||||
"path", "path_info", "method", "encoding", "content_type",
|
||||
// django.http.QueryDict
|
||||
// TODO: Model QueryDict
|
||||
"GET", "POST",
|
||||
// dict[str, str]
|
||||
"content_params", "COOKIES",
|
||||
// dict[str, Any]
|
||||
"META",
|
||||
// HttpHeaders (case insensitive dict-like)
|
||||
"headers",
|
||||
// MultiValueDict[str, UploadedFile]
|
||||
// TODO: Model MultiValueDict
|
||||
// TODO: Model UploadedFile
|
||||
"FILES",
|
||||
// django.urls.ResolverMatch
|
||||
// TODO: Model ResolverMatch
|
||||
"resolver_match"]
|
||||
// TODO: Handle calls to methods
|
||||
// TODO: Handle that a HttpRequest is iterable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
578
python/ql/src/experimental/semmle/python/frameworks/Fabric.qll
Normal file
578
python/ql/src/experimental/semmle/python/frameworks/Fabric.qll
Normal file
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
|
||||
* both version 1.x and 2.x.
|
||||
*
|
||||
* See
|
||||
* - http://docs.fabfile.org/en/1.14/tutorial.html and
|
||||
* - http://docs.fabfile.org/en/2.5/getting-started.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import experimental.dataflow.DataFlow
|
||||
private import experimental.dataflow.RemoteFlowSources
|
||||
private import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
|
||||
* version 1.x.
|
||||
*
|
||||
* See http://docs.fabfile.org/en/1.14/tutorial.html.
|
||||
*/
|
||||
private module FabricV1 {
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
private DataFlow::Node fabric(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["api"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = fabric()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
fabric_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fabric_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(string attr_name) {
|
||||
result = fabric_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/** Provides models for the `fabric` module. */
|
||||
module fabric {
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.api
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.api` module. */
|
||||
DataFlow::Node api() { result = fabric_attr("api") }
|
||||
|
||||
/** Provides models for the `fabric.api` module */
|
||||
module api {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.api` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node api_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["run", "local", "sudo"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.api" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = api()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `api_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
api_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate api_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(api_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.api` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node api_attr(string attr_name) {
|
||||
result = api_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either
|
||||
* - `fabric.api.local`
|
||||
* - `fabric.api.run`
|
||||
* - `fabric.api.sudo`
|
||||
* See
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.local
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.run
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.sudo
|
||||
*/
|
||||
private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
FabricApiLocalRunSudoCall() {
|
||||
node.getFunction() = api_attr(["local", "run", "sudo"]).asCfgNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("command")]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private module FabricV2 {
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
private DataFlow::Node fabric(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["connection",
|
||||
// connection.py
|
||||
"Connection",
|
||||
// group.py
|
||||
"group", "SerialGroup", "ThreadingGroup",
|
||||
// tasks.py
|
||||
"tasks", "task"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = fabric()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
fabric_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fabric_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(string attr_name) {
|
||||
result = fabric_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/** Provides models for the `fabric` module. */
|
||||
module fabric {
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.connection
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.connection` module. */
|
||||
DataFlow::Node connection() { result = fabric_attr("connection") }
|
||||
|
||||
/** Provides models for the `fabric.connection` module */
|
||||
module connection {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.connection` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node connection_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["Connection"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.connection" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = connection()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `connection_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
connection_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate connection_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(connection_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.connection` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node connection_attr(string attr_name) {
|
||||
result = connection_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.connection.Connection` class
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.
|
||||
*/
|
||||
module Connection {
|
||||
/** Gets a reference to the `fabric.connection.Connection` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = connection_attr("Connection")
|
||||
or
|
||||
// handle `fabric.Connection` alias
|
||||
t.start() and
|
||||
result = fabric_attr("Connection")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.connection.Connection` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* A source of an instance of `fabric.connection.Connection`.
|
||||
*
|
||||
* This can include instantiation of the class, return value from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use `Connection::instance()` predicate to get references to instances of `fabric.connection.Connection`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fabric.connection.Connection`. */
|
||||
private DataFlow::Node 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() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private DataFlow::Node instanceRunMethods(DataFlow::TypeTracker t) {
|
||||
t.startInAttr(["run", "sudo", "local"]) and
|
||||
result = instance()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instanceRunMethods(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
DataFlow::Node instanceRunMethods() {
|
||||
result = instanceRunMethods(DataFlow::TypeTracker::end())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either `run`, `sudo`, or `local` 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
|
||||
*/
|
||||
private class FabricConnectionRunSudoLocalCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
FabricConnectionRunSudoLocalCall() {
|
||||
node.getFunction() = fabric::connection::Connection::instanceRunMethods().asCfgNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("command")]
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.tasks
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.tasks` module. */
|
||||
DataFlow::Node tasks() { result = fabric_attr("tasks") }
|
||||
|
||||
/** Provides models for the `fabric.tasks` module */
|
||||
module tasks {
|
||||
/** Gets a reference to the `fabric.tasks.task` decorator. */
|
||||
private DataFlow::Node task(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.tasks.task")
|
||||
or
|
||||
t.startInAttr("task") and
|
||||
result = tasks()
|
||||
or
|
||||
// Handle `fabric.task` alias
|
||||
t.startInAttr("task") and
|
||||
result = fabric()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = task(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.tasks.task` decorator. */
|
||||
DataFlow::Node task() { result = task(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
|
||||
class FabricTaskFirstParamConnectionInstance extends fabric::connection::Connection::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
FabricTaskFirstParamConnectionInstance() {
|
||||
exists(Function func |
|
||||
func.getADecorator() = fabric::tasks::task().asExpr() and
|
||||
this.getParameter() = func.getArg(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// fabric.group
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.group` module. */
|
||||
DataFlow::Node group() { result = fabric_attr("group") }
|
||||
|
||||
/** Provides models for the `fabric.group` module */
|
||||
module group {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.group` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node group_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["SerialGroup", "ThreadingGroup"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.group" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = group()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `group_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
group_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate group_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(group_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.group` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node group_attr(string attr_name) {
|
||||
result = group_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.group.Group` class and its subclasses.
|
||||
*
|
||||
* `fabric.group.Group` is an abstract class, that has concrete implementations
|
||||
* `SerialGroup` and `ThreadingGroup`.
|
||||
*
|
||||
* See
|
||||
* - https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group
|
||||
* - https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.SerialGroup
|
||||
* - https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.ThreadingGroup
|
||||
*/
|
||||
module Group {
|
||||
/**
|
||||
* A source of an instance of a subclass of `fabric.group.Group`
|
||||
*
|
||||
* This can include instantiation of a class, return value from function
|
||||
* calls, or a special parameter that will be set when functions are call by external
|
||||
* library.
|
||||
*
|
||||
* Use `Group::subclassInstance()` predicate to get references to an instance of a subclass of `fabric.group.Group`.
|
||||
*/
|
||||
abstract class SubclassInstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** Gets a reference to an instance of a subclass of `fabric.group.Group`. */
|
||||
private DataFlow::Node subclassInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof SubclassInstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subclassInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a subclass of `fabric.group.Group`. */
|
||||
DataFlow::Node subclassInstance() {
|
||||
result = subclassInstance(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `run` method 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 DataFlow::Node subclassInstanceRunMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("run") and
|
||||
result = subclassInstance()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subclassInstanceRunMethod(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `run` method 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
|
||||
*/
|
||||
DataFlow::Node subclassInstanceRunMethod() {
|
||||
result = subclassInstanceRunMethod(DataFlow::TypeTracker::end())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
FabricGroupRunCall() {
|
||||
node.getFunction() = fabric::group::Group::subclassInstanceRunMethod().asCfgNode()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("command")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.group.SerialGroup` class
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.SerialGroup.
|
||||
*/
|
||||
module SerialGroup {
|
||||
/** Gets a reference to the `fabric.group.SerialGroup` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = group_attr("SerialGroup")
|
||||
or
|
||||
// Handle `fabric.SerialGroup` alias
|
||||
t.start() and
|
||||
result = fabric_attr("SerialGroup")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.group.SerialGroup` class. */
|
||||
private DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.group.ThreadingGroup` class
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.ThreadingGroup.
|
||||
*/
|
||||
module ThreadingGroup {
|
||||
/** Gets a reference to the `fabric.group.ThreadingGroup` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = group_attr("ThreadingGroup")
|
||||
or
|
||||
// Handle `fabric.ThreadingGroup` alias
|
||||
t.start() and
|
||||
result = fabric_attr("ThreadingGroup")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.group.ThreadingGroup` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,16 +131,6 @@ private module Flask {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the argument used to pass in the URL pattern. */
|
||||
abstract DataFlow::Node getUrlPatternArg();
|
||||
|
||||
override string getUrlPattern() {
|
||||
exists(StrConst str |
|
||||
DataFlow::localFlow(DataFlow::exprNode(str), this.getUrlPatternArg()) and
|
||||
result = str.getText()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user