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

Skip to content

Commit 8476bc7

Browse files
committed
Python: correctly handle flask.make_response
Fixes #1572 Adjust mock so it's more aligned with what the flask code actually does. Tests were passing before, even though we didn't handle the case in real code :\
1 parent 002190f commit 8476bc7

7 files changed

Lines changed: 42 additions & 26 deletions

File tree

python/ql/src/semmle/python/web/flask/General.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import python
22
import semmle.python.web.Http
3+
import semmle.python.web.flask.Response
34

45
/** The flask app class */
56
ClassValue theFlaskClass() { result = Value::named("flask.Flask") }
@@ -92,7 +93,7 @@ private class AsView extends TaintSource {
9293

9394
class FlaskCookieSet extends CookieSet, CallNode {
9495
FlaskCookieSet() {
95-
this.getFunction().(AttrNode).getObject("set_cookie").pointsTo().getClass() = theFlaskReponseClass()
96+
any(FlaskResponseTaintKind t).taints(this.getFunction().(AttrNode).getObject("set_cookie"))
9697
}
9798

9899
override string toString() { result = CallNode.super.toString() }

python/ql/src/semmle/python/web/flask/Response.qll

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ class FlaskRoutedResponse extends HttpResponseTaintSink {
2323
class FlaskResponseArgument extends HttpResponseTaintSink {
2424
FlaskResponseArgument() {
2525
exists(CallNode call |
26-
call.getFunction().pointsTo(theFlaskReponseClass()) and
26+
(
27+
call.getFunction().pointsTo(theFlaskReponseClass())
28+
or
29+
call.getFunction().pointsTo(Value::named("flask.make_response"))
30+
) and
2731
call.getArg(0) = this
2832
)
2933
}
@@ -32,3 +36,20 @@ class FlaskResponseArgument extends HttpResponseTaintSink {
3236

3337
override string toString() { result = "flask.response.argument" }
3438
}
39+
40+
class FlaskResponseTaintKind extends TaintKind {
41+
FlaskResponseTaintKind() { this = "flask.Response" }
42+
}
43+
44+
class FlaskResponseConfiguration extends TaintTracking::Configuration {
45+
FlaskResponseConfiguration() { this = "Flask response configuration" }
46+
47+
override predicate isSource(DataFlow::Node node, TaintKind kind) {
48+
kind instanceof FlaskResponseTaintKind and
49+
(
50+
node.asCfgNode().(CallNode).getFunction().pointsTo(theFlaskReponseClass())
51+
or
52+
node.asCfgNode().(CallNode).getFunction().pointsTo(Value::named("flask.make_response"))
53+
)
54+
}
55+
}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
edges
2-
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string |
3-
| ../lib/flask/__init__.py:14:19:14:20 | externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string |
42
| reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string |
53
| reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:7:18:7:45 | externally controlled string |
64
| reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string |
75
| reflected_xss.py:7:18:7:45 | externally controlled string | reflected_xss.py:8:44:8:53 | externally controlled string |
8-
| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string |
9-
| reflected_xss.py:8:26:8:53 | externally controlled string | ../lib/flask/__init__.py:14:19:14:20 | externally controlled string |
106
| reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
117
| reflected_xss.py:8:44:8:53 | externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string |
128
#select
13-
| ../lib/flask/__init__.py:16:25:16:26 | rv | reflected_xss.py:7:18:7:29 | dict of externally controlled string | ../lib/flask/__init__.py:16:25:16:26 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | Attribute | user-provided value |
9+
| reflected_xss.py:8:26:8:53 | BinaryExpr | reflected_xss.py:7:18:7:29 | dict of externally controlled string | reflected_xss.py:8:26:8:53 | externally controlled string | Cross-site scripting vulnerability due to $@. | reflected_xss.py:7:18:7:29 | Attribute | user-provided value |

python/ql/test/query-tests/Security/CWE-079/reflected_xss.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,3 @@ def unsafe():
1111
def safe():
1212
first_name = request.args.get('name', '')
1313
return make_response("Your name is " + escape(first_name))
14-
15-
urlpatterns = [
16-
url(r'^r1$', response_unsafe, name='response-unsafe'),
17-
url(r'^r2$', response_safe, name='response-safe')
18-
]
Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
from .globals import request
2+
from .globals import current_app
13

4+
class Flask(object):
5+
# Only some methods mocked, signature copied from
6+
# https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask
7+
def run(host=None, port=None, debug=None, load_dotenv=True, **options):
8+
pass
29

3-
class Flask(object):
4-
def run(self, *args, **kwargs): pass
10+
def make_response(rv):
11+
pass
512

6-
from .globals import request
13+
def add_url_rule(rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
14+
pass
715

816
class Response(object):
917
pass
@@ -12,12 +20,11 @@ def redirect(location, code=302, Response=None):
1220
pass
1321

1422
def make_response(rv):
15-
if isinstance(rv, str):
16-
return Response(rv)
17-
elif isinstance(rv, Response):
18-
return rv
19-
else:
20-
pass
23+
if not args:
24+
return current_app.response_class()
25+
if len(args) == 1:
26+
args = args[0]
27+
return current_app.make_response(args)
2128

2229
def escape(txt):
2330
return Markup.escape(txt)

python/ql/test/query-tests/Security/lib/flask/globals.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ class LocalProxy(object):
33
pass
44

55
request = LocalProxy()
6+
current_app = LocalProxy()
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
2-
3-
41
class View(object):
52
pass
63

74

85
class MethodView(object):
96
pass
10-
11-

0 commit comments

Comments
 (0)