mirror of
https://github.com/github/codeql.git
synced 2025-12-17 17:23:36 +01:00
Python: Align flask taint modeling with rest of code
This was a good time to do this, so we don't have 2 different ways of doing the
same thing.
I needed to do this to figure out if we should expose
`API::moduleImport("flask").getMember("request")` in a helper predicate or
not. I think I ended up using more refenreces to this in the end. Although it's
not unreasonable to let someone do this themselves, I also think it's reasonable
that we provide a helper predicate for this.
This commit is contained in:
@@ -74,14 +74,8 @@ private module FlaskModel {
|
|||||||
API::Node instance() { result = classRef().getReturn() }
|
API::Node instance() { result = classRef().getReturn() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// flask
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
/** Provides models for the `flask` module. */
|
|
||||||
module flask {
|
|
||||||
/** Gets a reference to the `flask.request` object. */
|
/** Gets a reference to the `flask.request` object. */
|
||||||
API::Node request() { result = API::moduleImport("flask").getMember("request") }
|
API::Node request() { result = API::moduleImport("flask").getMember("request") }
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides models for the `flask.Response` class
|
* Provides models for the `flask.Response` class
|
||||||
@@ -305,42 +299,43 @@ private module FlaskModel {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// flask.Request taint modeling
|
// flask.Request taint modeling
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TODO: Do we even need this class? :|
|
|
||||||
/**
|
/**
|
||||||
* A source of remote flow from a flask request.
|
* A source of remote flow from a flask request.
|
||||||
*
|
*
|
||||||
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
|
||||||
*/
|
*/
|
||||||
private class RequestSource extends RemoteFlowSource::Range {
|
private class RequestSource extends RemoteFlowSource::Range {
|
||||||
RequestSource() { this = flask::request().getAUse() }
|
RequestSource() { this = request().getAUse() }
|
||||||
|
|
||||||
override string getSourceType() { result = "flask.request" }
|
override string getSourceType() { result = "flask.request" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private module FlaskRequestTracking {
|
|
||||||
/** Gets a reference to either of the `get_json` or `get_data` attributes of a Flask request. */
|
|
||||||
API::Node tainted_methods(string attr_name) {
|
|
||||||
attr_name in ["get_data", "get_json"] and
|
|
||||||
result = flask::request().getMember(attr_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source of remote flow from attributes from a flask request.
|
* Taint propagation for a flask request.
|
||||||
*
|
*
|
||||||
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
|
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
|
||||||
*/
|
*/
|
||||||
private class RequestInputAccess extends RemoteFlowSource::Range {
|
private class FlaskRequestAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||||
string attr_name;
|
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||||
|
// Methods
|
||||||
RequestInputAccess() {
|
exists(string method_name | method_name in ["get_data", "get_json"] |
|
||||||
// attributes
|
// Method access
|
||||||
this = flask::request().getMember(attr_name).getAnImmediateUse() and
|
nodeFrom = request().getAUse() and
|
||||||
attr_name in [
|
nodeTo = request().getMember(method_name).getAnImmediateUse()
|
||||||
|
or
|
||||||
|
// Method call
|
||||||
|
nodeFrom = request().getMember(method_name).getAUse() and
|
||||||
|
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
|
||||||
|
)
|
||||||
|
or
|
||||||
|
// Attributes
|
||||||
|
nodeFrom = request().getAUse() and
|
||||||
|
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
|
||||||
|
read.getAttributeName() in [
|
||||||
// str
|
// str
|
||||||
"path", "full_path", "base_url", "url", "access_control_request_method",
|
"path", "full_path", "base_url", "url", "access_control_request_method",
|
||||||
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype", "origin",
|
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype",
|
||||||
"query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
"origin", "query_string", "referrer", "remote_addr", "remote_user", "user_agent",
|
||||||
// dict
|
// dict
|
||||||
"environ", "cookies", "mimetype_params", "view_args",
|
"environ", "cookies", "mimetype_params", "view_args",
|
||||||
// json
|
// json
|
||||||
@@ -371,34 +366,25 @@ private module FlaskModel {
|
|||||||
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
// TODO: dict-like with wsgiref.headers.Header compatibility methods
|
||||||
"headers"
|
"headers"
|
||||||
]
|
]
|
||||||
or
|
)
|
||||||
// methods (needs special handling to track bound-methods -- see `FlaskRequestMethodCallsAdditionalTaintStep` below)
|
|
||||||
this = FlaskRequestTracking::tainted_methods(attr_name).getAUse()
|
|
||||||
}
|
|
||||||
|
|
||||||
override string getSourceType() { result = "flask.request input" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FlaskRequestMethodCallsAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
|
||||||
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(_).getAUse() and
|
|
||||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RequestInputMultiDict extends RequestInputAccess,
|
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
|
||||||
Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
|
string attr_name;
|
||||||
RequestInputMultiDict() { attr_name in ["args", "values", "form", "files"] }
|
|
||||||
|
RequestAttrMultiDict() {
|
||||||
|
attr_name in ["args", "values", "form", "files"] and
|
||||||
|
this = request().getMember(attr_name).getAnImmediateUse()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RequestInputFiles extends RequestInputMultiDict {
|
private class RequestAttrFiles extends RequestAttrMultiDict {
|
||||||
// TODO: Somehow specify that elements of `RequestInputFiles` are
|
// TODO: Somehow specify that elements of `RequestAttrFiles` are
|
||||||
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
|
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
|
||||||
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
|
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
|
||||||
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
|
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
|
||||||
RequestInputFiles() { attr_name = "files" }
|
RequestAttrFiles() { attr_name = "files" }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user