Skip to content

Commit b9984be

Browse files
Add test cases
1 parent 68d9091 commit b9984be

File tree

5 files changed

+42
-16
lines changed

5 files changed

+42
-16
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,13 @@ module Flask {
222222
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
223223

224224
/** An `Headers` instance that is part of a Flask response. */
225-
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource {
226-
FlaskResponseHeadersInstances() { this = request().getMember("headers").asSource() }
225+
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource
226+
{
227+
FlaskResponseHeadersInstances() {
228+
this.(DataFlow::AttrRead).getObject() = instance() and
229+
this.(DataFlow::AttrRead).getAttributeName() = "headers"
230+
}
227231
}
228-
229232
// TODO: headers arg to make_response
230233
}
231234

python/ql/lib/semmle/python/frameworks/Werkzeug.qll

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ module Werkzeug {
188188
DataFlow::MethodCallNode
189189
{
190190
HeaderWriteCall() {
191-
this.getObject() = instance() and
192-
this.getMethodName() = ["add", "add_header", "set", "set_default", "__setitem__"]
191+
this.calls(instance(), ["add", "add_header", "set", "set_default", "__setitem__"])
193192
}
194193

195194
override DataFlow::Node getNameArg() { result = this.getArg(0) }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
edges
2+
| flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:1:29:1:35 | ControlFlowNode for request | provenance | |
3+
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:20:18:20:24 | ControlFlowNode for request | provenance | |
4+
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:29:18:29:24 | ControlFlowNode for request | provenance | |
5+
| flask_tests.py:20:5:20:14 | ControlFlowNode for rfs_header | flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | provenance | |
6+
| flask_tests.py:20:18:20:24 | ControlFlowNode for request | flask_tests.py:20:5:20:14 | ControlFlowNode for rfs_header | provenance | |
7+
| flask_tests.py:29:5:29:14 | ControlFlowNode for rfs_header | flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | provenance | |
8+
| flask_tests.py:29:18:29:24 | ControlFlowNode for request | flask_tests.py:29:5:29:14 | ControlFlowNode for rfs_header | provenance | |
9+
nodes
10+
| flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
11+
| flask_tests.py:1:29:1:35 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
12+
| flask_tests.py:20:5:20:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
13+
| flask_tests.py:20:18:20:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
14+
| flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
15+
| flask_tests.py:29:5:29:14 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
16+
| flask_tests.py:29:18:29:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
17+
| flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
18+
subpaths
19+
#select
20+
| flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:23:22:23:31 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
21+
| flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:32:22:32:31 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-113/HeaderInjection.ql

python/ql/test/experimental/query-tests/Security/CWE-113/flask_bad.py renamed to python/ql/test/query-tests/Security/CWE-113-HeaderInjection/flask_tests.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,41 @@ def werkzeug_headers():
99
rfs_header = request.args["rfs_header"]
1010
response = Response()
1111
headers = Headers()
12-
headers.add("HeaderName", rfs_header)
13-
response.headers = headers
12+
headers.add("HeaderName", rfs_header) # GOOD: Newlines are rejected from header value.
13+
headers.add(rfs_header, "HeaderValue") # BAD: User controls header name. Not yet found.
14+
response.headers = headers
1415
return response
1516

1617

1718
@app.route("/flask_Response")
1819
def flask_Response():
1920
rfs_header = request.args["rfs_header"]
2021
response = Response()
21-
response.headers['HeaderName'] = rfs_header
22+
response.headers['HeaderName'] = rfs_header # GOOD
23+
response.headers[rfs_header] = "HeaderValue" # BAD
2224
return response
2325

2426

2527
@app.route("/flask_make_response")
2628
def flask_make_response():
2729
rfs_header = request.args["rfs_header"]
28-
resp = make_response("hello")
29-
resp.headers['HeaderName'] = rfs_header
30-
return resp
30+
response = make_response("hello")
31+
response.headers['HeaderName'] = rfs_header # GOOD
32+
response.headers[rfs_header] = "HeaderValue" # BAD
33+
return response
3134

3235

3336
@app.route("/flask_make_response_extend")
3437
def flask_make_response_extend():
3538
rfs_header = request.args["rfs_header"]
3639
resp = make_response("hello")
3740
resp.headers.extend(
38-
{'HeaderName': rfs_header})
41+
{'HeaderName': rfs_header}) # GOOD
42+
resp.headers.extend(
43+
{rfs_header: "HeaderValue"}) # BAD but not yet found
3944
return resp
4045

4146

4247
@app.route("/Response_arg")
4348
def Response_arg():
44-
return Response(headers={'HeaderName': request.args["rfs_header"]})
45-
46-
# if __name__ == "__main__":
47-
# app.run(debug=True)
49+
return Response(headers={'HeaderName': request.args["rfs_header"], request.args["rfs_header"]: "HeaderValue"}) # BAD but not yet found

0 commit comments

Comments
 (0)