@@ -15,6 +15,9 @@ private import experimental.semmle.python.frameworks.Werkzeug
1515 * See https://flask.palletsprojects.com/en/1.1.x/.
1616 */
1717private module FlaskModel {
18+ // ---------------------------------------------------------------------------
19+ // flask
20+ // ---------------------------------------------------------------------------
1821 /** Gets a reference to the `flask` module. */
1922 private DataFlow:: Node flask ( DataFlow:: TypeTracker t ) {
2023 t .start ( ) and
@@ -26,21 +29,52 @@ private module FlaskModel {
2629 /** Gets a reference to the `flask` module. */
2730 DataFlow:: Node flask ( ) { result = flask ( DataFlow:: TypeTracker:: end ( ) ) }
2831
29- /** Provides models for the `flask` module. */
30- module flask {
31- /** Gets a reference to the `flask.request` object. */
32- private DataFlow:: Node request ( DataFlow:: TypeTracker t ) {
32+ /**
33+ * Gets a reference to the attribute `attr_name` of the `flask` module.
34+ * WARNING: Only holds for a few predefined attributes.
35+ */
36+ private DataFlow:: Node flask_attr ( DataFlow:: TypeTracker t , string attr_name ) {
37+ attr_name in [ "request" , "make_response" , "Response" ] and
38+ (
3339 t .start ( ) and
34- result = DataFlow:: importNode ( "flask.request" )
40+ result = DataFlow:: importNode ( "flask" + "." + attr_name )
3541 or
36- t .startInAttr ( "request" ) and
42+ t .startInAttr ( attr_name ) and
3743 result = flask ( )
38- or
39- exists ( DataFlow:: TypeTracker t2 | result = request ( t2 ) .track ( t2 , t ) )
40- }
44+ )
45+ or
46+ // Due to bad performance when using normal setup with `flask_attr(t2, attr_name).track(t2, t)`
47+ // we have inlined that code and forced a join
48+ exists ( DataFlow:: TypeTracker t2 |
49+ exists ( DataFlow:: StepSummary summary |
50+ flask_attr_first_join ( t2 , attr_name , result , summary ) and
51+ t = t2 .append ( summary )
52+ )
53+ )
54+ }
55+
56+ pragma [ nomagic]
57+ private predicate flask_attr_first_join (
58+ DataFlow:: TypeTracker t2 , string attr_name , DataFlow:: Node res , DataFlow:: StepSummary summary
59+ ) {
60+ DataFlow:: StepSummary:: step ( flask_attr ( t2 , attr_name ) , res , summary )
61+ }
62+
63+ /**
64+ * Gets a reference to the attribute `attr_name` of the `flask` module.
65+ * WARNING: Only holds for a few predefined attributes.
66+ */
67+ private DataFlow:: Node flask_attr ( string attr_name ) {
68+ result = flask_attr ( DataFlow:: TypeTracker:: end ( ) , attr_name )
69+ }
4170
71+ /** Provides models for the `flask` module. */
72+ module flask {
4273 /** Gets a reference to the `flask.request` object. */
43- DataFlow:: Node request ( ) { result = request ( DataFlow:: TypeTracker:: end ( ) ) }
74+ DataFlow:: Node request ( ) { result = flask_attr ( "request" ) }
75+
76+ /** Gets a reference to the `flask.make_response` function. */
77+ DataFlow:: Node make_response ( ) { result = flask_attr ( "make_response" ) }
4478
4579 /**
4680 * Provides models for the `flask.Flask` class
@@ -96,7 +130,7 @@ private module FlaskModel {
96130 * WARNING: Only holds for a few predefined attributes.
97131 */
98132 private DataFlow:: Node instance_attr ( DataFlow:: TypeTracker t , string attr_name ) {
99- attr_name in [ "route" , "add_url_rule" ] and
133+ attr_name in [ "route" , "add_url_rule" , "make_response" ] and
100134 t .startInAttr ( attr_name ) and
101135 result = flask:: Flask:: instance ( )
102136 or
@@ -131,9 +165,99 @@ private module FlaskModel {
131165
132166 /** Gets a reference to the `add_url_rule` method on an instance of `flask.Flask`. */
133167 DataFlow:: Node add_url_rule ( ) { result = instance_attr ( "add_url_rule" ) }
168+
169+ /** Gets a reference to the `make_response` method on an instance of `flask.Flask`. */
170+ // HACK: We can't call this predicate `make_response` since shadowing is
171+ // completely disallowed in QL. I added an underscore to move thing forwards for
172+ // now :(
173+ DataFlow:: Node make_response_ ( ) { result = instance_attr ( "make_response" ) }
174+
175+ /** Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance. */
176+ private DataFlow:: Node response_class ( DataFlow:: TypeTracker t ) {
177+ t .startInAttr ( "response_class" ) and
178+ result in [ classRef ( ) , instance ( ) ]
179+ or
180+ exists ( DataFlow:: TypeTracker t2 | result = response_class ( t2 ) .track ( t2 , t ) )
181+ }
182+
183+ /**
184+ * Gets a reference to the `response_class` attribute on the `flask.Flask` class or an instance.
185+ *
186+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.response_class
187+ */
188+ DataFlow:: Node response_class ( ) { result = response_class ( DataFlow:: TypeTracker:: end ( ) ) }
134189 }
135190 }
136191
192+ /**
193+ * Provides models for the `flask.Response` class
194+ *
195+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Response.
196+ */
197+ module Response {
198+ /** Gets a reference to the `flask.Response` class. */
199+ private DataFlow:: Node classRef ( DataFlow:: TypeTracker t ) {
200+ t .start ( ) and
201+ result in [ flask_attr ( "Response" ) , flask:: Flask:: response_class ( ) ]
202+ or
203+ exists ( DataFlow:: TypeTracker t2 | result = classRef ( t2 ) .track ( t2 , t ) )
204+ }
205+
206+ /** Gets a reference to the `flask.Response` class. */
207+ DataFlow:: Node classRef ( ) { result = classRef ( DataFlow:: TypeTracker:: end ( ) ) }
208+
209+ /**
210+ * A source of an instance of `flask.Response`.
211+ *
212+ * This can include instantiation of the class, return value from function
213+ * calls, or a special parameter that will be set when functions are call by external
214+ * library.
215+ *
216+ * Use `Response::instance()` predicate to get references to instances of `flask.Response`.
217+ */
218+ abstract class InstanceSource extends HTTP:: Server:: HttpResponse:: Range , DataFlow:: Node { }
219+
220+ /** A direct instantiation of `flask.Response`. */
221+ private class ClassInstantiation extends InstanceSource , DataFlow:: CfgNode {
222+ override CallNode node ;
223+
224+ ClassInstantiation ( ) { node .getFunction ( ) = classRef ( ) .asCfgNode ( ) }
225+
226+ override DataFlow:: Node getBody ( ) { result .asCfgNode ( ) = node .getArg ( 0 ) }
227+
228+ override string getMimetypeDefault ( ) { result = "text/html" }
229+
230+ /** Gets the argument passed to the `mimetype` parameter, if any. */
231+ private DataFlow:: Node getMimetypeArg ( ) {
232+ result .asCfgNode ( ) in [ node .getArg ( 3 ) , node .getArgByName ( "mimetype" ) ]
233+ }
234+
235+ /** Gets the argument passed to the `content_type` parameter, if any. */
236+ private DataFlow:: Node getContentTypeArg ( ) {
237+ result .asCfgNode ( ) in [ node .getArg ( 4 ) , node .getArgByName ( "content_type" ) ]
238+ }
239+
240+ override DataFlow:: Node getMimetypeOrContentTypeArg ( ) {
241+ result = this .getContentTypeArg ( )
242+ or
243+ // content_type argument takes priority over mimetype argument
244+ not exists ( this .getContentTypeArg ( ) ) and
245+ result = this .getMimetypeArg ( )
246+ }
247+ }
248+
249+ /** Gets a reference to an instance of `flask.Response`. */
250+ private DataFlow:: Node instance ( DataFlow:: TypeTracker t ) {
251+ t .start ( ) and
252+ result instanceof InstanceSource
253+ or
254+ exists ( DataFlow:: TypeTracker t2 | result = instance ( t2 ) .track ( t2 , t ) )
255+ }
256+
257+ /** Gets a reference to an instance of `flask.Response`. */
258+ DataFlow:: Node instance ( ) { result = instance ( DataFlow:: TypeTracker:: end ( ) ) }
259+ }
260+
137261 // ---------------------------------------------------------------------------
138262 // routing modeling
139263 // ---------------------------------------------------------------------------
@@ -324,8 +448,50 @@ private module FlaskModel {
324448 private class RequestInputFiles extends RequestInputMultiDict {
325449 RequestInputFiles ( ) { attr_name = "files" }
326450 }
451+
327452 // TODO: Somehow specify that elements of `RequestInputFiles` are
328453 // Werkzeug::werkzeug::datastructures::FileStorage and should have those additional taint steps
329454 // AND that the 0-indexed argument to its' save method is a sink for path-injection.
330455 // https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.save
456+ // ---------------------------------------------------------------------------
457+ // Response modeling
458+ // ---------------------------------------------------------------------------
459+ /**
460+ * A call to either `flask.make_response` function, or the `make_response` method on
461+ * an instance of `flask.Flask`.
462+ *
463+ * See
464+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
465+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
466+ */
467+ private class FlaskMakeResponseCall extends HTTP:: Server:: HttpResponse:: Range , DataFlow:: CfgNode {
468+ override CallNode node ;
469+
470+ FlaskMakeResponseCall ( ) {
471+ node .getFunction ( ) = flask:: make_response ( ) .asCfgNode ( )
472+ or
473+ node .getFunction ( ) = flask:: Flask:: make_response_ ( ) .asCfgNode ( )
474+ }
475+
476+ override DataFlow:: Node getBody ( ) { result .asCfgNode ( ) = node .getArg ( 0 ) }
477+
478+ override string getMimetypeDefault ( ) { result = "text/html" }
479+
480+ override DataFlow:: Node getMimetypeOrContentTypeArg ( ) { none ( ) }
481+ }
482+
483+ private class FlaskRouteHandlerReturn extends HTTP:: Server:: HttpResponse:: Range , DataFlow:: CfgNode {
484+ FlaskRouteHandlerReturn ( ) {
485+ exists ( Function routeHandler |
486+ routeHandler = any ( FlaskRouteSetup rs ) .getARouteHandler ( ) and
487+ node = routeHandler .getAReturnValueFlowNode ( )
488+ )
489+ }
490+
491+ override DataFlow:: Node getBody ( ) { result = this }
492+
493+ override DataFlow:: Node getMimetypeOrContentTypeArg ( ) { none ( ) }
494+
495+ override string getMimetypeDefault ( ) { result = "text/html" }
496+ }
331497}
0 commit comments