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

Posted

技术标签:

【中文标题】如何在 HTML 脚本标签中插入任意 JSON【英文标题】:How to insert arbitrary JSON in HTML's script tag 【发布时间】:2017-01-04 17:35:09 【问题描述】:

我想将 JSON 的内容存储在 html 文档的源代码中,在脚本标记内。

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

我在 SO 上阅读了两个概念。

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://***.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('&lt;/script', r'&lt;\/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 stringSanitize <script> element contentsEscape </ in script tag contents

关于这个问题的一些很好的外部资源: Flask 的 tojson 过滤器的实现 source Rail 的 json_escape 方法的 help 和 source 在 Django ticket 和 proposed code 中进行了长达 5 年的讨论

【问题讨论】:

您应该将 &lt;&gt;&amp; 编码为 HTML 实体。 我花了一个小时写这个问题,包括参考我找到的所有以前的 SO 答案。收到一个单行和一个关闭 / -1 感觉根本没有帮助。 至少 JSON.stringify() 和 Python 的 json.dumps() 不会将 / 转义为 \/。我正在寻找一种自动化方式,它使用脚本标记解析器来解码 JSON 或字符串上的 JSON.parse()。在服务器端手动转义也需要在客户端手动进行。 自上次评论以来,我发现 Flask 中的 |tojson 过滤器实现是最好的资源。源代码以及一些非常重要的 cmets 都写在那里。 github.com/pallets/flask/blob/…我对正确方法的理解如下:1.使用方法1.来自我的问题。 2. 将 、& 和 ' 编码为 u00 形式(不是 HTML 实体!)。 3. 仔细检查 JSON 编码器是否转义 `\`,因为它取决于不同的实现(有时甚至会更改中间版本)。 请参阅 archive.oreilly.com/pub/a/actionscript/excerpts/as3-cookbook/… 以获取 、&、引号和斜杠的“u00”替代列表。 【参考方案1】:

以下是我如何处理这个问题中相对较小的部分,即在脚本元素中存储 JSON 的编码问题。简短的回答是您必须转义&lt;/,因为它们一起终止了脚本元素——即使在JSON 字符串文字中也是如此。您 can't HTML-encode entities 获取脚本元素。你可以 JavaScript-backslash-escape 斜杠。我更喜欢 JavaScript-hex-escape 小于尖括号为\u003C

.replace('&lt;', r'\u003C')

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

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

这会将data = 'test': 'foo &lt;/script&gt; bar'; 变成

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

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

我从Jinja 模板引擎中的little gem 得到这个想法。当您使用 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。)

【讨论】:

@hyperknot 现在我看到你的评论,你在几年前链接到这个相同的例程。伙计,我希望我能早点看到。我经历的奥德赛找到它。哦,好吧,这实际上是令人放心的。我会让这个答案成为.replace('&lt;', r'\x3C') 我认为你的问题的一个方便的答案。【参考方案2】:

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

HTML 解析器可能会被结束脚本标签欺骗(最好是任何结束标签) JS 解析器可能会被反斜杠和引号欺骗(使用非常糟糕的编码器)

是的,对所有可能混淆所涉及的不同解析器的字符进行编码会更“安全”。保持其可读性可能与您的安全范式相矛盾。

注意:JSON字符串编码的结果应该是规范的和OFC的,而不是破坏的,如可解析的。 JSON 是 JS 的子集,因此可以毫无风险地进行 JS 解析。因此,您所要做的就是确保提取 JS 代码的 HTML-Parser 实例不会被您的用户数据欺骗。

所以真正的陷阱是两个解析器的嵌套。实际上,我会敦促您将类似的内容放入单独的请求中。这样你就可以完全避免这种情况。

假设在这样的解析器中可能发生所有可能的样式和纠错,那么其他标签(打开或关闭)可能会实现类似的壮举。

如:向解析器暗示脚本标签已隐式结束。

因此建议以您选择的任何可逆方法对斜杠和所有标签大括号 (/,) 进行编码,而不仅仅是脚本标签的关闭,只要它不会混淆 HTML -解析器:

最好的选择是 base64(但您希望更具可读性) HTMLentities 可以,虽然会让人感到困惑 :) 自己转义也可以,只需转义单个字符而不是 &lt;/script 片段

总而言之,是的,最好进行一些更改,但请注意,首先尝试这样的操作,而不是通过 XHR 加载 JSON 或至少使用严格的字符串编码,如 base64。

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

【讨论】:

HTML 解析器不会被结束脚本标签“欺骗”;它以所需的记录方式识别所谓的不可替换字符数据中的结束标记。

以上是关于如何在 HTML 脚本标签中插入任意 JSON的主要内容,如果未能解决你的问题,请参考以下文章

在脚本标签中嵌入 JSON 对象

如何更改新添加的脚本标签内容的缩进

如何从js更改html中脚本标签中的数据?

如何将脚本标签插入头部标签的顶部/开头?

如何使用美丽的汤从脚本标签中提取 json?

如何在 <script> 标记中的 html 上使用 json 脚本并在 <img src=???/> 中填充数据