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

Skip to content

Commit 082e35c

Browse files
committed
Python: Model mimetype instead of content-type for HTTP Response
Since that's really what we're after (at least for now)
1 parent 81a42b7 commit 082e35c

6 files changed

Lines changed: 43 additions & 50 deletions

File tree

python/ql/src/experimental/Security-new-dataflow/CWE-079/ReflectedXss.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ReflectedXssConfiguration extends TaintTracking::Configuration {
2626

2727
override predicate isSink(DataFlow::Node sink) {
2828
exists(HTTP::Server::HttpResponse response |
29-
response.getContentType().toLowerCase().matches("text/html%") and
29+
response.getMimetype().toLowerCase() = "text/html" and
3030
sink = response.getBody()
3131
)
3232
}

python/ql/src/experimental/semmle/python/Concepts.qll

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,8 @@ module HTTP {
237237
/** Gets the data-flow node that specifies the body of this HTTP response. */
238238
DataFlow::Node getBody() { result = range.getBody() }
239239

240-
/** Gets the content-type of this HTTP response, if it can be statically determined. */
241-
string getContentType() { result = range.getContentType() }
240+
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
241+
string getMimetype() { result = range.getMimetype() }
242242
}
243243

244244
/** Provides a class for modeling new HTTP response APIs. */
@@ -256,21 +256,21 @@ module HTTP {
256256
/** Gets the data-flow node that specifies the body of this HTTP response. */
257257
abstract DataFlow::Node getBody();
258258

259-
/** Gets the data-flow node that specifies the content-type of this HTTP response, if any. */
260-
abstract DataFlow::Node getContentTypeArg();
259+
/** Gets the data-flow node that specifies the content-type/mimetype of this HTTP response, if any. */
260+
abstract DataFlow::Node getMimetypeOrContentTypeArg();
261261

262-
/** Gets the default content-type that should be used if `getContentTypeArg` has no results. */
263-
abstract string getContentTypeDefault();
262+
/** Gets the default mimetype that should be used if `getMimetypeOrContentTypeArg` has no results. */
263+
abstract string getMimetypeDefault();
264264

265-
/** Gets the content-type of this HTTP response, if it can be statically determined. */
266-
string getContentType() {
265+
/** Gets the mimetype of this HTTP response, if it can be statically determined. */
266+
string getMimetype() {
267267
exists(StrConst str |
268-
DataFlow::localFlow(DataFlow::exprNode(str), this.getContentTypeArg()) and
269-
result = str.getText()
268+
DataFlow::localFlow(DataFlow::exprNode(str), this.getMimetypeOrContentTypeArg()) and
269+
result = str.getText().splitAt(";", 0)
270270
)
271271
or
272-
not exists(this.getContentTypeArg()) and
273-
result = this.getContentTypeDefault()
272+
not exists(this.getMimetypeOrContentTypeArg()) and
273+
result = this.getMimetypeDefault()
274274
}
275275
}
276276
}

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

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,27 +210,23 @@ private module FlaskModel {
210210

211211
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
212212

213-
override string getContentTypeDefault() { result = "text/html" }
213+
override string getMimetypeDefault() { result = "text/html" }
214214

215215
/** Gets the argument passed to the `mimetype` parameter, if any. */
216216
private DataFlow::Node getMimetypeArg() {
217217
result.asCfgNode() in [node.getArg(3), node.getArgByName("mimetype")]
218218
}
219219

220-
/**
221-
* Gets the actual argument passed to the `content_type` parameter, if any.
222-
* This helper method exists since `getContentTypeArg` is the method exposed by
223-
* `HttpResponse::Range`)
224-
*/
225-
private DataFlow::Node actualContentTypeArg() {
220+
/** Gets the argument passed to the `content_type` parameter, if any. */
221+
private DataFlow::Node getContentTypeArg() {
226222
result.asCfgNode() in [node.getArg(4), node.getArgByName("content_type")]
227223
}
228224

229-
override DataFlow::Node getContentTypeArg() {
230-
result = this.actualContentTypeArg()
225+
override DataFlow::Node getMimetypeOrContentTypeArg() {
226+
result = this.getContentTypeArg()
231227
or
232228
// content_type argument takes priority over mimetype argument
233-
not exists(this.actualContentTypeArg()) and
229+
not exists(this.getContentTypeArg()) and
234230
result = this.getMimetypeArg()
235231
}
236232
}
@@ -464,8 +460,8 @@ private module FlaskModel {
464460

465461
override DataFlow::Node getBody() { result.asCfgNode() = node.getArg(0) }
466462

467-
override string getContentTypeDefault() { result = "text/html" }
463+
override string getMimetypeDefault() { result = "text/html" }
468464

469-
override DataFlow::Node getContentTypeArg() { none() }
465+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
470466
}
471467
}
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
| response_test.py:92:12:92:79 | ControlFlowNode for Response() | Unexpected result: contentType=text/plain; charset=utf-8 |
2-
| response_test.py:99:12:99:101 | ControlFlowNode for Response() | Unexpected result: contentType=text/plain; charset=utf-8 |
3-
| response_test.py:114:12:114:118 | ControlFlowNode for Response() | Unexpected result: contentType=text/plain; charset=utf-8 |

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77

88
@app.route("/html1") # $routeSetup="/html1"
99
def html1(): # $routeHandler
10-
return "<h1>hello</h1>" # $f-:HttpResponse $f-:contentType=text/html $f-:responseBody="<h1>hello</h1>"
10+
return "<h1>hello</h1>" # $f-:HttpResponse $f-:mimetype=text/html $f-:responseBody="<h1>hello</h1>"
1111

1212

1313
@app.route("/html2") # $routeSetup="/html2"
1414
def html2(): # $routeHandler
1515
# note that response saved in a variable intentionally -- we wan the annotations to
1616
# show that we recognize the response creation, and not the return (hopefully). (and
1717
# do the same in the following of the file)
18-
resp = make_response("<h1>hello</h1>") # $HttpResponse $contentType=text/html $responseBody="<h1>hello</h1>"
18+
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
1919
return resp
2020

2121

2222
@app.route("/html3") # $routeSetup="/html3"
2323
def html3(): # $routeHandler
24-
resp = app.make_response("<h1>hello</h1>") # $HttpResponse $contentType=text/html $responseBody="<h1>hello</h1>"
24+
resp = app.make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
2525
return resp
2626

2727

@@ -31,30 +31,30 @@ def html3(): # $routeHandler
3131

3232
@app.route("/html4") # $routeSetup="/html4"
3333
def html4(): # $routeHandler
34-
resp = Response("<h1>hello</h1>") # $HttpResponse $contentType=text/html $responseBody="<h1>hello</h1>"
34+
resp = Response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
3535
return resp
3636

3737

3838
@app.route("/html5") # $routeSetup="/html5"
3939
def html5(): # $routeHandler
4040
# note: flask.Flask.response_class is set to `flask.Response` by default.
4141
# it can be overridden, but we don't try to handle that right now.
42-
resp = Flask.response_class("<h1>hello</h1>") # $f-:HttpResponse $f-:contentType=text/html $f-:responseBody="<h1>hello</h1>"
42+
resp = Flask.response_class("<h1>hello</h1>") # $f-:HttpResponse $f-:mimetype=text/html $f-:responseBody="<h1>hello</h1>"
4343
return resp
4444

4545

4646
@app.route("/html6") # $routeSetup="/html6"
4747
def html6(): # $routeHandler
4848
# note: app.response_class (flask.Flask.response_class) is set to `flask.Response` by default.
4949
# it can be overridden, but we don't try to handle that right now.
50-
resp = app.response_class("<h1>hello</h1>") # $f-:HttpResponse $f-:contentType=text/html $f-:responseBody="<h1>hello</h1>"
50+
resp = app.response_class("<h1>hello</h1>") # $f-:HttpResponse $f-:mimetype=text/html $f-:responseBody="<h1>hello</h1>"
5151
return resp
5252

5353

5454
@app.route("/jsonify") # $routeSetup="/jsonify"
5555
def jsonify_route(): # $routeHandler
5656
data = {"foo": "bar"}
57-
response = jsonify(data) # $f-:HttpResponse $f-:contentType=application/json $f-:responseBody=data
57+
response = jsonify(data) # $f-:HttpResponse $f-:mimetype=application/json $f-:responseBody=data
5858
return response
5959

6060

@@ -65,15 +65,15 @@ def jsonify_route(): # $routeHandler
6565

6666
@app.route("/content-type/response-modification1") # $routeSetup="/content-type/response-modification1"
6767
def response_modification1(): # $routeHandler
68-
resp = make_response("<h1>hello</h1>") # $HttpResponse $contentType=text/html $responseBody="<h1>hello</h1>"
69-
resp.content_type = "text/plain" # $f-:HttpResponse $f-:contentType=text/plain
68+
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
69+
resp.content_type = "text/plain" # $f-:HttpResponse $f-:mimetype=text/plain
7070
return resp
7171

7272

7373
@app.route("/content-type/response-modification2") # $routeSetup="/content-type/response-modification2"
7474
def response_modification2(): # $routeHandler
75-
resp = make_response("<h1>hello</h1>") # $HttpResponse $contentType=text/html $responseBody="<h1>hello</h1>"
76-
resp.headers["content-type"] = "text/plain" # $f-:HttpResponse $f-:contentType=text/plain
75+
resp = make_response("<h1>hello</h1>") # $HttpResponse $mimetype=text/html $responseBody="<h1>hello</h1>"
76+
resp.headers["content-type"] = "text/plain" # $f-:HttpResponse $f-:mimetype=text/plain
7777
return resp
7878

7979

@@ -83,59 +83,59 @@ def response_modification2(): # $routeHandler
8383

8484
@app.route("/content-type/Response1") # $routeSetup="/content-type/Response1"
8585
def Response1(): # $routeHandler
86-
resp = Response("<h1>hello</h1>", mimetype="text/plain") # $HttpResponse $contentType=text/plain $responseBody="<h1>hello</h1>"
86+
resp = Response("<h1>hello</h1>", mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
8787
return resp
8888

8989

9090
@app.route("/content-type/Response2") # $routeSetup="/content-type/Response2"
9191
def Response2(): # $routeHandler
92-
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8") # $HttpResponse $f-:contentType=text/plain $responseBody="<h1>hello</h1>"
92+
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
9393
return resp
9494

9595

9696
@app.route("/content-type/Response3") # $routeSetup="/content-type/Response3"
9797
def Response3(): # $routeHandler
9898
# content_type argument takes priority (and result is text/plain)
99-
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8", mimetype="text/html") # $HttpResponse $f-:contentType=text/plain $responseBody="<h1>hello</h1>"
99+
resp = Response("<h1>hello</h1>", content_type="text/plain; charset=utf-8", mimetype="text/html") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
100100
return resp
101101

102102

103103
@app.route("/content-type/Response4") # $routeSetup="/content-type/Response4"
104104
def Response4(): # $routeHandler
105105
# note: capitalization of Content-Type does not matter
106-
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/plain"}) # $HttpResponse $f+:contentType=text/html $f-:contentType=text/plain $responseBody="<h1>hello</h1>"
106+
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/plain"}) # $HttpResponse $f+:mimetype=text/html $f-:mimetype=text/plain $responseBody="<h1>hello</h1>"
107107
return resp
108108

109109

110110
@app.route("/content-type/Response5") # $routeSetup="/content-type/Response5"
111111
def Response5(): # $routeHandler
112112
# content_type argument takes priority (and result is text/plain)
113113
# note: capitalization of Content-Type does not matter
114-
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, content_type="text/plain; charset=utf-8") # $HttpResponse $f-:contentType=text/plain $responseBody="<h1>hello</h1>"
114+
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, content_type="text/plain; charset=utf-8") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
115115
return resp
116116

117117

118118
@app.route("/content-type/Response6") # $routeSetup="/content-type/Response6"
119119
def Response6(): # $routeHandler
120120
# mimetype argument takes priority over header (and result is text/plain)
121121
# note: capitalization of Content-Type does not matter
122-
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, mimetype="text/plain") # $HttpResponse $contentType=text/plain $responseBody="<h1>hello</h1>"
122+
resp = Response("<h1>hello</h1>", headers={"Content-TYPE": "text/html"}, mimetype="text/plain") # $HttpResponse $mimetype=text/plain $responseBody="<h1>hello</h1>"
123123
return resp
124124

125125

126126
@app.route("/content-type/Flask-response-class") # $routeSetup="/content-type/Flask-response-class"
127127
def Flask_response_class(): # $routeHandler
128128
# note: flask.Flask.response_class is set to `flask.Response` by default.
129129
# it can be overridden, but we don't try to handle that right now.
130-
resp = Flask.response_class("<h1>hello</h1>", mimetype="text/plain") # $f-:HttpResponse $f-:contentType=text/plain $f-:responseBody="<h1>hello</h1>"
130+
resp = Flask.response_class("<h1>hello</h1>", mimetype="text/plain") # $f-:HttpResponse $f-:mimetype=text/plain $f-:responseBody="<h1>hello</h1>"
131131
return resp
132132

133133

134134
@app.route("/content-type/app-response-class") # $routeSetup="/content-type/app-response-class"
135135
def app_response_class(): # $routeHandler
136136
# note: app.response_class (flask.Flask.response_class) is set to `flask.Response` by default.
137137
# it can be overridden, but we don't try to handle that right now.
138-
resp = app.response_class("<h1>hello</h1>", mimetype="text/plain") # $f-:HttpResponse $f-:contentType=text/plain $f-:responseBody="<h1>hello</h1>"
138+
resp = app.response_class("<h1>hello</h1>", mimetype="text/plain") # $f-:HttpResponse $f-:mimetype=text/plain $f-:responseBody="<h1>hello</h1>"
139139
return resp
140140

141141

python/ql/test/experimental/meta/ConceptsTest.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class HttpServerHttpResponseTest extends InlineExpectationsTest {
148148

149149
HttpServerHttpResponseTest() { this = "HttpServerHttpResponseTest: " + file }
150150

151-
override string getARelevantTag() { result in ["HttpResponse", "responseBody", "contentType"] }
151+
override string getARelevantTag() { result in ["HttpResponse", "responseBody", "mimetype"] }
152152

153153
override predicate hasActualResult(Location location, string element, string tag, string value) {
154154
// By adding `file` as a class field, and these two restrictions, it's possible to
@@ -175,8 +175,8 @@ class HttpServerHttpResponseTest extends InlineExpectationsTest {
175175
exists(HTTP::Server::HttpResponse response |
176176
location = response.getLocation() and
177177
element = response.toString() and
178-
value = response.getContentType() and
179-
tag = "contentType"
178+
value = response.getMimetype() and
179+
tag = "mimetype"
180180
)
181181
)
182182
}

0 commit comments

Comments
 (0)