如何在HTML的脚本标记中插入任意JSON

时间:2016-08-28 16:39:16

标签: javascript python html json xss

我想在脚本标记内的HTML文档源中存储JSON的内容。

该JSON的内容确实取决于用户提交的输入,因此需要非常小心地为XSS清理该字符串。

我在这里读了两个概念。

1。将所有</script标记替换为<\/script,或将所有</替换为<\/服务器端。

代码明智,它看起来如下(使用Python和jinja2作为示例):

// view
data = {
    'test': 'asdas</script><b>as\'da</b><b>as"da</b>',
}

context_dict = {
    'data_json': json.dumps(data, ensure_ascii=False).replace('</script', r'<\/script'),
}

// template
<script>
    var data_json = {{ data_json | safe }};
</script>

// js
access it simply as window.data_json object

2。将数据编码为HTML实体编码的JSON字符串,unescape +在客户端解析它。 Unescape来自这个答案:https://stackoverflow.com/a/34064434/518169

// view
context_dict = {
    'data_json': json.dumps(data, ensure_ascii=False),
}

// template
<script>
    var data_json = '{{ data_json }}'; // encoded into HTML entities, like &lt; &gt; &amp;
</script>

// js
function htmlDecode(input) {
  var doc = new DOMParser().parseFromString(input, "text/html");
  return doc.documentElement.textContent;
}

var decoded = htmlDecode(window.data_json);
var data_json = JSON.parse(decoded);

此方法不起作用,因为脚本源中的\"在JS变量中成为"。此外,它创建了一个更大的HTML文档,也不是真正的人类可读性,所以如果它没有意味着巨大的安全风险,我会选择第一个。

使用第一个版本是否存在安全风险?是否足以使用.replace('</script', r'<\/script')清理JSON编码的字符串?

关于SO的参考:
Best way to store JSON in an HTML attribute?
Why split the <script> tag when writing it with document.write()?
Script tag in JavaScript string
Sanitize <script> element contents
Escape </ in script tag contents

关于这个问题的一些很好的外部资源:
Flask的tojson过滤器实施source
铁路的json_escape方法helpsource
在Django进行了长达5年的讨论ticketproposed code

2 个答案:

答案 0 :(得分:1)

首先,你的偏执是有根据的。

  • HTML解析器可能被关闭脚本标记欺骗(最好由任何结束标记假设)
  • JS-parser可能被反斜杠和引号欺骗(编码器非常糟糕)

,编码所有可能会混淆不同解析器的字符会更“安全”。保持人类可读性可能与您的安全范例相矛盾。

注意:JSON字符串编码的结果应该是canoncical和OFC,而不是像可解析的那样破坏。 JSON是JS的一个子集,因此可以在没有任何风险的情况下进行JS解析。因此,您所要做的就是确保提取JS代码的HTML-Parser实例不会被您的用户数据欺骗。

所以真正的陷阱是两个解析器的嵌套。实际上,我会敦促你把这样的东西放到一个单独的请求中。这样你就可以完全避免这种情况。

假设在这样的解析器中可能发生的所有可能的样式和错误更正,可能是其他标记(打开或关闭)可能实现类似的壮举。

如下所示:向解析器建议脚本标记已隐式结束。

所以建议对斜杠和所有标记括号(/,&lt;,&gt;)进行编码,而不仅仅是关闭脚本标记,只要你选择任何可逆的方法,只要它不会混淆。 HTML-Parser:

  • 最佳选择是base64(但你想要更具可读性)
  • HTMLentities会做,虽然令人困惑:)
  • 执行自己的转义也会有效,只是逃避单个字符而不是</script片段

总之,是的,这可能是最好的一些变化,但请注意,您已经离“安全”已经一步之遥,首先尝试这样的事情,而不是通过XHR加载JSON或至少使用严格的字符串编码,如base64。

P.S。:如果您可以从其他人的代码中学习编码好的字符串,但如果他们不能完全满足您的需要,您就不应该求助于“图书馆”或其他人的功能。 因此,请编写并彻底测试您自己的(de / en)编码器,并知道这个陷阱已被密封。

答案 1 :(得分:1)

这是我处理此问题中相对较小的部分的方法,即在脚本元素中存储JSON的编码问题。简短的答案是,您必须转义</,因为它们一起终止了脚本元素-即使在JSON字符串文字中也是如此。您can't HTML-encode entities输入一个脚本元素。您可以JavaScript反斜杠转义斜杠。我更喜欢用JavaScript十六进制转义小于小于的括号作为\u003C

.replace('<', r'\u003C')

我在尝试从oembed结果传递json时遇到了这个问题。其中一些包含脚本关闭标记(名称中未提及Twitter)。

json_for_script = json.dumps(data).replace('<', r'\u003C');

这会将data = {'test': 'foo </script> bar'};变成

'{"test": "foo \\u003C/script> bar"}'

这是不会终止脚本元素的有效JSON。

我从little gem模板引擎中的Jinja中得到了这个主意。这是您使用{{data|tojson}} filter时运行的。

def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
    """Works exactly like :func:`dumps` but is safe for use in ``<script>``
    tags.  It accepts the same arguments and returns a JSON string.  Note that
    this is available in templates through the ``|tojson`` filter which will
    also mark the result as safe.  Due to how this function escapes certain
    characters this is safe even if used outside of ``<script>`` tags.
    The following characters are escaped in strings:
    -   ``<``
    -   ``>``
    -   ``&``
    -   ``'``
    This makes it safe to embed such strings in any place in HTML with the
    notable exception of double quoted attributes.  In that case single
    quote your attributes or HTML escape it in addition.
    """
    if dumper is None:
        dumper = json.dumps
    rv = dumper(obj, **kwargs) \
        .replace(u'<', u'\\u003c') \
        .replace(u'>', u'\\u003e') \
        .replace(u'&', u'\\u0026') \
        .replace(u"'", u'\\u0027')
    return Markup(rv)

(您可以使用\x3C而不是\xu003C,因为它是有效的JavaScript,因此可以在脚本元素中使用。但是也可以坚持使用valid JSON。)