Question

Do Chameleon templates escape/strip XSS and HTML tags for variables? Would the following be safe?

<script type="text/javascript">

    var initialComments = ${comments};
    for (var i = 0; i < initialComments.length; i++) {
        initialComments[i].userId = initialComments[i].user_id;
    }
    var post = ${post}
    // ...

</script>
Was it helpful?

Solution

This is actually harder than it seems, and depends upon circumstances. If the output format is HTML, then the script contents are a special CDATA section, wherein the XML/SGML escapes are not interpreted. If the output format is XML, with XML content type, then the escapes work as expected. Even though it could be possible to patch Chameleon so that it understood the <script></script> element as special, currently it does not.

When embedded in <script> tag, depending on a browser version, a sole </ or </script will end the script tag, making it vulnerable to XSS. Furthermore, a < can start a HTML comment as in <!--, and legacy considerations require that <script></script> can be written balanced WITHIN the script tag, thus the safest is to escape the < altogether in all strings.

Another problem is that JSON is not a strict subset of JavaScript, allowing character strings like U+2028 and U+2029 break the script, if you use ensure_ascii=False:

Thus the proper way to do this on python/chameleon, to embed within a <script> tag in HTML is to use:

post_json = (json.dumps(foo, ensure_ascii=False)
    .replace('\u2028', r'\u2028')
    .replace('\u2029', r'\u2029')
    .replace('<', r'\u003c'))

This is true for only content embedded in <script> tags.

or if you just want to get unreadable escapes for pretty much all characters you can be content with:

post_json = json.dumps(foo, ensure_ascii=True)\
    .replace('<', r'\u003c')

and then embed with

${structure: post_json}

I posted a feature request in the Python bug tracker to have a keyword argument to do this automatically, but it was rejected.

The safer way to embed stuff is to store it in data-* attributes.

UPDATE escaping </ alone is not enough.

OTHER TIPS

From the Chameleon introduction page:

By default, the string is escaped before insertion. To avoid this, use the structure: prefix

However, that won't escape JavaScript values. Use JSON for that; in your view, use:

post_json = (json.dumps(post)
    .replace(u'<', u'\\u003c')
    .replace(u'>', u'\\u003e')
    .replace(u'&', u'\\u0026')
    .replace(u"'", u'\\u0027'))

to produce a HTML-safe JSON string; JSON (as produced by json.dumps() at least 1) is a Javascript subset, here with any HTML-dangerous characters escaped (with thanks to the Flask json.htmlsafe_dumps() function).

Interpolate that into your template using structure:

<script type="text/javascript">

    var initialComments = ${comments};
    for (var i = 0; i < initialComments.length; i++) {
        initialComments[i].userId = initialComments[i].user_id;
    }
    var post = ${structure:post_json};
    // ...

</script>

1JSON allows for U+2028 and U+2029 characters but the json.dumps() function escapes all non-ASCII codepoints by default.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top