|
|
|
|
@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.RemoteFlowSources
|
|
|
|
|
private import semmle.python.dataflow.new.TaintTracking
|
|
|
|
|
private import semmle.python.Concepts
|
|
|
|
|
private import semmle.python.frameworks.Werkzeug
|
|
|
|
|
private import semmle.python.ApiGraphs
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides models for the `flask` PyPI package.
|
|
|
|
|
@@ -19,62 +20,20 @@ private module FlaskModel {
|
|
|
|
|
// flask
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
/** Gets a reference to the `flask` module. */
|
|
|
|
|
private DataFlow::Node flask(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result = DataFlow::importNode("flask")
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = flask(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask` module. */
|
|
|
|
|
DataFlow::Node flask() { result = flask(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node flask() { result = API::moduleImport("flask") }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of the `flask` module.
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node flask_attr(DataFlow::TypeTracker t, string attr_name) {
|
|
|
|
|
attr_name in ["request", "make_response", "Response", "views", "redirect"] and
|
|
|
|
|
(
|
|
|
|
|
t.start() and
|
|
|
|
|
result = DataFlow::importNode("flask" + "." + attr_name)
|
|
|
|
|
or
|
|
|
|
|
t.startInAttr(attr_name) and
|
|
|
|
|
result = flask()
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
// Due to bad performance when using normal setup with `flask_attr(t2, attr_name).track(t2, t)`
|
|
|
|
|
// we have inlined that code and forced a join
|
|
|
|
|
exists(DataFlow::TypeTracker t2 |
|
|
|
|
|
exists(DataFlow::StepSummary summary |
|
|
|
|
|
flask_attr_first_join(t2, attr_name, result, summary) and
|
|
|
|
|
t = t2.append(summary)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pragma[nomagic]
|
|
|
|
|
private predicate flask_attr_first_join(
|
|
|
|
|
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
|
|
|
|
) {
|
|
|
|
|
DataFlow::StepSummary::step(flask_attr(t2, attr_name), res, summary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of the `flask` module.
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node flask_attr(string attr_name) {
|
|
|
|
|
result = flask_attr(DataFlow::TypeTracker::end(), attr_name)
|
|
|
|
|
}
|
|
|
|
|
private API::Node flask_attr(string attr_name) { result = flask().getMember(attr_name) }
|
|
|
|
|
|
|
|
|
|
/** Provides models for the `flask` module. */
|
|
|
|
|
module flask {
|
|
|
|
|
/** Gets a reference to the `flask.request` object. */
|
|
|
|
|
DataFlow::Node request() { result = flask_attr("request") }
|
|
|
|
|
API::Node request() { result = flask_attr("request") }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask.make_response` function. */
|
|
|
|
|
DataFlow::Node make_response() { result = flask_attr("make_response") }
|
|
|
|
|
API::Node make_response() { result = flask_attr("make_response") }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides models for the `flask.Flask` class
|
|
|
|
|
@@ -83,159 +42,44 @@ private module FlaskModel {
|
|
|
|
|
*/
|
|
|
|
|
module Flask {
|
|
|
|
|
/** Gets a reference to the `flask.Flask` class. */
|
|
|
|
|
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result = DataFlow::importNode("flask.Flask")
|
|
|
|
|
or
|
|
|
|
|
t.startInAttr("Flask") and
|
|
|
|
|
result = flask()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask.Flask` class. */
|
|
|
|
|
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A source of instances of `flask.Flask`, extend this class to model new instances.
|
|
|
|
|
*
|
|
|
|
|
* 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 `Flask::instance()` to get references to instances of `flask.Flask`.
|
|
|
|
|
*/
|
|
|
|
|
abstract class InstanceSource extends DataFlow::Node { }
|
|
|
|
|
|
|
|
|
|
/** A direct instantiation of `flask.Flask`. */
|
|
|
|
|
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
|
|
|
|
|
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
|
|
|
|
}
|
|
|
|
|
API::Node classRef() { result = flask().getMember("Flask") }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to an instance of `flask.Flask` (a flask application). */
|
|
|
|
|
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 `flask.Flask` (a flask application). */
|
|
|
|
|
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node instance() { result = classRef().getReturn() }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node instance_attr(DataFlow::TypeTracker t, string attr_name) {
|
|
|
|
|
attr_name in ["route", "add_url_rule", "make_response"] and
|
|
|
|
|
t.startInAttr(attr_name) and
|
|
|
|
|
result = flask::Flask::instance()
|
|
|
|
|
or
|
|
|
|
|
// Due to bad performance when using normal setup with `instance_attr(t2, attr_name).track(t2, t)`
|
|
|
|
|
// we have inlined that code and forced a join
|
|
|
|
|
exists(DataFlow::TypeTracker t2 |
|
|
|
|
|
exists(DataFlow::StepSummary summary |
|
|
|
|
|
instance_attr_first_join(t2, attr_name, result, summary) and
|
|
|
|
|
t = t2.append(summary)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pragma[nomagic]
|
|
|
|
|
private predicate instance_attr_first_join(
|
|
|
|
|
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
|
|
|
|
DataFlow::StepSummary summary
|
|
|
|
|
) {
|
|
|
|
|
DataFlow::StepSummary::step(instance_attr(t2, attr_name), res, summary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of an instance of `flask.Flask` (a flask application).
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node instance_attr(string attr_name) {
|
|
|
|
|
result = instance_attr(DataFlow::TypeTracker::end(), attr_name)
|
|
|
|
|
}
|
|
|
|
|
private API::Node instance_attr(string attr_name) { result = instance().getMember(attr_name) }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `route` method on an instance of `flask.Flask`. */
|
|
|
|
|
DataFlow::Node route() { result = instance_attr("route") }
|
|
|
|
|
API::Node route() { result = instance_attr("route") }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Flask`. */
|
|
|
|
|
DataFlow::Node add_url_rule() { result = instance_attr("add_url_rule") }
|
|
|
|
|
API::Node add_url_rule() { result = instance_attr("add_url_rule") }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `make_response` method on an instance of `flask.Flask`. */
|
|
|
|
|
// HACK: We can't call this predicate `make_response` since shadowing is
|
|
|
|
|
// completely disallowed in QL. I added an underscore to move things forward for
|
|
|
|
|
// now :(
|
|
|
|
|
DataFlow::Node make_response_() { result = instance_attr("make_response") }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance. */
|
|
|
|
|
private DataFlow::Node response_class(DataFlow::TypeTracker t) {
|
|
|
|
|
t.startInAttr("response_class") and
|
|
|
|
|
result in [classRef(), instance()]
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = response_class(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
API::Node make_response_() { result = instance_attr("make_response") }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance.
|
|
|
|
|
*
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.response_class
|
|
|
|
|
*/
|
|
|
|
|
DataFlow::Node response_class() { result = response_class(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node response_class() { result = [classRef(), instance()].getMember("response_class") }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
// flask.views
|
|
|
|
|
// -------------------------------------------------------------------------
|
|
|
|
|
/** Gets a reference to the `flask.views` module. */
|
|
|
|
|
DataFlow::Node views() { result = flask_attr("views") }
|
|
|
|
|
API::Node views() { result = flask_attr("views") }
|
|
|
|
|
|
|
|
|
|
/** Provides models for the `flask.views` module */
|
|
|
|
|
module views {
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of the `flask.views` module.
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node views_attr(DataFlow::TypeTracker t, string attr_name) {
|
|
|
|
|
attr_name in ["View", "MethodView"] and
|
|
|
|
|
(
|
|
|
|
|
t.start() and
|
|
|
|
|
result = DataFlow::importNode("flask.views" + "." + attr_name)
|
|
|
|
|
or
|
|
|
|
|
t.startInAttr(attr_name) and
|
|
|
|
|
result = views()
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
// Due to bad performance when using normal setup with `views_attr(t2, attr_name).track(t2, t)`
|
|
|
|
|
// we have inlined that code and forced a join
|
|
|
|
|
exists(DataFlow::TypeTracker t2 |
|
|
|
|
|
exists(DataFlow::StepSummary summary |
|
|
|
|
|
views_attr_first_join(t2, attr_name, result, summary) and
|
|
|
|
|
t = t2.append(summary)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pragma[nomagic]
|
|
|
|
|
private predicate views_attr_first_join(
|
|
|
|
|
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
|
|
|
|
DataFlow::StepSummary summary
|
|
|
|
|
) {
|
|
|
|
|
DataFlow::StepSummary::step(views_attr(t2, attr_name), res, summary)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a reference to the attribute `attr_name` of the `flask.views` module.
|
|
|
|
|
* WARNING: Only holds for a few predefined attributes.
|
|
|
|
|
*/
|
|
|
|
|
private DataFlow::Node views_attr(string attr_name) {
|
|
|
|
|
result = views_attr(DataFlow::TypeTracker::end(), attr_name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Provides models for the `flask.views.View` class and subclasses.
|
|
|
|
|
*
|
|
|
|
|
@@ -243,18 +87,9 @@ private module FlaskModel {
|
|
|
|
|
*/
|
|
|
|
|
module View {
|
|
|
|
|
/** Gets a reference to the `flask.views.View` class or any subclass. */
|
|
|
|
|
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result = views_attr(["View", "MethodView"])
|
|
|
|
|
or
|
|
|
|
|
// subclasses in project code
|
|
|
|
|
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
|
|
|
|
|
API::Node subclassRef() {
|
|
|
|
|
result = views().getMember(["View", "MethodView"]).getASubclass*()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask.views.View` class or any subclass. */
|
|
|
|
|
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -263,19 +98,8 @@ private module FlaskModel {
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/views/#method-based-dispatching.
|
|
|
|
|
*/
|
|
|
|
|
module MethodView {
|
|
|
|
|
/** Gets a reference to the `flask.views.View` class or any subclass. */
|
|
|
|
|
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result = views_attr("MethodView")
|
|
|
|
|
or
|
|
|
|
|
// subclasses in project code
|
|
|
|
|
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask.views.View` class or any subclass. */
|
|
|
|
|
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
/** Gets a reference to the `flask.views.MethodView` class or any subclass. */
|
|
|
|
|
API::Node subclassRef() { result = views().getMember("MethodView").getASubclass*() }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -287,15 +111,7 @@ private module FlaskModel {
|
|
|
|
|
*/
|
|
|
|
|
module Response {
|
|
|
|
|
/** Gets a reference to the `flask.Response` class. */
|
|
|
|
|
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result in [flask_attr("Response"), flask::Flask::response_class()]
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `flask.Response` class. */
|
|
|
|
|
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node classRef() { result = [flask_attr("Response"), flask::Flask::response_class()] }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A source of instances of `flask.Response`, extend this class to model new instances.
|
|
|
|
|
@@ -309,23 +125,21 @@ private module FlaskModel {
|
|
|
|
|
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node { }
|
|
|
|
|
|
|
|
|
|
/** A direct instantiation of `flask.Response`. */
|
|
|
|
|
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
|
|
|
|
ClassInstantiation() { this = classRef().getACall() }
|
|
|
|
|
|
|
|
|
|
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
|
|
|
|
|
override DataFlow::Node getBody() { result = this.getArg(0) }
|
|
|
|
|
|
|
|
|
|
override string getMimetypeDefault() { result = "text/html" }
|
|
|
|
|
|
|
|
|
|
/** Gets the argument passed to the `mimetype` parameter, if any. */
|
|
|
|
|
private DataFlow::Node getMimetypeArg() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(3), node.getArgByName("mimetype")]
|
|
|
|
|
result in [this.getArg(3), this.getArgByName("mimetype")]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets the argument passed to the `content_type` parameter, if any. */
|
|
|
|
|
private DataFlow::Node getContentTypeArg() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(4), node.getArgByName("content_type")]
|
|
|
|
|
result in [this.getArg(4), this.getArgByName("content_type")]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getMimetypeOrContentTypeArg() {
|
|
|
|
|
@@ -338,15 +152,7 @@ private module FlaskModel {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to an instance of `flask.Response`. */
|
|
|
|
|
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 `flask.Response`. */
|
|
|
|
|
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node instance() { result = classRef().getReturn() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
@@ -354,7 +160,12 @@ private module FlaskModel {
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
/** A flask View class defined in project code. */
|
|
|
|
|
class FlaskViewClassDef extends Class {
|
|
|
|
|
FlaskViewClassDef() { this.getABase() = flask::views::View::subclassRef().asExpr() }
|
|
|
|
|
API::Node api_node;
|
|
|
|
|
|
|
|
|
|
FlaskViewClassDef() {
|
|
|
|
|
this.getABase() = flask::views::View::subclassRef().getAUse().asExpr() and
|
|
|
|
|
api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a function that could handle incoming requests, if any. */
|
|
|
|
|
Function getARequestHandler() {
|
|
|
|
|
@@ -364,42 +175,15 @@ private module FlaskModel {
|
|
|
|
|
result.getName() = "dispatch_request"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to this class. */
|
|
|
|
|
private DataFlow::Node getARef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result.asExpr().(ClassExpr) = this.getParent()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = this.getARef(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to this class. */
|
|
|
|
|
DataFlow::Node getARef() { result = this.getARef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `as_view` classmethod of this class. */
|
|
|
|
|
private DataFlow::Node asViewRef(DataFlow::TypeTracker t) {
|
|
|
|
|
t.startInAttr("as_view") and
|
|
|
|
|
result = this.getARef()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = this.asViewRef(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `as_view` classmethod of this class. */
|
|
|
|
|
DataFlow::Node asViewRef() { result = this.asViewRef(DataFlow::TypeTracker::end()) }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
|
|
|
|
|
private DataFlow::Node asViewResult(DataFlow::TypeTracker t) {
|
|
|
|
|
t.start() and
|
|
|
|
|
result.asCfgNode().(CallNode).getFunction() = this.asViewRef().asCfgNode()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = asViewResult(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the result of calling the `as_view` classmethod of this class. */
|
|
|
|
|
DataFlow::Node asViewResult() { result = asViewResult(DataFlow::TypeTracker::end()) }
|
|
|
|
|
API::Node asViewResult() { result = api_node.getMember("as_view").getReturn() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class FlaskMethodViewClassDef extends FlaskViewClassDef {
|
|
|
|
|
FlaskMethodViewClassDef() { this.getABase() = flask::views::MethodView::subclassRef().asExpr() }
|
|
|
|
|
FlaskMethodViewClassDef() {
|
|
|
|
|
this.getABase() = flask::views::MethodView::subclassRef().getAUse().asExpr() and
|
|
|
|
|
api_node.getAnImmediateUse().asExpr().(ClassExpr) = this.getParent()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override Function getARequestHandler() {
|
|
|
|
|
result = super.getARequestHandler()
|
|
|
|
|
@@ -442,13 +226,11 @@ private module FlaskModel {
|
|
|
|
|
*
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.route
|
|
|
|
|
*/
|
|
|
|
|
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
|
|
|
|
|
FlaskAppRouteCall() { node.getFunction() = flask::Flask::route().asCfgNode() }
|
|
|
|
|
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
|
|
|
|
|
FlaskAppRouteCall() { this.getFunction() = flask::Flask::route().getAUse() }
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getUrlPatternArg() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
|
|
|
|
|
result in [this.getArg(0), this.getArgByName("rule")]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override Function getARequestHandler() { result.getADecorator().getAFlowNode() = node }
|
|
|
|
|
@@ -459,18 +241,14 @@ private module FlaskModel {
|
|
|
|
|
*
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
|
|
|
|
|
*/
|
|
|
|
|
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
|
|
|
|
|
FlaskAppAddUrlRuleCall() { node.getFunction() = flask::Flask::add_url_rule().asCfgNode() }
|
|
|
|
|
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
|
|
|
|
|
FlaskAppAddUrlRuleCall() { this.getFunction() = flask::Flask::add_url_rule().getAUse() }
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getUrlPatternArg() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(0), node.getArgByName("rule")]
|
|
|
|
|
result in [this.getArg(0), this.getArgByName("rule")]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DataFlow::Node getViewArg() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(2), node.getArgByName("view_func")]
|
|
|
|
|
}
|
|
|
|
|
DataFlow::Node getViewArg() { result in [this.getArg(2), this.getArgByName("view_func")] }
|
|
|
|
|
|
|
|
|
|
override Function getARequestHandler() {
|
|
|
|
|
exists(DataFlow::LocalSourceNode func_src |
|
|
|
|
|
@@ -479,7 +257,7 @@ private module FlaskModel {
|
|
|
|
|
)
|
|
|
|
|
or
|
|
|
|
|
exists(FlaskViewClassDef vc |
|
|
|
|
|
getViewArg() = vc.asViewResult() and
|
|
|
|
|
getViewArg() = vc.asViewResult().getAUse() and
|
|
|
|
|
result = vc.getARequestHandler()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
@@ -511,41 +289,16 @@ private module FlaskModel {
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
|
|
|
|
|
*/
|
|
|
|
|
private class RequestSource extends RemoteFlowSource::Range {
|
|
|
|
|
RequestSource() { this = flask::request() }
|
|
|
|
|
RequestSource() { this = flask::request().getAUse() }
|
|
|
|
|
|
|
|
|
|
override string getSourceType() { result = "flask.request" }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private module FlaskRequestTracking {
|
|
|
|
|
/** Gets a reference to the `get_data` attribute of a Flask request. */
|
|
|
|
|
private DataFlow::Node get_data(DataFlow::TypeTracker t) {
|
|
|
|
|
t.startInAttr("get_data") and
|
|
|
|
|
result = flask::request()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = get_data(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `get_data` attribute of a Flask request. */
|
|
|
|
|
DataFlow::Node get_data() { result = get_data(DataFlow::TypeTracker::end()) }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `get_json` attribute of a Flask request. */
|
|
|
|
|
private DataFlow::Node get_json(DataFlow::TypeTracker t) {
|
|
|
|
|
t.startInAttr("get_json") and
|
|
|
|
|
result = flask::request()
|
|
|
|
|
or
|
|
|
|
|
exists(DataFlow::TypeTracker t2 | result = get_json(t2).track(t2, t))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to the `get_json` attribute of a Flask request. */
|
|
|
|
|
DataFlow::Node get_json() { result = get_json(DataFlow::TypeTracker::end()) }
|
|
|
|
|
|
|
|
|
|
/** Gets a reference to either of the `get_json` or `get_data` attributes of a Flask request. */
|
|
|
|
|
DataFlow::Node tainted_methods(string attr_name) {
|
|
|
|
|
result = get_data() and
|
|
|
|
|
attr_name = "get_data"
|
|
|
|
|
or
|
|
|
|
|
result = get_json() and
|
|
|
|
|
attr_name = "get_json"
|
|
|
|
|
API::Node tainted_methods(string attr_name) {
|
|
|
|
|
attr_name in ["get_data", "get_json"] and
|
|
|
|
|
result = flask::request().getMember(attr_name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -559,48 +312,45 @@ private module FlaskModel {
|
|
|
|
|
|
|
|
|
|
RequestInputAccess() {
|
|
|
|
|
// attributes
|
|
|
|
|
exists(AttrNode attr |
|
|
|
|
|
this.asCfgNode() = attr and attr.getObject(attr_name) = flask::request().asCfgNode()
|
|
|
|
|
|
|
|
|
|
|
attr_name in [
|
|
|
|
|
// str
|
|
|
|
|
"path", "full_path", "base_url", "url", "access_control_request_method",
|
|
|
|
|
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype",
|
|
|
|
|
"origin", "query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
|
|
|
|
// dict
|
|
|
|
|
"environ", "cookies", "mimetype_params", "view_args",
|
|
|
|
|
// json
|
|
|
|
|
"json",
|
|
|
|
|
// List[str]
|
|
|
|
|
"access_route",
|
|
|
|
|
// file-like
|
|
|
|
|
"stream", "input_stream",
|
|
|
|
|
// MultiDict[str, str]
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
|
|
|
|
|
"args", "values", "form",
|
|
|
|
|
// MultiDict[str, FileStorage]
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
|
|
|
|
|
// TODO: FileStorage needs extra taint steps
|
|
|
|
|
"files",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
|
|
|
|
|
"access_control_request_headers", "pragma",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
|
|
|
|
|
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
|
|
|
|
|
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
|
|
|
|
|
// TODO: dict subclass with extra attributes like `username` and `password`
|
|
|
|
|
"authorization",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
|
|
|
|
|
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
|
|
|
|
|
"cache_control",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
|
|
|
|
|
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
|
|
|
|
"headers"
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
this = flask::request().getMember(attr_name).getAnImmediateUse() and
|
|
|
|
|
attr_name in [
|
|
|
|
|
// str
|
|
|
|
|
"path", "full_path", "base_url", "url", "access_control_request_method",
|
|
|
|
|
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype", "origin",
|
|
|
|
|
"query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
|
|
|
|
// dict
|
|
|
|
|
"environ", "cookies", "mimetype_params", "view_args",
|
|
|
|
|
// json
|
|
|
|
|
"json",
|
|
|
|
|
// List[str]
|
|
|
|
|
"access_route",
|
|
|
|
|
// file-like
|
|
|
|
|
"stream", "input_stream",
|
|
|
|
|
// MultiDict[str, str]
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
|
|
|
|
|
"args", "values", "form",
|
|
|
|
|
// MultiDict[str, FileStorage]
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
|
|
|
|
|
// TODO: FileStorage needs extra taint steps
|
|
|
|
|
"files",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
|
|
|
|
|
"access_control_request_headers", "pragma",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
|
|
|
|
|
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
|
|
|
|
|
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
|
|
|
|
|
// TODO: dict subclass with extra attributes like `username` and `password`
|
|
|
|
|
"authorization",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
|
|
|
|
|
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
|
|
|
|
|
"cache_control",
|
|
|
|
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
|
|
|
|
|
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
|
|
|
|
"headers"
|
|
|
|
|
]
|
|
|
|
|
or
|
|
|
|
|
// methods (needs special handling to track bound-methods -- see `FlaskRequestMethodCallsAdditionalTaintStep` below)
|
|
|
|
|
this = FlaskRequestTracking::tainted_methods(attr_name)
|
|
|
|
|
this = FlaskRequestTracking::tainted_methods(attr_name).getAUse()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override string getSourceType() { result = "flask.request input" }
|
|
|
|
|
@@ -610,8 +360,8 @@ private module FlaskModel {
|
|
|
|
|
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
|
|
|
|
// NOTE: `request -> request.tainted_method` part is handled as part of RequestInputAccess
|
|
|
|
|
// tainted_method -> tainted_method()
|
|
|
|
|
nodeFrom = FlaskRequestTracking::tainted_methods(_) and
|
|
|
|
|
nodeTo.asCfgNode().(CallNode).getFunction() = nodeFrom.asCfgNode()
|
|
|
|
|
nodeFrom = FlaskRequestTracking::tainted_methods(_).getAUse() and
|
|
|
|
|
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -639,16 +389,15 @@ private module FlaskModel {
|
|
|
|
|
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
|
|
|
|
|
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
|
|
|
|
|
*/
|
|
|
|
|
private class FlaskMakeResponseCall extends HTTP::Server::HttpResponse::Range, DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
|
|
|
|
|
private class FlaskMakeResponseCall extends HTTP::Server::HttpResponse::Range,
|
|
|
|
|
DataFlow::CallCfgNode {
|
|
|
|
|
FlaskMakeResponseCall() {
|
|
|
|
|
node.getFunction() = flask::make_response().asCfgNode()
|
|
|
|
|
this.getFunction() = flask::make_response().getAUse()
|
|
|
|
|
or
|
|
|
|
|
node.getFunction() = flask::Flask::make_response_().asCfgNode()
|
|
|
|
|
this.getFunction() = flask::Flask::make_response_().getAUse()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
|
|
|
|
|
override DataFlow::Node getBody() { result = this.getArg(0) }
|
|
|
|
|
|
|
|
|
|
override string getMimetypeDefault() { result = "text/html" }
|
|
|
|
|
|
|
|
|
|
@@ -676,13 +425,11 @@ private module FlaskModel {
|
|
|
|
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.redirect
|
|
|
|
|
*/
|
|
|
|
|
private class FlaskRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
|
|
|
|
DataFlow::CfgNode {
|
|
|
|
|
override CallNode node;
|
|
|
|
|
|
|
|
|
|
FlaskRedirectCall() { node.getFunction() = flask_attr("redirect").asCfgNode() }
|
|
|
|
|
DataFlow::CallCfgNode {
|
|
|
|
|
FlaskRedirectCall() { this.getFunction() = flask_attr("redirect").getAUse() }
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getRedirectLocation() {
|
|
|
|
|
result.asCfgNode() in [node.getArg(0), node.getArgByName("location")]
|
|
|
|
|
result in [this.getArg(0), this.getArgByName("location")]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override DataFlow::Node getBody() { none() }
|
|
|
|
|
|