Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 19b7ea8

Browse files
committed
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.
1 parent ba61099 commit 19b7ea8

1 file changed

Lines changed: 64 additions & 78 deletions

File tree

  • python/ql/src/semmle/python/frameworks

python/ql/src/semmle/python/frameworks/Flask.qll

Lines changed: 64 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,8 @@ private module FlaskModel {
7474
API::Node instance() { result = classRef().getReturn() }
7575
}
7676

77-
// ---------------------------------------------------------------------------
78-
// flask
79-
// ---------------------------------------------------------------------------
80-
/** Provides models for the `flask` module. */
81-
module flask {
82-
/** Gets a reference to the `flask.request` object. */
83-
API::Node request() { result = API::moduleImport("flask").getMember("request") }
84-
}
77+
/** Gets a reference to the `flask.request` object. */
78+
API::Node request() { result = API::moduleImport("flask").getMember("request") }
8579

8680
/**
8781
* Provides models for the `flask.Response` class
@@ -305,100 +299,92 @@ private module FlaskModel {
305299
// ---------------------------------------------------------------------------
306300
// flask.Request taint modeling
307301
// ---------------------------------------------------------------------------
308-
// TODO: Do we even need this class? :|
309302
/**
310303
* A source of remote flow from a flask request.
311304
*
312305
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
313306
*/
314307
private class RequestSource extends RemoteFlowSource::Range {
315-
RequestSource() { this = flask::request().getAUse() }
308+
RequestSource() { this = request().getAUse() }
316309

317310
override string getSourceType() { result = "flask.request" }
318311
}
319312

320-
private module FlaskRequestTracking {
321-
/** Gets a reference to either of the `get_json` or `get_data` attributes of a Flask request. */
322-
API::Node tainted_methods(string attr_name) {
323-
attr_name in ["get_data", "get_json"] and
324-
result = flask::request().getMember(attr_name)
325-
}
326-
}
327-
328313
/**
329-
* A source of remote flow from attributes from a flask request.
314+
* Taint propagation for a flask request.
330315
*
331316
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
332317
*/
333-
private class RequestInputAccess extends RemoteFlowSource::Range {
334-
string attr_name;
335-
336-
RequestInputAccess() {
337-
// attributes
338-
this = flask::request().getMember(attr_name).getAnImmediateUse() and
339-
attr_name in [
340-
// str
341-
"path", "full_path", "base_url", "url", "access_control_request_method",
342-
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype", "origin",
343-
"query_string", "referrer", "remote_addr", "remote_user", "user_agent",
344-
// dict
345-
"environ", "cookies", "mimetype_params", "view_args",
346-
// json
347-
"json",
348-
// List[str]
349-
"access_route",
350-
// file-like
351-
"stream", "input_stream",
352-
// MultiDict[str, str]
353-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
354-
"args", "values", "form",
355-
// MultiDict[str, FileStorage]
356-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
357-
// TODO: FileStorage needs extra taint steps
358-
"files",
359-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
360-
"access_control_request_headers", "pragma",
361-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
362-
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
363-
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
364-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
365-
// TODO: dict subclass with extra attributes like `username` and `password`
366-
"authorization",
367-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
368-
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
369-
"cache_control",
370-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
371-
// TODO: dict-like with wsgiref.headers.Header compatibility methods
372-
"headers"
373-
]
318+
private class FlaskRequestAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
319+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
320+
// Methods
321+
exists(string method_name | method_name in ["get_data", "get_json"] |
322+
// Method access
323+
nodeFrom = request().getAUse() and
324+
nodeTo = request().getMember(method_name).getAnImmediateUse()
325+
or
326+
// Method call
327+
nodeFrom = request().getMember(method_name).getAUse() and
328+
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
329+
)
374330
or
375-
// methods (needs special handling to track bound-methods -- see `FlaskRequestMethodCallsAdditionalTaintStep` below)
376-
this = FlaskRequestTracking::tainted_methods(attr_name).getAUse()
331+
// Attributes
332+
nodeFrom = request().getAUse() and
333+
exists(DataFlow::AttrRead read | nodeTo = read and read.getObject() = nodeFrom |
334+
read.getAttributeName() in [
335+
// str
336+
"path", "full_path", "base_url", "url", "access_control_request_method",
337+
"content_encoding", "content_md5", "content_type", "data", "method", "mimetype",
338+
"origin", "query_string", "referrer", "remote_addr", "remote_user", "user_agent",
339+
// dict
340+
"environ", "cookies", "mimetype_params", "view_args",
341+
// json
342+
"json",
343+
// List[str]
344+
"access_route",
345+
// file-like
346+
"stream", "input_stream",
347+
// MultiDict[str, str]
348+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
349+
"args", "values", "form",
350+
// MultiDict[str, FileStorage]
351+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
352+
// TODO: FileStorage needs extra taint steps
353+
"files",
354+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
355+
"access_control_request_headers", "pragma",
356+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
357+
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
358+
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
359+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
360+
// TODO: dict subclass with extra attributes like `username` and `password`
361+
"authorization",
362+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
363+
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
364+
"cache_control",
365+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
366+
// TODO: dict-like with wsgiref.headers.Header compatibility methods
367+
"headers"
368+
]
369+
)
377370
}
378-
379-
override string getSourceType() { result = "flask.request input" }
380371
}
381372

382-
private class FlaskRequestMethodCallsAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
383-
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
384-
// NOTE: `request -> request.tainted_method` part is handled as part of RequestInputAccess
385-
// tainted_method -> tainted_method()
386-
nodeFrom = FlaskRequestTracking::tainted_methods(_).getAUse() and
387-
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
388-
}
389-
}
373+
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
374+
string attr_name;
390375

391-
private class RequestInputMultiDict extends RequestInputAccess,
392-
Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
393-
RequestInputMultiDict() { attr_name in ["args", "values", "form", "files"] }
376+
RequestAttrMultiDict() {
377+
attr_name in ["args", "values", "form", "files"] and
378+
this = request().getMember(attr_name).getAnImmediateUse()
379+
}
394380
}
395381

396-
private class RequestInputFiles extends RequestInputMultiDict {
397-
// TODO: Somehow specify that elements of `RequestInputFiles` are
382+
private class RequestAttrFiles extends RequestAttrMultiDict {
383+
// TODO: Somehow specify that elements of `RequestAttrFiles` are
398384
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
399385
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
400386
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
401-
RequestInputFiles() { attr_name = "files" }
387+
RequestAttrFiles() { attr_name = "files" }
402388
}
403389

404390
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)