CVE-2020-11022/CVE-2020-11023: jQuery 3.5.0 安全修复详情
jQuery 3.5.0 已于上月发布。此版本包含了我报告的两个作为“安全修复”的漏洞。
jQuery 3.5.0 发布! | 官方 jQuery 博客
https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/
这两个漏洞已注册为 CVE-2020-11022 和 CVE-2020-11023:
https://github.com/advisories/GHSA-gxr4-xjj5-5px2
https://github.com/advisories/GHSA-jpcq-cgw6-v4j6
在本文中,我将解释这些漏洞的详细信息。
问题概述
具有以下特性的应用程序会受到影响:
- 应用程序允许用户编写任意 HTML(但会进行清理)
- 应用程序使用 jQuery 动态附加清理后的 HTML
以下代码是此类应用程序的示例:
|
|
在这种情况下,如果清理操作正确执行,通常看起来不会发生 XSS,因为它只是附加了经过清理的安全 HTML。然而,实际上,.html()
在内部进行了特殊的字符串处理,并导致了 XSS。这就是我将在本文中解释的问题。
PoC 示例
存在多种变体,但我将展示三个基本的 PoC。通常,以下 HTML 不会执行 JavaScript:
PoC 1.
|
|
PoC 2. (仅影响 jQuery 3.x)
|
|
PoC 3.
|
|
您可能认为有一个带有 onerror
属性的 img
标签,但仔细检查后,您会发现它实际上位于属性或 style
元素内部,因此不会被执行。因此,即使这些 HTML 是由 HTML 清理器生成的清理后 HTML,也完全不显得异常。
然而,在所有情况下,如果通过 jQuery 的 .html()
附加 HTML,JavaScript 会意外执行。
您可以在以下链接测试每个 PoC:
https://vulnerabledoma.in/jquery_htmlPrefilter_xss.html
接下来,我将解释为什么会发生这种情况。
CVE-2020-11023:根本原因(PoC 1 和 2)
PoC 1 和 2 具有相同的根本原因。在 .html()
内部,作为参数传递的 HTML 字符串会传递给 $.htmlPrefilter()
方法。htmlPrefilter
使用以下正则表达式处理自闭合标签(如 <tagname />
)的替换:
|
|
如果 PoC 1 的 HTML 经过此替换,输出将是:
|
|
黄色部分是替换后的字符串。由于此替换,style
元素内的 <style />
被替换为 <style ></style>
,结果导致之后的字符串被踢出 style
元素。之后,.html()
将替换后的 HTML 分配给 innerHTML
。此时,<img ...>
字符串变为实际的 img
标签,并触发 onerror
事件。
顺便提一下,上述正则表达式在 jQuery 3.x 之前使用。从 3.x 开始,使用了稍作修改的另一个正则表达式:
|
|
此更改引入了另一个 XSS 向量,该向量仅通过更基本的元素和属性即可导致 XSS。PoC 2 的向量就是由此更改引入的,它仅适用于 jQuery 3.x。
|
|
在这种情况下,属性值中的 <img ...>
字符串被踢出,导致 XSS。
我解释了 PoC 1 和 2 的根本原因。jQuery 团队是如何修复这个问题的?
修复方案(PoC 1 和 2)
jQuery 团队通过将 $.htmlPrefilter()
方法替换为恒等函数来修复此问题。因此,传递的 HTML 字符串不再被 htmlPrefilter
函数修改。
然而,这并没有解决所有 XSS 问题。在 .html()
内部,还进行了另一种字符串处理,并引入了另一个问题(PoC 3)。
CVE-2020-11022:根本原因(PoC 3)
在 .html()
内部,如果作为参数传递的 HTML 开头出现的标签是特定标签之一,jQuery 会尝试用另一个标签包装它并进行后续处理。这是因为某些标签由于 HTML 规范或浏览器错误,在没有包装处理的情况下会被自动移除。
option
元素就是这样的元素之一——在 MSIE9 中,由于浏览器错误,如果 option
元素没有用 select
元素包装,则在分配给 innerHTML
时会自动移除。
为了处理这个问题,如果传递的 HTML 字符串的第一个元素是 option
元素,jQuery 会尝试用 <select multiple='multiple'>
和 </select>
包装整个传递的 HTML 字符串。
包装标签的定义位于:
https://github.com/jquery/jquery/blob/3.4.1/src/manipulation/wrapMap.js#L9
实际的包装处理在以下位置完成:
https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/manipulation/buildFragment.js#L39
PoC 3 的问题就是通过这种包装处理发生的。如果 PoC 3 的 HTML 经过此包装处理,HTML 将变为:
|
|
当此 HTML 在 jQuery 内部代码中分配给 innerHTML
时,JavaScript 会被执行。
脚本执行的原因在于 <select>
标签的解析。<select>
不允许在该元素内部放置除 option
、optgroup
、script
和 template
元素之外的 HTML 标签。由于此规范,插入的 <style>
被忽略,<style>
内的 </select>
变为实际的 select
元素的闭合标签,然后 <select>
块在此处关闭。最终,接下来的 <img ...>
被踢出 <style>
,onerror
事件触发 -> XSS。这就是根本原因。
修复方案(PoC 3)
jQuery 团队通过仅对 MSIE9 应用包装处理来修复此问题。
MSIE9 不易受此问题影响,因为 MSIE9 的 <select>
解析有些特殊(是的,这是错误的)。因此,仅对 MSIE9 应用包装处理可以解决此问题。
需要注意的是,这些问题不仅存在于 .html()
中,还存在于 .append()
、$('<tag>')
等函数中。基本上,问题是通过内部使用 $.htmlPrefilter()
方法或包装处理的 API 发生的。
更新建议
如果您的应用程序通过 jQuery 函数附加清理后的 HTML,您应该更新到 3.5.0 或更高版本。如果由于某些原因难以更新,我建议使用 DOMPurify(一种 XSS 清理器)来清理 HTML。DOMPurify 有一个 SAFE_FOR_JQUERY
选项,可以在考虑 jQuery 行为的情况下进行清理。您可以像这样使用它:
|
|
请注意,DOMPurify 最近在 SAFE_FOR_JQUERY
中存在绕过问题。请确保使用 2.0.8 或更高版本。
最后
我从 @PwnFunction 的 XSS 挑战开始调查此问题:
https://xss.pwnfunction.com/challenges/ww3/
实际上,其中一些漏洞是已知的,并且是该挑战的预期解决方案。(您可以在 DOMPurify 的变更日志中找到这一事实。至少从 2014 年起就已经知道,并且 DOMPurify 从 2014 年起就有了 SAFE_FOR_JQUERY
选项。)
以该挑战为契机,我开始重新阅读 jQuery 的源代码,并注意到了另一个未公开提及的向量(PoC 2)。由于该向量仅通过简单的元素和属性即可允许 XSS,我认为许多应用程序都存在漏洞。当我实际调查时,立即发现了一些易受攻击的应用程序。我向受影响应用程序的开发者报告了此问题,同时认为此问题应由 jQuery 方面修复,因此我决定向 jQuery 团队报告。jQuery 团队迅速解决了这些问题,即使他们必须进行破坏性更改。感谢 jQuery 团队。同时,感谢 @PwnFunction,XSS 挑战的创建者,他给了我调查此问题的机会。
就是这样。我希望本文有助于保护您的 Web 应用程序或发现漏洞。