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

Skip to content

Commit e614365

Browse files
committed
Python: Adopt new approach in flask modeling
Removed all the dict-like stuff, not sure that is how we should do things.
1 parent a82fa04 commit e614365

4 files changed

Lines changed: 32 additions & 120 deletions

File tree

python/ql/src/experimental/dataflow/internal/DataFlowUtil.qll

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,28 +65,3 @@ EssaNode importMember(string moduleName, string memberName) {
6565
result.getVar().(AssignmentDefinition).getSourceVariable() = var
6666
)
6767
}
68-
69-
abstract class ListLike extends Node {
70-
/** Gets a Node that is an access of an element of this list-like object */
71-
Node getElementAccess() {
72-
// subscript
73-
result.asCfgNode().(SubscriptNode).getObject() = this.asCfgNode()
74-
or
75-
// get
76-
// NOTE: will not track bound method, `f = obj.func; f()`
77-
result.asCfgNode().(CallNode).getFunction().(AttrNode).getObject("pop") = this.asCfgNode()
78-
}
79-
}
80-
81-
/** Class of dictionary-like objects */
82-
abstract class DictLike extends Node {
83-
/** Gets a Node that is an access of an element of this dictionary-like object */
84-
Node getElementAccess() {
85-
// subscript
86-
result.asCfgNode().(SubscriptNode).getObject() = this.asCfgNode()
87-
or
88-
// get
89-
// NOTE: will not track bound method, `f = obj.func; f()`
90-
result.asCfgNode().(CallNode).getFunction().(AttrNode).getObject("get") = this.asCfgNode()
91-
}
92-
}

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ private import experimental.dataflow.RemoteFlowSources
88
private import experimental.semmle.python.Concepts
99
private import experimental.semmle.python.frameworks.Werkzeug
1010

11+
// for old improved impl see
12+
// https://github.com/github/codeql/blob/9f95212e103c68d0c1dfa4b6f30fb5d53954ccef/python/ql/src/semmle/python/web/flask/Request.qll
1113
private module Flask {
1214
/** Gets a reference to the `flask` module. */
1315
DataFlow::Node flask(DataFlow::TypeTracker t) {
@@ -36,7 +38,12 @@ private module Flask {
3638
DataFlow::Node request() { result = flask::request(DataFlow::TypeTracker::end()) }
3739
}
3840

39-
// TODO: Do we even need this class then? :|
41+
// TODO: Do we even need this class? :|
42+
/**
43+
* A source of remote flow from a flask request.
44+
*
45+
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
46+
*/
4047
private class RequestSource extends RemoteFlowSource::Range {
4148
RequestSource() { this = flask::request() }
4249

@@ -111,13 +118,8 @@ private module Flask {
111118
private class RequestInputFiles extends RequestInputMultiDict {
112119
RequestInputFiles() { attr_name = "files" }
113120
}
114-
115-
private class RequestInputFileStorage extends Werkzeug::Datastructures::FileStorage {
116-
RequestInputFileStorage() {
117-
exists(RequestInputFiles files, Werkzeug::Datastructures::MultiDictTracked filesTracked |
118-
filesTracked.getMultiDict() = files and
119-
this = filesTracked.getElementAccess()
120-
)
121-
}
122-
}
121+
// TODO: Somehow specify that elements of `RequestInputFiles` are
122+
// Werkzeug::Datastructures::FileStorage and should have those additional taint steps
123+
// AND that the 0-indexed argument to its' save method is a sink for path-injection.
124+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
123125
}

python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll

Lines changed: 16 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ private import python
66
private import experimental.dataflow.DataFlow
77
private import experimental.dataflow.TaintTracking
88

9+
// for old impl see
10+
// https://github.com/github/codeql/blob/9f95212e103c68d0c1dfa4b6f30fb5d53954ccef/python/ql/src/semmle/python/libraries/Werkzeug.qll
911
module Werkzeug {
1012
module Datastructures {
1113
// ---------------------------------------------------------------------- //
@@ -18,94 +20,26 @@ module Werkzeug {
1820
*/
1921
abstract class MultiDict extends DataFlow::Node { }
2022

21-
private DataFlow::Node multiDictTrack(MultiDict multiDict, DataFlow::TypeTracker t) {
22-
t.start() and
23-
result instanceof MultiDict
24-
or
25-
exists(DataFlow::TypeTracker t2 | result = multiDictTrack(multiDict, t2).track(t2, t))
26-
}
27-
28-
/** Gets a reference to the MultiDict attributes of `flask.request`. */
29-
private DataFlow::Node multiDictTrack(MultiDict multiDict) {
30-
result = multiDictTrack(multiDict, DataFlow::TypeTracker::end())
31-
}
32-
33-
class MultiDictTracked extends DataFlow::Node, DataFlow::DictLike {
34-
MultiDict multiDict;
35-
36-
MultiDictTracked() { this = multiDictTrack(multiDict) }
37-
38-
MultiDict getMultiDict() { result = multiDict }
39-
40-
override DataFlow::Node getElementAccess() {
41-
result = DataFlow::DictLike.super.getElementAccess()
23+
private module MultiDictTracking {
24+
private DataFlow::Node getlist(DataFlow::TypeTracker t) {
25+
t.startInAttr("getlist") and
26+
result instanceof MultiDict
4227
or
43-
exists(MultiDictGetListCallResultTracked tracked_call_result |
44-
tracked_call_result.getCall().getMultiDict() = this and
45-
result = tracked_call_result.getElementAccess()
46-
)
47-
}
48-
}
49-
50-
private DataFlow::Node multiDictGetListTrack(MultiDictTracked multiDict, DataFlow::TypeTracker t) {
51-
/*
52-
* using t.startInAttr("getlist") was not good solution
53-
* ```py
54-
* a = request.args
55-
* b = a
56-
* a.getlist("key")
57-
* b.getlist("key")
58-
* ```
59-
* would give `request.args` -> `b.getlist` -- this is correct, but not helpful in a taint-path explanation,
60-
* we REALLY WANT it to be `request.args -> a -> b -> b.getlist`
61-
* This requirement means that we do need the predicate `multiDictTrack`, which could be spared otherwise.
62-
*/
63-
64-
t.start() and
65-
result.asCfgNode().(AttrNode).getObject("getlist") = multiDict.asCfgNode()
66-
or
67-
exists(DataFlow::TypeTracker t2 | result = multiDictGetListTrack(multiDict, t2).track(t2, t))
68-
}
69-
70-
private DataFlow::Node multiDictGetListTrack(MultiDictTracked multiDict) {
71-
result = multiDictGetListTrack(multiDict, DataFlow::TypeTracker::end())
72-
}
73-
74-
private class MultiDictGetListCall extends DataFlow::Node {
75-
MultiDictTracked multiDict;
76-
77-
MultiDictGetListCall() {
78-
this.asCfgNode().(CallNode).getFunction() = multiDictGetListTrack(multiDict).asCfgNode()
28+
exists(DataFlow::TypeTracker t2 | result = getlist(t2).track(t2, t))
7929
}
8030

81-
MultiDictTracked getMultiDict() { result = multiDict }
82-
}
83-
84-
private DataFlow::Node multiDictGetListCallTrack(
85-
MultiDictGetListCall call, DataFlow::TypeTracker t
86-
) {
87-
t.start() and
88-
result = call
89-
or
90-
exists(DataFlow::TypeTracker t2 | result = multiDictGetListCallTrack(call, t2).track(t2, t))
91-
}
92-
93-
/** Gets a reference to the MultiDict attributes of `flask.request`. */
94-
private DataFlow::Node multiDictGetListCallTrack(MultiDictGetListCall call) {
95-
result = multiDictGetListCallTrack(call, DataFlow::TypeTracker::end())
96-
}
97-
98-
private class MultiDictGetListCallResultTracked extends DataFlow::Node, DataFlow::ListLike {
99-
MultiDictGetListCall call;
100-
101-
MultiDictGetListCallResultTracked() { this = multiDictGetListCallTrack(call) }
102-
103-
MultiDictGetListCall getCall() { result = call }
31+
DataFlow::Node getlist() { result = getlist(DataFlow::TypeTracker::end()) }
10432
}
10533

10634
private class MultiDictAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
10735
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
108-
nodeTo.(MultiDictGetListCall).getMultiDict() = nodeFrom.(MultiDictTracked)
36+
// obj -> obj.getlist
37+
nodeTo.asCfgNode().(AttrNode).getObject("getlist") = nodeFrom.asCfgNode() and
38+
nodeTo = MultiDictTracking::getlist()
39+
or
40+
// getlist -> getlist()
41+
nodeFrom = MultiDictTracking::getlist() and
42+
nodeTo.asCfgNode().(CallNode).getFunction() = nodeFrom.asCfgNode()
10943
}
11044
}
11145

@@ -121,6 +55,7 @@ module Werkzeug {
12155

12256
private class FileStorageAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
12357
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
58+
// TODO: should be `nodeFrom = tracked(any(FileStorage fs))`
12459
nodeFrom instanceof FileStorage and
12560
exists(string name |
12661
name in ["filename",

python/ql/test/experimental/library-tests/frameworks/flask/TestTaint.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@
3535
| test.py:65 | ok | test_taint | request.data |
3636
| test.py:68 | ok | test_taint | request.files |
3737
| test.py:69 | ok | test_taint | request.files['key'] |
38-
| test.py:70 | ok | test_taint | request.files['key'].filename |
39-
| test.py:71 | ok | test_taint | request.files['key'].stream |
38+
| test.py:70 | fail | test_taint | request.files['key'].filename |
39+
| test.py:71 | fail | test_taint | request.files['key'].stream |
4040
| test.py:72 | ok | test_taint | request.files.getlist(..) |
41-
| test.py:73 | ok | test_taint | request.files.getlist(..)[0].filename |
42-
| test.py:74 | ok | test_taint | request.files.getlist(..)[0].stream |
41+
| test.py:73 | fail | test_taint | request.files.getlist(..)[0].filename |
42+
| test.py:74 | fail | test_taint | request.files.getlist(..)[0].stream |
4343
| test.py:77 | ok | test_taint | request.form |
4444
| test.py:78 | ok | test_taint | request.form['key'] |
4545
| test.py:79 | ok | test_taint | request.form.getlist(..) |

0 commit comments

Comments
 (0)