使用Jinja2为JavaScript转义字符串?

25

如何使用Jinja2转义HTML,以便在JavaScript(jQuery)中将其用作字符串?

如果我使用Django的模板系统,我可以编写:

$("#mydiv").append("{{ html_string|escapejs }}");

Django的|escapejs过滤器会转义html_string中可能破坏代码块预期使用的引号、特殊字符等内容,但Jinja2似乎没有相应的过滤器(我是不是错了?)。

除了从Django复制/粘贴代码外,是否有更简洁的解决方案?


请见此处:http://jinja.pocoo.org/docs/templates/#escaping - JAR.JAR.beans
我不需要转义jinja标签文本本身,我需要确保html_string不包含任何有害字符。 - meshy
也许 |safe 过滤器是你需要的,而不是使用:http://flask.pocoo.org/docs/templating/#standard-filters - JAR.JAR.beans
@meshy:同样的原则也适用。使用json.dumps()函数将JavaScript字面量合并进去。 - Martijn Pieters
显示剩余3条评论
6个回答

21

Jinja2有一个很好的过滤器tojson。如果您从字符串生成JSON,则会生成用双引号""括起来的字符串。这在JavaScript中是安全的。您无需手动添加引号。

$("#mydiv").append({{ html_string|tojson }});

它还转义了<>&符号。因此,即使字符串包含像<script>这样的XSS危险内容,它也是安全的。


15

这是一个基于Django的escapejs过滤器,我为在Jinja2模板中使用而编写的:

_js_escapes = {
        '\\': '\\u005C',
        '\'': '\\u0027',
        '"': '\\u0022',
        '>': '\\u003E',
        '<': '\\u003C',
        '&': '\\u0026',
        '=': '\\u003D',
        '-': '\\u002D',
        ';': '\\u003B',
        u'\u2028': '\\u2028',
        u'\u2029': '\\u2029'
}
# Escape every ASCII character with a value less than 32.
_js_escapes.update(('%c' % z, '\\u%04X' % z) for z in xrange(32))
def jinja2_escapejs_filter(value):
        retval = []
        for letter in value:
                if _js_escapes.has_key(letter):
                        retval.append(_js_escapes[letter])
                else:
                        retval.append(letter)

        return jinja2.Markup("".join(retval))
JINJA_ENVIRONMENT.filters['escapejs'] = jinja2_escapejs_filter

在模板中安全使用的示例:

<script type="text/javascript">
<!--
var variableName = "{{ variableName | escapejs }}";
…
//-->
</script>

当变量variableName是strunicode类型时。


已经要求使用引号(单引号或双引号)将{{ variableName | espacejs }}括起来,因此无法使用方括号技巧。否则,即使是一个空格也可能是危险的。 - Tometzky
2
这个答案在我的帖子中帮了我很多。在Python中打印字符串时,需要转义字符以便在JavaScript的引号标签内作为JSON解析。感谢您的帮助! - Matthisk

10

我去年也遇到过类似的问题。不确定你是否使用了bottle,但我的解决方案大致如下。

import json

def escapejs(val):
    return json.dumps(str(val)) # *but see [Important Note] below to be safe

@app.route('/foo')
def foo():
    return bottle.jinja2_template('foo', template_settings={'filters': {'escapejs': escapejs}})

(我将template_settings字典包装在一个辅助函数中,因为我在各处都使用它,但在这个示例中,我保持它简单。)

不幸的是,它不像内置的jinja2过滤器那样简单,但我很高兴能够快乐地生活——特别是考虑到我还有其他几个自定义过滤器要添加。

重要提示:向@medmunds致敬,感谢他在下面的评论中提醒我们,json.dumps不安全。换句话说,在生产的、面向互联网的服务器上不应该使用它。建议编写更安全的json转义例程(或者窃取django的——对不起OP,我知道你希望避免这种情况),并调用它来代替使用json.dumps。


太好了!我没有使用Bottle,而是使用pywebkitgtk构建桌面应用程序,但是json.dumps正是我需要的。比我想象的简单得多!谢谢! - meshy
哦,感谢你提供的Bottle链接!我之前没见过这个,可能会很有用 ;) - meshy
8
我认为json.dumps()没有转义你需要担心的所有内容。例如,按当前方式编写的escapejs("</script>")会返回 "</script>"-- 这似乎可能允许一个闭合的脚本标记(和其后的任何内容!)泄漏到你的HTML中去。(Django的escapejs过滤器对<和>字符进行Unicode转义,可以避免这个问题。) - medmunds
1
...嗯,看起来这个答案肯定存在XSS漏洞。 Khan Academy 在这里提供了一些示例,并提供了自己的escapejs和jsonify过滤器。看起来他们从Django借鉴了他们的escapejs实现 - medmunds
@medmunds,感谢您指出XSS漏洞。 您是正确的;我将更新条目以指出这一点。(顺便说一句,在我的情况下,我的服务是内部的,位于防火墙后面,因此XSS攻击并不是最重要的问题。尽管如此,我仍会修复我的代码,以养成正确的习惯。) - ron rothman

1

我刚刚研究了这个问题,我的解决方案是定义一个过滤器:

from flask import Flask, Markup
app = Flask(__name__)
app.jinja_env.filters['json'] = lambda v: Markup(json.dumps(v))

在模板中:
<script>
var myvar = {{myvar|json}} ;
</script>

这个有趣的特性是myvar可以是任何可进行JSON序列化的内容。

8
不要在用户生成的内容中这样做,因为用户可以执行JS。例如,当 myvar = </script><script>alert('XSS'); 时。 - Blaise

0

基于 @tometzky 的代码,这是我的 Python 3 版本:

_js_escapes = {
        '\\': '\\u005C',
        '\'': '\\u0027',
        '"': '\\u0022',
        '>': '\\u003E',
        '<': '\\u003C',
        '&': '\\u0026',
        '=': '\\u003D',
        '-': '\\u002D',
        ';': '\\u003B',
        u'\u2028': '\\u2028',
        u'\u2029': '\\u2029'
}
# Escape every ASCII character with a value less than 32.
_js_escapes.update(('%c' % z, '\\u%04X' % z) for z in range(32))

@register.filter
def escapejs(value):
    return jinja2.Markup("".join(_js_escapes.get(l, l) for l in value))

使用方法完全相同。


-1
你也可以使用jinja2的autoescape。因此,例如,你可以在Python的jinja2环境中添加autoescape:
JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    autoescape=True)

或者,你可以使用在Jinja 2.4中添加的Autoescape扩展,来更好地控制自动转义在HTML中的使用位置。有关此内容的更多信息在此处和示例(在Google应用引擎中)在此处

Python:

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'])

HTML:

{% autoescape true %}
    <html>
        <body>
            {{ IWillBeEscaped }}
        </body>
    </html>
{% endautoescape %}

1
这不只是标准的HTML转义吗?问题是关于JavaScript的吗? - pip
2
不,那绝对是HTML转义。 - Simon Steinberger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接