Skip to content

Commit 71da217

Browse files
authored
Merge pull request #10535 from RasmusWL/flask-jsonify
Python: Model `flask.jsonify`
2 parents 11ba0f0 + d3f811c commit 71da217

File tree

5 files changed

+39
-3
lines changed

5 files changed

+39
-3
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added modeling of creating Flask responses with `flask.jsonify`.

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,22 @@ module Flask {
171171
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
172172
}
173173

174+
/**
175+
* A call to `flask.jsonify` function. This creates a JSON response.
176+
*
177+
* See
178+
* - https://flask.palletsprojects.com/en/2.2.x/api/#flask.json.jsonify
179+
*/
180+
private class FlaskJsonifyCall extends InstanceSource, DataFlow::CallCfgNode {
181+
FlaskJsonifyCall() { this = API::moduleImport("flask").getMember("jsonify").getACall() }
182+
183+
override DataFlow::Node getBody() { result in [this.getArg(_), this.getArgByName(_)] }
184+
185+
override string getMimetypeDefault() { result = "application/json" }
186+
187+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
188+
}
189+
174190
/** Gets a reference to an instance of `flask.Response`. */
175191
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
176192
t.start() and

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def html8(): # $requestHandler
6666

6767
@app.route("/jsonify") # $routeSetup="/jsonify"
6868
def jsonify_route(): # $requestHandler
69-
data = {"foo": "bar"}
70-
resp = jsonify(data) # $ MISSING: HttpResponse mimetype=application/json responseBody=data
69+
x = "x"; y = "y"; z = "z"
70+
resp = jsonify(x, y, z=z) # $ HttpResponse mimetype=application/json responseBody=x responseBody=y responseBody=z
7171
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
7272

7373
################################################################################

python/ql/test/query-tests/Security/CWE-209-StackTraceExposure/StackTraceExposure.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edges
55
| test.py:50:29:50:31 | ControlFlowNode for err | test.py:50:16:50:32 | ControlFlowNode for format_error() |
66
| test.py:50:29:50:31 | ControlFlowNode for err | test.py:52:18:52:20 | ControlFlowNode for msg |
77
| test.py:52:18:52:20 | ControlFlowNode for msg | test.py:53:12:53:27 | ControlFlowNode for BinaryExpr |
8+
| test.py:65:25:65:25 | SSA variable e | test.py:66:24:66:40 | ControlFlowNode for Dict |
89
nodes
910
| test.py:16:16:16:37 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
1011
| test.py:23:25:23:25 | SSA variable e | semmle.label | SSA variable e |
@@ -16,10 +17,13 @@ nodes
1617
| test.py:50:29:50:31 | ControlFlowNode for err | semmle.label | ControlFlowNode for err |
1718
| test.py:52:18:52:20 | ControlFlowNode for msg | semmle.label | ControlFlowNode for msg |
1819
| test.py:53:12:53:27 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
20+
| test.py:65:25:65:25 | SSA variable e | semmle.label | SSA variable e |
21+
| test.py:66:24:66:40 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
1922
subpaths
2023
| test.py:50:29:50:31 | ControlFlowNode for err | test.py:52:18:52:20 | ControlFlowNode for msg | test.py:53:12:53:27 | ControlFlowNode for BinaryExpr | test.py:50:16:50:32 | ControlFlowNode for format_error() |
2124
#select
2225
| test.py:16:16:16:37 | ControlFlowNode for Attribute() | test.py:16:16:16:37 | ControlFlowNode for Attribute() | test.py:16:16:16:37 | ControlFlowNode for Attribute() | $@ flows to this location and may be exposed to an external user. | test.py:16:16:16:37 | ControlFlowNode for Attribute() | Stack trace information |
2326
| test.py:24:16:24:16 | ControlFlowNode for e | test.py:23:25:23:25 | SSA variable e | test.py:24:16:24:16 | ControlFlowNode for e | $@ flows to this location and may be exposed to an external user. | test.py:23:25:23:25 | SSA variable e | Stack trace information |
2427
| test.py:32:16:32:30 | ControlFlowNode for Attribute | test.py:31:25:31:25 | SSA variable e | test.py:32:16:32:30 | ControlFlowNode for Attribute | $@ flows to this location and may be exposed to an external user. | test.py:31:25:31:25 | SSA variable e | Stack trace information |
2528
| test.py:50:16:50:32 | ControlFlowNode for format_error() | test.py:49:15:49:36 | ControlFlowNode for Attribute() | test.py:50:16:50:32 | ControlFlowNode for format_error() | $@ flows to this location and may be exposed to an external user. | test.py:49:15:49:36 | ControlFlowNode for Attribute() | Stack trace information |
29+
| test.py:66:24:66:40 | ControlFlowNode for Dict | test.py:65:25:65:25 | SSA variable e | test.py:66:24:66:40 | ControlFlowNode for Dict | $@ flows to this location and may be exposed to an external user. | test.py:65:25:65:25 | SSA variable e | Stack trace information |

python/ql/test/query-tests/Security/CWE-209-StackTraceExposure/test.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from flask import Flask, request, make_response
1+
from flask import Flask, request, make_response, jsonify
22
app = Flask(__name__)
33

44

@@ -56,3 +56,15 @@ def format_error(msg):
5656
@app.route('/maybe_xss')
5757
def maybe_xss():
5858
return make_response(request.args.get('name', ''))
59+
60+
# BAD
61+
@app.route('/bad/jsonify')
62+
def bad_jsonify():
63+
try:
64+
do_computation()
65+
except Exception as e: # $ exceptionInfo
66+
return jsonify({"error": str(e)})
67+
68+
69+
if __name__ == "__main__":
70+
app.run(debug=True)

0 commit comments

Comments
 (0)