Skip to content

Commit 9499edf

Browse files
authored
Merge pull request github#5078 from RasmusWL/flask-blueprints
Python: Add modeling of Flask blueprints
2 parents c32e54e + bc8e613 commit 9499edf

File tree

4 files changed

+72
-4
lines changed

4 files changed

+72
-4
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* Added modeling of flask blueprints (`flask.Blueprint`), specifically request handlers defined with such blueprints. This can result in new sources of remote user input (`RemoteFlowSource`) -- since we're now able to detect routed parameters -- and new XSS sinks from the responses of these request handlers.

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

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ private module FlaskModel {
7272
API::Node response_class() { result = [classRef(), instance()].getMember("response_class") }
7373
}
7474

75+
/**
76+
* Provides models for the `flask.Blueprint` class
77+
*
78+
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Blueprint.
79+
*/
80+
module Blueprint {
81+
/** Gets a reference to the `flask.Blueprint` class. */
82+
API::Node classRef() { result = flask().getMember("Blueprint") }
83+
84+
/** Gets a reference to an instance of `flask.Blueprint`. */
85+
API::Node instance() { result = classRef().getReturn() }
86+
87+
/**
88+
* Gets a reference to the attribute `attr_name` of an instance of `flask.Blueprint`.
89+
*/
90+
private API::Node instance_attr(string attr_name) { result = instance().getMember(attr_name) }
91+
92+
/** Gets a reference to the `route` method on an instance of `flask.Blueprint`. */
93+
API::Node route() { result = instance_attr("route") }
94+
95+
/** Gets a reference to the `add_url_rule` method on an instance of `flask.Blueprint`. */
96+
API::Node add_url_rule() { result = instance_attr("add_url_rule") }
97+
}
98+
7599
// -------------------------------------------------------------------------
76100
// flask.views
77101
// -------------------------------------------------------------------------
@@ -222,12 +246,16 @@ private module FlaskModel {
222246
}
223247

224248
/**
225-
* A call to the `route` method on an instance of `flask.Flask`.
249+
* A call to the `route` method on an instance of `flask.Flask` or an instance of `flask.Blueprint`.
226250
*
227251
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.route
228252
*/
229253
private class FlaskAppRouteCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
230-
FlaskAppRouteCall() { this.getFunction() = flask::Flask::route().getAUse() }
254+
FlaskAppRouteCall() {
255+
this.getFunction() = flask::Flask::route().getAUse()
256+
or
257+
this.getFunction() = flask::Blueprint::route().getAUse()
258+
}
231259

232260
override DataFlow::Node getUrlPatternArg() {
233261
result in [this.getArg(0), this.getArgByName("rule")]
@@ -237,12 +265,16 @@ private module FlaskModel {
237265
}
238266

239267
/**
240-
* A call to the `add_url_rule` method on an instance of `flask.Flask`.
268+
* A call to the `add_url_rule` method on an instance of `flask.Flask` or an instance of `flask.Blueprint`.
241269
*
242270
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
243271
*/
244272
private class FlaskAppAddUrlRuleCall extends FlaskRouteSetup, DataFlow::CallCfgNode {
245-
FlaskAppAddUrlRuleCall() { this.getFunction() = flask::Flask::add_url_rule().getAUse() }
273+
FlaskAppAddUrlRuleCall() {
274+
this.getFunction() = flask::Flask::add_url_rule().getAUse()
275+
or
276+
this.getFunction() = flask::Blueprint::add_url_rule().getAUse()
277+
}
246278

247279
override DataFlow::Node getUrlPatternArg() {
248280
result in [this.getArg(0), this.getArgByName("rule")]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import flask
2+
3+
bp3 = flask.Blueprint("bp3", __name__)
4+
5+
@bp3.route("/bp3/example") # $ routeSetup="/bp3/example"
6+
def bp3_example(): # $ requestHandler
7+
return "bp 3 example" # $ HttpResponse

python/ql/test/experimental/library-tests/frameworks/flask/routing_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,32 @@ def get(self, foo, not_routed=42): # $ requestHandler routedParameter=foo SPURI
9393
pass
9494

9595

96+
# Blueprints
97+
#
98+
# see https://flask.palletsprojects.com/en/1.1.x/blueprints/
99+
100+
bp1 = flask.Blueprint("bp1", __name__)
101+
102+
@bp1.route("/bp1/example/<foo>") # $ routeSetup="/bp1/example/<foo>"
103+
def bp1_example(foo): # $ requestHandler routedParameter=foo
104+
return "bp 1 example foo={}".format(foo) # $ HttpResponse
105+
106+
app.register_blueprint(bp1) # by default, URLs of blueprints are not prefixed
107+
108+
109+
bp2 = flask.Blueprint("bp2", __name__)
110+
111+
@bp2.route("/example") # $ routeSetup="/example"
112+
def bp2_example(): # $ requestHandler
113+
return "bp 2 example" # $ HttpResponse
114+
115+
app.register_blueprint(bp2, url_prefix="/bp2") # but it is possible to add a URL prefix
116+
117+
118+
from external_blueprint import bp3
119+
app.register_blueprint(bp3)
120+
121+
96122
if __name__ == "__main__":
123+
print(app.url_map)
97124
app.run(debug=True)

0 commit comments

Comments
 (0)