Skip to content

Commit 9cb4899

Browse files
committed
Python: Add FileStorage modeling in Flask
1 parent 09b0c30 commit 9cb4899

File tree

2 files changed

+28
-14
lines changed

2 files changed

+28
-14
lines changed

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -410,12 +410,26 @@ module Flask {
410410
}
411411
}
412412

413-
private class RequestAttrFiles extends RequestAttrMultiDict {
414-
// TODO: Somehow specify that elements of `RequestAttrFiles` are
415-
// Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
416-
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
417-
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
418-
RequestAttrFiles() { attr_name = "files" }
413+
/** An `FileStorage` instance that originates from a flask request. */
414+
private class FlaskRequestFileStorageInstances extends Werkzeug::FileStorage::InstanceSource {
415+
FlaskRequestFileStorageInstances() {
416+
// TODO: this currently only works in local-scope, since writing type-trackers for
417+
// this is a little too much effort. Once API-graphs are available for more
418+
// things, we can rewrite this.
419+
//
420+
// TODO: This approach for identifying member-access is very adhoc, and we should
421+
// be able to do something more structured for providing modeling of the members
422+
// of a container-object.
423+
exists(DataFlow::AttrRead files | files.accesses(request().getAUse(), "files") |
424+
this.asCfgNode().(SubscriptNode).getObject() = files.asCfgNode()
425+
or
426+
this.(DataFlow::MethodCallNode).calls(files, "get")
427+
or
428+
exists(DataFlow::MethodCallNode getlistCall | getlistCall.calls(files, "getlist") |
429+
this.asCfgNode().(SubscriptNode).getObject() = getlistCall.asCfgNode()
430+
)
431+
)
432+
}
419433
}
420434

421435
// ---------------------------------------------------------------------------

python/ql/test/library-tests/frameworks/flask/taint_test.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,16 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route
6868
# a werkzeug.datastructures.MultiDict, mapping [str, werkzeug.datastructures.FileStorage]
6969
request.files, # $ tainted
7070
request.files['key'], # $ tainted
71-
request.files['key'].filename, # $ MISSING: tainted
72-
request.files['key'].stream, # $ MISSING: tainted
73-
request.files['key'].read(), # $ MISSING: tainted
74-
request.files['key'].stream.read(), # $ MISSING: tainted
71+
request.files['key'].filename, # $ tainted
72+
request.files['key'].stream, # $ tainted
73+
request.files['key'].read(), # $ tainted
74+
request.files['key'].stream.read(), # $ tainted
7575
request.files.get('key'), # $ tainted
76-
request.files.get('key').filename, # $ MISSING: tainted
77-
request.files.get('key').stream, # $ MISSING: tainted
76+
request.files.get('key').filename, # $ tainted
77+
request.files.get('key').stream, # $ tainted
7878
request.files.getlist('key'), # $ tainted
79-
request.files.getlist('key')[0].filename, # $ MISSING: tainted
80-
request.files.getlist('key')[0].stream, # $ MISSING: tainted
79+
request.files.getlist('key')[0].filename, # $ tainted
80+
request.files.getlist('key')[0].stream, # $ tainted
8181

8282
# By default werkzeug.datastructures.ImmutableMultiDict -- although can be changed :\
8383
request.form, # $ tainted

0 commit comments

Comments
 (0)