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

Skip to content

Commit 7c205dd

Browse files
committed
Python: First attempt at modeling Flask
1 parent cdc5ca7 commit 7c205dd

5 files changed

Lines changed: 436 additions & 1 deletion

File tree

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

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,115 @@ private import experimental.dataflow.DataFlow
77
private import experimental.dataflow.RemoteFlowSources
88
private import experimental.semmle.python.Concepts
99

10-
private module Flask { }
10+
private module Flask {
11+
/** Gets a reference to the `flask` module. */
12+
DataFlow::Node flask(DataFlow::TypeTracker t) {
13+
t.start() and
14+
result = DataFlow::importModule("flask")
15+
or
16+
exists(DataFlow::TypeTracker t2 | result = flask(t2).track(t2, t))
17+
}
18+
19+
/** Gets a reference to the `flask` module. */
20+
DataFlow::Node flask() { result = flask(DataFlow::TypeTracker::end()) }
21+
22+
module flask {
23+
/** Gets a reference to the `flask.request` object. */
24+
DataFlow::Node request(DataFlow::TypeTracker t) {
25+
t.start() and
26+
result = DataFlow::importMember("flask", "request")
27+
or
28+
t.startInAttr("request") and
29+
result = flask()
30+
or
31+
exists(DataFlow::TypeTracker t2 | result = flask::request(t2).track(t2, t))
32+
}
33+
34+
/** Gets a reference to the `flask.request` object. */
35+
DataFlow::Node request() { result = flask::request(DataFlow::TypeTracker::end()) }
36+
}
37+
38+
// TODO: Do we even need this class then? :|
39+
private class RequestSource extends RemoteFlowSource::Range {
40+
RequestSource() { this = flask::request() }
41+
42+
override string getSourceType() { result = "flask.request" }
43+
}
44+
45+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict
46+
/** Gets a reference to the MultiDict attributes of `flask.request`. */
47+
DataFlow::Node requestMultiDictAttribute(DataFlow::TypeTracker t) {
48+
t.start() and
49+
result.asCfgNode().(AttrNode).getObject(["args", "values", "form"]) =
50+
flask::request().asCfgNode()
51+
or
52+
exists(DataFlow::TypeTracker t2 | result = requestMultiDictAttribute(t2).track(t2, t))
53+
}
54+
55+
/** Gets a reference to the MultiDict attributes of `flask.request`. */
56+
DataFlow::Node requestMultiDictAttribute() {
57+
result = requestMultiDictAttribute(DataFlow::TypeTracker::end())
58+
}
59+
60+
private class RequestInputAccess extends RemoteFlowSource::Range {
61+
RequestInputAccess() {
62+
// attributes
63+
exists(AttrNode attr, string name |
64+
this.asCfgNode() = attr and attr.getObject(name) = flask::request().asCfgNode()
65+
|
66+
name in ["path",
67+
// string
68+
"full_path", "base_url", "url", "access_control_request_method", "content_encoding",
69+
"content_md5", "content_type", "data", "method", "mimetype", "origin", "query_string",
70+
"referrer", "remote_addr", "remote_user", "user_agent",
71+
// dict
72+
"environ", "cookies", "mimetype_params", "view_args",
73+
//
74+
"args", "values", "form",
75+
// json
76+
"json",
77+
// List[str]
78+
"access_route",
79+
// file-like
80+
"stream", "input_stream",
81+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.HeaderSet
82+
"access_control_request_headers", "pragma",
83+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Accept
84+
// TODO: Kinda badly modeled for now -- has type List[Tuple[value, quality]], and some extra methods
85+
"accept_charsets", "accept_encodings", "accept_languages", "accept_mimetypes",
86+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Authorization
87+
// TODO: dict subclass with extra attributes like `username` and `password`
88+
"authorization",
89+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.RequestCacheControl
90+
// TODO: has attributes like `no_cache`, and `to_header` method (actually, many of these models do)
91+
"cache_control",
92+
// TODO: MultiDict[FileStorage]
93+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage
94+
"files",
95+
// https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers
96+
// TODO: dict-like with wsgiref.headers.Header compatibility methods
97+
"headers"]
98+
)
99+
or
100+
// methods
101+
exists(CallNode call, string name | this.asCfgNode() = call |
102+
// NOTE: will not track bound method, `f = func; f()`
103+
name in ["get_data", "get_json"] and
104+
call.getFunction().(AttrNode).getObject(name) = flask::request().asCfgNode()
105+
)
106+
or
107+
// multi dict special handling
108+
(
109+
this = requestMultiDictAttribute()
110+
or
111+
exists(CallNode call | this.asCfgNode() = call |
112+
// NOTE: will not track bound method, `f = func; f()`
113+
call.getFunction().(AttrNode).getObject("getlist") =
114+
requestMultiDictAttribute().asCfgNode()
115+
)
116+
)
117+
}
118+
119+
override string getSourceType() { result = "flask.request input" }
120+
}
121+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
| test.py:6 | fail | test_taint | name |
2+
| test.py:6 | fail | test_taint | number |
3+
| test.py:7 | ok | test_taint | foo |
4+
| test.py:14 | ok | test_taint | request.environ |
5+
| test.py:15 | ok | test_taint | request.environ.get(..) |
6+
| test.py:17 | ok | test_taint | request.path |
7+
| test.py:18 | ok | test_taint | request.full_path |
8+
| test.py:19 | ok | test_taint | request.base_url |
9+
| test.py:20 | ok | test_taint | request.url |
10+
| test.py:23 | fail | test_taint | request.accept_charsets.best |
11+
| test.py:24 | fail | test_taint | request.accept_charsets.best_match(..) |
12+
| test.py:25 | ok | test_taint | request.accept_charsets[0] |
13+
| test.py:26 | ok | test_taint | request.accept_encodings |
14+
| test.py:27 | ok | test_taint | request.accept_languages |
15+
| test.py:28 | ok | test_taint | request.accept_mimetypes |
16+
| test.py:31 | ok | test_taint | request.access_control_request_headers |
17+
| test.py:33 | ok | test_taint | request.access_control_request_method |
18+
| test.py:35 | ok | test_taint | request.access_route |
19+
| test.py:36 | ok | test_taint | request.access_route[0] |
20+
| test.py:39 | ok | test_taint | request.args |
21+
| test.py:40 | ok | test_taint | request.args['key'] |
22+
| test.py:41 | ok | test_taint | request.args.getlist(..) |
23+
| test.py:44 | ok | test_taint | request.authorization |
24+
| test.py:45 | ok | test_taint | request.authorization['username'] |
25+
| test.py:46 | fail | test_taint | request.authorization.username |
26+
| test.py:49 | ok | test_taint | request.cache_control |
27+
| test.py:51 | fail | test_taint | request.cache_control.max_age |
28+
| test.py:52 | fail | test_taint | request.cache_control.max_stale |
29+
| test.py:53 | fail | test_taint | request.cache_control.min_fresh |
30+
| test.py:55 | ok | test_taint | request.content_encoding |
31+
| test.py:57 | ok | test_taint | request.content_md5 |
32+
| test.py:59 | ok | test_taint | request.content_type |
33+
| test.py:62 | ok | test_taint | request.cookies |
34+
| test.py:63 | ok | test_taint | request.cookies['key'] |
35+
| test.py:65 | ok | test_taint | request.data |
36+
| test.py:68 | ok | test_taint | request.files |
37+
| test.py:69 | ok | test_taint | request.files['key'] |
38+
| test.py:70 | fail | test_taint | request.files['key'].filename |
39+
| test.py:71 | fail | test_taint | request.files['key'].stream |
40+
| test.py:72 | fail | test_taint | request.files.getlist(..) |
41+
| test.py:75 | ok | test_taint | request.form |
42+
| test.py:76 | ok | test_taint | request.form['key'] |
43+
| test.py:77 | ok | test_taint | request.form.getlist(..) |
44+
| test.py:79 | ok | test_taint | request.get_data() |
45+
| test.py:81 | ok | test_taint | request.get_json() |
46+
| test.py:82 | ok | test_taint | request.get_json()['foo'] |
47+
| test.py:83 | ok | test_taint | request.get_json()['foo']['bar'] |
48+
| test.py:87 | ok | test_taint | request.headers |
49+
| test.py:88 | ok | test_taint | request.headers['key'] |
50+
| test.py:89 | fail | test_taint | request.headers.get_all(..) |
51+
| test.py:90 | fail | test_taint | request.headers.getlist(..) |
52+
| test.py:91 | ok | test_taint | list(..) |
53+
| test.py:92 | fail | test_taint | request.headers.to_wsgi_list() |
54+
| test.py:94 | ok | test_taint | request.json |
55+
| test.py:95 | ok | test_taint | request.json['foo'] |
56+
| test.py:96 | ok | test_taint | request.json['foo']['bar'] |
57+
| test.py:98 | ok | test_taint | request.method |
58+
| test.py:100 | ok | test_taint | request.mimetype |
59+
| test.py:102 | ok | test_taint | request.mimetype_params |
60+
| test.py:104 | ok | test_taint | request.origin |
61+
| test.py:107 | ok | test_taint | request.pragma |
62+
| test.py:109 | ok | test_taint | request.query_string |
63+
| test.py:111 | ok | test_taint | request.referrer |
64+
| test.py:113 | ok | test_taint | request.remote_addr |
65+
| test.py:115 | ok | test_taint | request.remote_user |
66+
| test.py:118 | ok | test_taint | request.stream |
67+
| test.py:119 | ok | test_taint | request.input_stream |
68+
| test.py:121 | ok | test_taint | request.url |
69+
| test.py:123 | ok | test_taint | request.user_agent |
70+
| test.py:126 | ok | test_taint | request.values |
71+
| test.py:127 | ok | test_taint | request.values['key'] |
72+
| test.py:128 | ok | test_taint | request.values.getlist(..) |
73+
| test.py:131 | ok | test_taint | request.view_args |
74+
| test.py:132 | ok | test_taint | request.view_args['key'] |
75+
| test.py:136 | ok | test_taint | request.script_root |
76+
| test.py:137 | ok | test_taint | request.url_root |
77+
| test.py:141 | ok | test_taint | request.charset |
78+
| test.py:142 | ok | test_taint | request.url_charset |
79+
| test.py:146 | ok | test_taint | request.date |
80+
| test.py:149 | ok | test_taint | request.endpoint |
81+
| test.py:154 | ok | test_taint | request.host |
82+
| test.py:155 | ok | test_taint | request.host_url |
83+
| test.py:157 | ok | test_taint | request.scheme |
84+
| test.py:159 | ok | test_taint | request.script_root |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import experimental.dataflow.tainttracking.TestTaintLib
2+
import experimental.dataflow.RemoteFlowSources
3+
4+
class RemoteFlowTestTaintConfiguration extends TestTaintTrackingConfiguration {
5+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
6+
}

0 commit comments

Comments
 (0)