Auto escaping in Jinja and Flask

RohanRohan
3 min read

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.

0
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