Skip to content

Commit 4056358

Browse files
authored
Merge pull request github#13438 from RasmusWL/flask-render-string
Python: Add modeling of `flask.render_template_string`
2 parents 4dc596f + 6526364 commit 4056358

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added modeling of taint flow through the template argument of `flask.render_template_string` and `flask.stream_template_string`.

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ private import semmle.python.frameworks.Stdlib
1313
private import semmle.python.ApiGraphs
1414
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
1515
private import semmle.python.security.dataflow.PathInjectionCustomizations
16+
private import semmle.python.dataflow.new.FlowSummary
1617

1718
/**
1819
* Provides models for the `flask` PyPI package.
@@ -587,4 +588,57 @@ module Flask {
587588
private class FlaskLogger extends Stdlib::Logger::InstanceSource {
588589
FlaskLogger() { this = FlaskApp::instance().getMember("logger").asSource() }
589590
}
591+
592+
/**
593+
* A flow summary for `flask.render_template_string`.
594+
*
595+
* see https://flask.palletsprojects.com/en/2.3.x/api/#flask.render_template_string
596+
*/
597+
private class RenderTemplateStringSummary extends SummarizedCallable {
598+
RenderTemplateStringSummary() { this = "flask.render_template_string" }
599+
600+
override DataFlow::CallCfgNode getACall() {
601+
result = API::moduleImport("flask").getMember("render_template_string").getACall()
602+
}
603+
604+
override DataFlow::ArgumentNode getACallback() {
605+
result =
606+
API::moduleImport("flask")
607+
.getMember("render_template_string")
608+
.getAValueReachableFromSource()
609+
}
610+
611+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
612+
input = "Argument[0]" and
613+
output = "ReturnValue" and
614+
preservesValue = false
615+
}
616+
}
617+
618+
/**
619+
* A flow summary for `flask.stream_template_string`.
620+
*
621+
* see https://flask.palletsprojects.com/en/2.3.x/api/#flask.stream_template_string
622+
*/
623+
private class StreamTemplateStringSummary extends SummarizedCallable {
624+
StreamTemplateStringSummary() { this = "flask.stream_template_string" }
625+
626+
override DataFlow::CallCfgNode getACall() {
627+
result = API::moduleImport("flask").getMember("stream_template_string").getACall()
628+
}
629+
630+
override DataFlow::ArgumentNode getACallback() {
631+
result =
632+
API::moduleImport("flask")
633+
.getMember("stream_template_string")
634+
.getAValueReachableFromSource()
635+
}
636+
637+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
638+
input = "Argument[0]" and
639+
// Technically it's `Iterator[str]`, but list will do :)
640+
output = "ReturnValue.ListElement" and
641+
preservesValue = false
642+
}
643+
}
590644
}

python/ql/test/library-tests/frameworks/flask/taint_test.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from flask import Flask, request
1+
from flask import Flask, request, render_template_string, stream_template_string
22
app = Flask(__name__)
33

44
@app.route("/test_taint/<name>/<int:number>") # $routeSetup="/test_taint/<name>/<int:number>"
@@ -215,7 +215,34 @@ def test_taint(name = "World!", number="0", foo="foo"): # $requestHandler route
215215
gd(), # $ tainted
216216
)
217217

218-
218+
# ----------------------------------
219+
# non-request related taint-steps
220+
# ----------------------------------
221+
222+
# render_template_string
223+
source = TAINTED_STRING
224+
ensure_tainted(source) # $ tainted
225+
res = render_template_string(source)
226+
ensure_tainted(res) # $ tainted
227+
228+
# since template variables are auto-escaped, we don't treat result as tainted
229+
# see https://flask.palletsprojects.com/en/2.3.x/api/#flask.render_template_string
230+
res = render_template_string("Hello {{ foo }}", foo=TAINTED_STRING)
231+
ensure_not_tainted(res)
232+
233+
234+
# stream_template_string
235+
source = TAINTED_STRING
236+
ensure_tainted(source) # $ tainted
237+
res = stream_template_string(source)
238+
for x in res:
239+
ensure_tainted(x) # $ tainted
240+
241+
# since template variables are auto-escaped, we don't treat result as tainted
242+
# see https://flask.palletsprojects.com/en/2.3.x/api/#flask.stream_template_string
243+
res = stream_template_string("Hello {{ foo }}", foo=TAINTED_STRING)
244+
for x in res:
245+
ensure_not_tainted(x)
219246

220247

221248
@app.route("/debug/<foo>/<bar>", methods=['GET']) # $routeSetup="/debug/<foo>/<bar>"

0 commit comments

Comments
 (0)