jQuery 3.5.0安全修复细节:CVE-2020-11022与CVE-2020-11023漏洞解析

本文详细解析了jQuery 3.5.0中修复的两个安全漏洞CVE-2020-11022和CVE-2020-11023,包括漏洞原理、PoC示例、修复方案及防护建议,帮助开发者提升Web应用安全性。

CVE-2020-11022/CVE-2020-11023: jQuery 3.5.0安全修复细节

jQuery 3.5.0于上月发布。此版本包含了我报告的两个作为“安全修复”的bug。

jQuery 3.5.0 Released! | Official jQuery Blog
https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/

这些bug被注册为CVE-2020-11022和CVE-2020-11023:

https://github.com/advisories/GHSA-gxr4-xjj5-5px2
https://github.com/advisories/GHSA-jpcq-cgw6-v4j6

在本文中,我将解释这些bug的细节。

问题概述

具有以下特性的应用程序会受到影响:

  • 应用程序允许用户写入任意HTML(但经过清理)
  • 应用程序使用jQuery动态附加清理后的HTML

以下代码是此类应用程序的示例:

1
2
3
4
5
6
7
<div id="div"></div>
<script>
// 清理后的安全HTML
sanitizedHTML = '<p title="foo">bar</p>';
// 仅将清理后的HTML附加到<div>
$('#div').html(sanitizedHTML);
</script>

在这种情况下,如果清理操作正确执行,通常不会发生XSS,因为它只是附加了清理后的安全HTML。然而,实际上,.html()在内部执行了特殊的字符串处理,并导致了XSS。这就是我将在本文中解释的问题。

PoCs

存在许多变体,但我将展示三个基本的PoC。通常,以下HTML不会执行JavaScript:

PoC 1.

1
<style><style /><img src=x onerror=alert(1)>

PoC 2. (仅jQuery 3.x受影响)

1
<img alt="<x" title="/><img src=x onerror=alert(1)>">

PoC 3.

1
<option><style></option></select><img src=x onerror=alert(1)></style>

您可能认为有一个带有onerror属性的img标签,但仔细检查后,您会发现它实际上位于属性或style元素内部,因此不会执行。所以,即使这些HTML是由HTML清理器生成的清理后HTML,也完全不奇怪。

然而,在所有情况下,如果HTML通过jQuery的.html()附加,JavaScript会意外执行。

您可以在以下位置测试每个PoC:
https://vulnerabledoma.in/jquery_htmlPrefilter_xss.html

接下来,我将解释为什么会发生这种情况。

CVE-2020-11023:根本原因(PoC 1,2)

PoC 1和2具有相同的根本原因。在.html()内部,作为参数传递的HTML字符串被传递给$.htmlPrefilter()方法。htmlPrefilter使用以下正则表达式执行处理,将自闭合标签如<tagname />替换为<tagname ></tagname>

1
2
3
4
5
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi
[...]
htmlPrefilter: function( html ) {
  return html.replace( rxhtmlTag, "<$1></$2>" );
}

如果PoC 1的HTML经过此替换,输出将是:

1
2
> $.htmlPrefilter('<style><style /><img src=x onerror=alert(1)>')
< "<style><style ></style><img src=x onerror=alert(1)>"

黄色部分是替换后的字符串。由于此替换,style元素内的<style />被替换为<style ></style>,结果导致之后的字符串被踢出style元素。之后,.html()将替换后的HTML分配给innerHTML。这里,<img ...>字符串成为实际的img标签,并触发onerror事件。

顺便说一下,上述正则表达式在jQuery 3.x之前使用。从3.x开始,使用了另一个稍作修改的正则表达式:

https://github.com/jquery/jquery/commit/fb9472c7fbf9979f48ef49aff76903ac130d0959#diff-169760a97de5c86a886842060321d2c8L30-R30

1
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi

此更改引入了另一个XSS向量,该向量仅通过更基本的元素和属性即可导致XSS。PoC 2的向量就是由此更改引入的。它仅在jQuery 3.x上有效。

1
2
> $.htmlPrefilter('<img alt="<x" title="/><img src=x onerror=alert(1)>">')
< "<img alt="<x" title="></x"><img src=x onerror=alert(1)>">"

在这种情况下,属性值上的<img ...>字符串被踢出,导致XSS。

我解释了PoC 1和2的根本原因。jQuery团队如何修复此问题?

修复(PoC 1,2)

jQuery团队通过将$.htmlPrefilter()方法替换为恒等函数来修复此问题。因此,传递的HTML字符串不再由htmlPrefilter函数修改。

https://github.com/jquery/jquery/commit/90fed4b453a5becdb7f173d9e3c1492390a1441f#diff-169760a97de5c86a886842060321d2c8L201-R198

然而,这并未解决所有XSS问题。在.html()内部,执行了另一个字符串处理,并引入了另一个问题(PoC 3)。

CVE-2020-11022:根本原因(PoC 3)

.html()内部,如果作为参数传递的HTML开头的标签是特定标签之一,jQuery会尝试用另一个标签包装它一次,并进行下一步处理。这是因为某些标签由于HTML规范或浏览器bug,如果没有包装处理,会自动被移除。

option元素就是此类元素之一——在MSIE9中,由于其bug,如果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将变为:

1
<select multiple='multiple'><option><style></option></select><img src=x onerror=alert(1)></style></select>

当此HTML在jQuery内部代码中分配给innerHTML时,JavaScript被执行。

脚本执行的原因在于<select>标签的解析。<select>不允许在该元素内部放置除optionoptgroupscripttemplate元素之外的HTML标签。由于此规范,插入的<style>被忽略,<style>内部的</select>成为实际的select元素的闭合标签,然后<select>块在那里关闭。最终,接下来的<img ...>被踢出<style>onerror事件触发 -> XSS。这就是根本原因。

修复(PoC 3)

jQuery团队通过仅对MSIE9应用包装处理来修复此问题。

https://github.com/jquery/jquery/commit/966a70909019aa09632c87c0002c522fa4a1e30e#diff-51ec14165275b403bb33f28ce761cdedR25

MSIE9不易受此问题影响,因为MSIE9的<select>解析有些特殊(是的,这是错误的)。因此,仅对MSIE9应用包装处理可以解决此问题。

供您参考,这些问题不仅存在于.html()中,还存在于.append()$('<tag>')等中。基本上,问题通过内部使用$.htmlPrefilter()方法或包装处理的API发生。

更新它

如果您的应用程序通过jQuery函数附加清理后的HTML,您应该更新到3.5.0或更高版本。如果由于某些原因更新困难,我建议使用DOMPurify(XSS清理器)清理HTML。DOMPurify有一个SAFE_FOR_JQUERY选项,可以在考虑jQuery行为的情况下进行清理。您可以像这样使用它:

1
2
3
4
5
6
<div id="div"></div>
<script>
unsafeHtml = '<img alt="<x" title="/><img src=x onerror=alert(1)>">';
var sanitizedHtml = DOMPurify.sanitize( unsafeHtml, { SAFE_FOR_JQUERY: true } );
$('#div').html( sanitizedHtml );
</script>

请注意,DOMPurify最近在SAFE_FOR_JQUERY中存在绕过。请确保您使用2.0.8或更高版本。

最后

我从@PwnFunction的XSS挑战开始调查此问题:
https://xss.pwnfunction.com/challenges/ww3/

实际上,其中一些bug是已知的,并且是该挑战的预期解决方案。(您可以在DOMPurify的变更日志中找到这一事实。至少从2014年起就已经知道,并且DOMPurify自2014年起就有SAFE_FOR_JQUERY选项。)

以挑战为触发点,我开始重新阅读jQuery的源代码,并注意到另一个未公开提及的向量(PoC 2)。由于此向量仅通过简单的元素和属性即可允许XSS,我认为许多应用程序都易受攻击。当我实际调查时,我立即发现了一些易受攻击的应用程序。我向受影响应用程序的开发者报告了此问题,同时我认为此问题应由jQuery方面修复,因此我决定向jQuery团队报告。jQuery团队迅速解决了这些问题,尽管他们必须进行破坏性更改。感谢jQuery团队。同时,感谢@PwnFunction,XSS挑战的创建者,他给了我调查此问题的机会。

就是这样。我希望本文有助于保护您的Web应用程序或发现bug。

发布者:Masato Kinugawa
时间:12:45 PM

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计