Auto escaping in Jinja and Flask
Auto Escape
Auto escape means applying a proper escaping modifier to each variable in your template. As per the documentation, Flask configures Jinja2 to automatically escape all values unless explicitly told otherwise to help prevent XSS. Let's dive into this in further detail.
Simple Flask app
We will create a simple Flask application for demonstration. Here are the files we are going to need:
app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/", methods=["GET"])
def xss():
return render_template(
"home.html",
data="<script>alert('hello');</script>"
)
As you can see above, in the default route, we pass data (i.e. JavaScript) to the home.html template below. In a real-world scenario, this data may come from some sort of temporary or persistent storage where the attacker has added the malicious code. For example, an attacker could have inserted the code as part of a comment they left on your blog.
home.html
<div>
{{data}}
</div>
And this is what the folder structure looks like:
Demo
Now, let's run the app using flask run --debug
and navigate to localhost:5000. You will see this on your screen:
If you look at the source, you will see that proper escape modifiers have been applied:
In order to prevent Flask from auto-escaping the values, we need to add the filter safe
to the home template as follows:
home.html
<div>
{{data|safe}}
</div>
Now, refresh the page.
As you can see, the script was executed. Therefore, we need to be extremely careful while using the safe
filter.
Problems in templates even when auto-escape is on
You may think that as long as we don't turn the auto escape off, we don't need to worry about XSS in Jinja templates. Here are couple of issues that can occur while using Jinja templates even when auto escape is kept on:
Attribute Injection
Jinja escaping cannot protect you from XSS by attribute injection. To demonstrate this, let's change the route and the template as follows:
app.py
@app.route("/", methods=["GET"])
def xss():
return render_template(
"home.html",
attribute="onmouseover=alert('Hacked')"
)
home.html
<div>
<input {{attribute}} type="text">
</div>
Now, let's run the app using flask run --debug
and when you move the mouse over the textbox you will see:
To counter this, you should quote your attributes with single or double quotes when using Jinja expressions in them. Let's modify home.html and put quotes around the attribute as follows:
home.html
<div>
<input "{{attribute}}" type="text">
</div>
Now, let's run the app using flask run --debug
and when you move the mouse over the textbox, nothing happens:
javascript: URI in href
Jinja’s escaping offers no protection if the anchor
tag’s href
attribute contains a javascript:
URI. The browser will execute this when the anchor tag is clicked if not secured properly. To demonstrate this, let's again change the route and the template as follows:
app.py
@app.route("/", methods=["GET"])
def xss():
return render_template(
"home.html",
value="javascript:alert('unsafe');"
)
home.html
<div>
<a href="{{value}}">click here</a>
</div>
Run the Flask app and click on the link
To prevent this, you need the Content Security Policy (CSP) response header.
Summary
In this article, we took a closer look at how Flask provides some basic protection against XSS using the auto-escaping feature of the Jinja template engine. We saw how to turn this auto-escaping feature off and also explored problems that can occur even with this feature turned on.
Thanks for reading and I hope this article was helpful.
Subscribe to my newsletter
Read articles from Rohan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Rohan
Rohan
Software engineer and blogger