1
1
/**
2
- * Provides classes modeling security-relevant aspects of the `flask` package.
2
+ * Provides classes modeling security-relevant aspects of the `flask` PyPI package.
3
+ * See https://flask.palletsprojects.com/en/1.1.x/.
3
4
*/
4
5
5
6
private import python
@@ -11,6 +12,10 @@ private import experimental.semmle.python.frameworks.Werkzeug
11
12
12
13
// for old improved impl see
13
14
// https://github.com/github/codeql/blob/9f95212e103c68d0c1dfa4b6f30fb5d53954ccef/python/ql/src/semmle/python/web/flask/Request.qll
15
+ /**
16
+ * Provides models for the `flask` PyPI package.
17
+ * See https://flask.palletsprojects.com/en/1.1.x/.
18
+ */
14
19
private module Flask {
15
20
/** Gets a reference to the `flask` module. */
16
21
DataFlow:: Node flask ( DataFlow:: TypeTracker t ) {
@@ -23,6 +28,7 @@ private module Flask {
23
28
/** Gets a reference to the `flask` module. */
24
29
DataFlow:: Node flask ( ) { result = flask ( DataFlow:: TypeTracker:: end ( ) ) }
25
30
31
+ /** Provides models for the `flask` module. */
26
32
module flask {
27
33
/** Gets a reference to the `flask.request` object. */
28
34
DataFlow:: Node request ( DataFlow:: TypeTracker t ) {
@@ -32,13 +38,154 @@ private module Flask {
32
38
t .startInAttr ( "request" ) and
33
39
result = flask ( )
34
40
or
35
- exists ( DataFlow:: TypeTracker t2 | result = flask :: request ( t2 ) .track ( t2 , t ) )
41
+ exists ( DataFlow:: TypeTracker t2 | result = request ( t2 ) .track ( t2 , t ) )
36
42
}
37
43
38
44
/** Gets a reference to the `flask.request` object. */
39
- DataFlow:: Node request ( ) { result = flask:: request ( DataFlow:: TypeTracker:: end ( ) ) }
45
+ DataFlow:: Node request ( ) { result = request ( DataFlow:: TypeTracker:: end ( ) ) }
46
+
47
+ /** Gets a reference to the `flask.Flask` class. */
48
+ private DataFlow:: Node classFlask ( DataFlow:: TypeTracker t ) {
49
+ t .start ( ) and
50
+ result = DataFlow:: importNode ( "flask.Flask" )
51
+ or
52
+ t .startInAttr ( "Flask" ) and
53
+ result = flask ( )
54
+ or
55
+ exists ( DataFlow:: TypeTracker t2 | result = classFlask ( t2 ) .track ( t2 , t ) )
56
+ }
57
+
58
+ /** Gets a reference to the `flask.Flask` class. */
59
+ DataFlow:: Node classFlask ( ) { result = classFlask ( DataFlow:: TypeTracker:: end ( ) ) }
60
+
61
+ /** Gets a reference to an instance of `flask.Flask` (a Flask application). */
62
+ private DataFlow:: Node app ( DataFlow:: TypeTracker t ) {
63
+ t .start ( ) and
64
+ result .asCfgNode ( ) .( CallNode ) .getFunction ( ) = flask:: classFlask ( ) .asCfgNode ( )
65
+ or
66
+ exists ( DataFlow:: TypeTracker t2 | result = app ( t2 ) .track ( t2 , t ) )
67
+ }
68
+
69
+ /** Gets a reference to an instance of `flask.Flask` (a flask application). */
70
+ DataFlow:: Node app ( ) { result = app ( DataFlow:: TypeTracker:: end ( ) ) }
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // routing modeling
75
+ // ---------------------------------------------------------------------------
76
+ /**
77
+ * Gets a reference to the attribute `attr_name` of a flask application.
78
+ * WARNING: Only holds for a few predefined attributes.
79
+ */
80
+ private DataFlow:: Node app_attr ( DataFlow:: TypeTracker t , string attr_name ) {
81
+ attr_name in [ "route" , "add_url_rule" ] and
82
+ t .startInAttr ( attr_name ) and
83
+ result = flask:: app ( )
84
+ or
85
+ // Due to bad performance when using normal setup with `app_attr(t2, attr_name).track(t2, t)`
86
+ // we have inlined that code and forced a join
87
+ exists ( DataFlow:: TypeTracker t2 |
88
+ exists ( DataFlow:: StepSummary summary |
89
+ app_attr_first_join ( t2 , attr_name , result , summary ) and
90
+ t = t2 .append ( summary )
91
+ )
92
+ )
93
+ }
94
+
95
+ pragma [ nomagic]
96
+ private predicate app_attr_first_join (
97
+ DataFlow:: TypeTracker t2 , string attr_name , DataFlow:: Node res , DataFlow:: StepSummary summary
98
+ ) {
99
+ DataFlow:: StepSummary:: step ( app_attr ( t2 , attr_name ) , res , summary )
100
+ }
101
+
102
+ /**
103
+ * Gets a reference to the attribute `attr_name` of a flask application.
104
+ * WARNING: Only holds for a few predefined attributes.
105
+ */
106
+ private DataFlow:: Node app_attr ( string attr_name ) {
107
+ result = app_attr ( DataFlow:: TypeTracker:: end ( ) , attr_name )
108
+ }
109
+
110
+ private string werkzeug_rule_re ( ) {
111
+ // since flask uses werkzeug internally, we are using its routing rules from
112
+ // https://github.com/pallets/werkzeug/blob/4dc8d6ab840d4b78cbd5789cef91b01e3bde01d5/src/werkzeug/routing.py#L138-L151
113
+ result =
114
+ "(?<static>[^<]*)<(?:(?<converter>[a-zA-Z_][a-zA-Z0-9_]*)(?:\\((?<args>.*?)\\))?\\:)?(?<variable>[a-zA-Z_][a-zA-Z0-9_]*)>"
115
+ }
116
+
117
+ /** A route setup made by flask (sharing handling of URL patterns). */
118
+ abstract private class FlaskRouteSetup extends HTTP:: Server:: RouteSetup:: Range {
119
+ override Parameter getARoutedParameter ( ) {
120
+ // If we don't know the URL pattern, we simply mark all parameters as a routed
121
+ // parameter. This should give us more RemoteFlowSources but could also lead to
122
+ // more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
123
+ not exists ( this .getUrlPattern ( ) ) and
124
+ result = this .getARouteHandler ( ) .getArgByName ( _)
125
+ or
126
+ exists ( string name |
127
+ result = this .getARouteHandler ( ) .getArgByName ( name ) and
128
+ exists ( string match |
129
+ match = this .getUrlPattern ( ) .regexpFind ( werkzeug_rule_re ( ) , _, _) and
130
+ name = match .regexpCapture ( werkzeug_rule_re ( ) , 4 )
131
+ )
132
+ )
133
+ }
134
+
135
+ /** Gets the argument used to pass in the URL pattern. */
136
+ abstract DataFlow:: Node getUrlPatternArg ( ) ;
137
+
138
+ override string getUrlPattern ( ) {
139
+ exists ( StrConst str |
140
+ DataFlow:: localFlow ( DataFlow:: exprNode ( str ) , this .getUrlPatternArg ( ) ) and
141
+ result = str .getText ( )
142
+ )
143
+ }
144
+ }
145
+
146
+ /**
147
+ * A call to `flask.Flask.route`.
148
+ *
149
+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.route
150
+ */
151
+ private class FlaskAppRouteCall extends FlaskRouteSetup , DataFlow:: CfgNode {
152
+ override CallNode node ;
153
+
154
+ FlaskAppRouteCall ( ) { node .getFunction ( ) = app_attr ( "route" ) .asCfgNode ( ) }
155
+
156
+ override DataFlow:: Node getUrlPatternArg ( ) {
157
+ result .asCfgNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "rule" ) ]
158
+ }
159
+
160
+ override Function getARouteHandler ( ) { result .getADecorator ( ) .getAFlowNode ( ) = node }
161
+ }
162
+
163
+ /**
164
+ * A call to `flask.Flask.add_url_rule`.
165
+ *
166
+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.add_url_rule
167
+ */
168
+ private class FlaskAppAddUrlRule extends FlaskRouteSetup , DataFlow:: CfgNode {
169
+ override CallNode node ;
170
+
171
+ FlaskAppAddUrlRule ( ) { node .getFunction ( ) = app_attr ( "add_url_rule" ) .asCfgNode ( ) }
172
+
173
+ override DataFlow:: Node getUrlPatternArg ( ) {
174
+ result .asCfgNode ( ) in [ node .getArg ( 0 ) , node .getArgByName ( "rule" ) ]
175
+ }
176
+
177
+ override Function getARouteHandler ( ) {
178
+ exists ( DataFlow:: Node view_func_arg , DataFlow:: Node func_src |
179
+ view_func_arg .asCfgNode ( ) in [ node .getArg ( 2 ) , node .getArgByName ( "view_func" ) ] and
180
+ DataFlow:: localFlow ( func_src , view_func_arg ) and
181
+ func_src .asExpr ( ) .( CallableExpr ) = result .getDefinition ( )
182
+ )
183
+ }
40
184
}
41
185
186
+ // ---------------------------------------------------------------------------
187
+ // flask.Request taint modeling
188
+ // ---------------------------------------------------------------------------
42
189
// TODO: Do we even need this class? :|
43
190
/**
44
191
* A source of remote flow from a flask request.
0 commit comments