Merge pull request #5103 from tausbn/python-port-flask-to-api-graphs

Python: Port Flask models to use API graphs
This commit is contained in:
Rasmus Wriedt Larsen
2021-02-16 15:00:46 +01:00
committed by GitHub
5 changed files with 157 additions and 358 deletions

View File

@@ -92,6 +92,11 @@ module API {
*/
Node getReturn() { result = getASuccessor(Label::return()) }
/**
* Gets a node representing a subclass of the class represented by this node.
*/
Node getASubclass() { result = getASuccessor(Label::subclass()) }
/**
* Gets a string representation of the lexicographically least among all shortest access paths
* from the root to this node.
@@ -312,12 +317,11 @@ module API {
* For instance, `prefix_member("foo.bar", "baz", "foo.bar.baz")` would hold.
*/
private predicate prefix_member(TApiNode base, string member, TApiNode sub) {
exists(string base_str, string sub_str |
base = MkModuleImport(base_str) and
exists(string sub_str, string regexp |
regexp = "(.+)[.]([^.]+)" and
base = MkModuleImport(sub_str.regexpCapture(regexp, 1)) and
member = sub_str.regexpCapture(regexp, 2) and
sub = MkModuleImport(sub_str)
|
base_str + "." + member = sub_str and
not member.matches("%.%")
)
}
@@ -351,13 +355,19 @@ module API {
// the relationship between `pred` and `ref`.
use(base, src) and pred = trackUseNode(src)
|
// Reading an attribute on a node that is a use of `base`:
// Referring to an attribute on a node that is a use of `base`:
lbl = Label::memberFromRef(ref) and
ref = pred.getAnAttributeRead()
ref = pred.getAnAttributeReference()
or
// Calling a node that is a use of `base`
lbl = Label::return() and
ref = pred.getACall()
or
// Subclassing a node
lbl = Label::subclass() and
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
ref.asExpr().(ClassExpr).getABase() = superclass.asExpr()
)
)
}
@@ -468,4 +478,6 @@ private module Label {
/** Gets the `return` edge label. */
string return() { result = "getReturn()" }
string subclass() { result = "getASubclass()" }
}

View File

@@ -159,7 +159,9 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode {
* Instances of this class correspond to the `NameNode` for `attr`, and also gives access to `value` by
* virtue of being a `DefinitionNode`.
*/
private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode { }
private class ClassAttributeAssignmentNode extends DefinitionNode, NameNode {
ClassAttributeAssignmentNode() { this.getScope() = any(ClassExpr c).getInnerScope() }
}
/**
* An attribute assignment via a class field, e.g.

View File

@@ -165,6 +165,23 @@ class CfgNode extends Node, TCfgNode {
override Location getLocation() { result = node.getLocation() }
}
/** A data-flow node corresponding to a `CallNode` in the control-flow graph. */
class CallCfgNode extends CfgNode {
override CallNode node;
/**
* Gets the data-flow node for the function component of the call corresponding to this data-flow
* node.
*/
Node getFunction() { result.asCfgNode() = node.getFunction() }
/** Gets the data-flow node corresponding to the i'th argument of the call corresponding to this data-flow node */
Node getArg(int i) { result.asCfgNode() = node.getArg(i) }
/** Gets the data-flow node corresponding to the named argument of the call corresponding to this data-flow node */
Node getArgByName(string name) { result.asCfgNode() = node.getArgByName(name) }
}
/**
* An expression, viewed as a node in a data flow graph.
*
@@ -481,7 +498,7 @@ class LocalSourceNode extends Node {
/**
* Gets a call to this node.
*/
Node getACall() { Cached::call(this, result) }
CallCfgNode getACall() { Cached::call(this, result) }
}
cached
@@ -526,10 +543,10 @@ private module Cached {
* Holds if `func` flows to the callee of `call`.
*/
cached
predicate call(LocalSourceNode func, Node call) {
predicate call(LocalSourceNode func, CallCfgNode call) {
exists(CfgNode n |
func.flowsTo(n) and
n.asCfgNode() = call.asCfgNode().(CallNode).getFunction()
n = call.getFunction()
)
}
}

View File

@@ -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() }