I'm trying to fix a possible XSS vulnerability in WebApp (Flask) and I'm not sure what is the best way to prevent XSS without breaking functionality. For example, I have the following code block:

messages = db.execute("SELECT message, created_at FROM messages ORDER BY created_at DESC").fetchall()
messages_for_render = []
current_number = len(messages)
replace_str = re.compile("(guestbook)", re.IGNORECASE)
for message in messages:
    text = replace_str.sub(r"<b>\1</b>", message['message'])
        'number': current_number,
        'text': text,
        'created_at': message['created_at']
    current_number -= 1
return render_template_string('''
{% extends 'base.html' %}
{% block header %}
    <h1>{% block title %}Guestbook{% endblock %}</h1>
{% endblock %}
{% block content %}
{% for message in messages_for_render %}
    <h1> Message #{{ message.number }}</h1>
    <p>{{ message.text | safe }}</p>
    <small>Created at {{ message.created_at }}</small>
{% endfor %}
<a href={{ url_for('guestbook.add_message') }}>Add message</a>
{% endblock %}
''', messages_for_render=messages_for_render)

As you may see, message.text should contain <b>guestbook</b>, which should be correctly displayed.

If I'll try to remove | safe to disable autoescape, then required tags will be disabled too (same for the render_template_string() -> render_template() replacement).

I could try to replace dangerous symbols, such as <>"'&, manually, but I think there should be a more elegant solution.

Can anyone give me advice, what is the best way to deal with such cases?


1 Answer 1


Anywhere you've got some static text (whether it contains HTML entities or not), or text that can be procedurally generated by the server (such as message numbers and date strings), you can (and should) just put it in the template rather than putting it in the DB. The DB should be for dynamic content (such as user-supplied text) and its metadata (such as entry dates).

To solve your problem, strip out the <b>guestbook</b> (and any other "required tags") from the message.text, move that literal string into the template, and use Flask's HTML output encoding on the user's actual custom text.

An alternative approach - though one I'd trust less - would be input validation. Rather than escaping content on output, you could filter and/or escape content on input (when messages are posted to the server, before adding them to the DB). The problem with that approach is that it means you have to trust everything in your DB to be safe to render as HTML, not just today, but also in the future. It sounds like you've already got some XSS entries in your DB, which would need to be manually removed. Worse though, since the HTML standard is a living document, it's not a good idea to validate once and trust it to be safe forevermore; the set of dangerous text could change. You could probably manage it, though, with sufficiently aggressive escaping.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .