RSTR-XSS-004 — Flask Markup(...) / direct return with request input
Summary
A Flask handler returns request input as part of the HTTP response
without HTML-escaping it, either by wrapping it in Markup(...) (which
disables Jinja's auto-escaping) or by returning the value as part of a
hand-built HTML string.
The browser parses the response in the application's origin, so any
HTML the attacker submits — including <script> tags or event
handlers — runs there.
Severity
High.
Languages
Python (Flask / Quart).
What rastray flags
@app.route('/echo')
def echo():
return Markup(f'<h1>{request.args["msg"]}</h1>') # ← flagged
@app.route('/raw')
def raw():
return '<p>Hi, ' + request.args['name'] + '</p>' # ← flagged
What rastray deliberately does not flag
return render_template('echo.html', msg=request.args['msg'])— Jinja auto-escapes by default.- Values returned through
jsonify(...)— JSON, not HTML. - Values explicitly passed through
markupsafe.escape(...)first.
How to fix it
Use Jinja templates and let auto-escaping handle the data:
# templates/echo.html
# <h1>{{ msg }}</h1>
@app.route('/echo')
def echo():
return render_template('echo.html', msg=request.args['msg'])
If you need a string response, escape explicitly:
from markupsafe import escape
@app.route('/raw')
def raw():
return f'<p>Hi, {escape(request.args["name"])}</p>'
Markup(...) is the promise "I have already escaped this; treat it
as trusted HTML." Only use it on values you yourself produced from
safe sources.