Firefox通用CSP strict-dynamic绕过漏洞分析(CVE-2018-5175)

本文详细分析了Firefox中存在的CSP strict-dynamic通用绕过漏洞(CVE-2018-5175),该漏洞允许攻击者通过开发者工具中的require.js库绕过内容安全策略保护,执行恶意脚本。文章深入探讨了strict-dynamic机制的工作原理、已知绕过方法,以及Firefox内部资源访问机制如何导致这一安全缺陷。

CVE-2018-5175:Firefox中的通用CSP strict-dynamic绕过

在本篇博客中,我将介绍一个在Firefox 60中修复的CSP strict-dynamic绕过漏洞。

https://www.mozilla.org/en-US/security/advisories/mfsa2018-11/#CVE-2018-5175

该漏洞允许绕过使用"strict-dynamic"脚本源策略的网站的内容安全策略(CSP)保护。如果目标网站存在HTML注入漏洞,攻击者可以注入对Firefox开发者工具中require.js库的引用,然后利用该库的已知技术绕过CSP限制执行注入脚本。

什么是"strict-dynamic"?

也许你应该阅读CSP规范:https://www.w3.org/TR/CSP3/#strict-dynamic-usage

但为了练习英语写作,我将解释strict-dynamic。如果你已经了解strict-dynamic,可以跳过本节。

众所周知的CSP通过白名单域名来限制资源加载。例如,以下CSP设置仅允许从自身源和trusted.example.com加载JavaScript:

1
Content-Security-Policy: script-src 'self' trusted.example.com

由于此CSP,即使页面存在XSS漏洞,也能防止执行来自内联脚本或evil.example.org的JavaScript文件。这看起来足够安全,但如果trusted.example.com有任何绕过CSP的脚本,仍然可能执行JavaScript。更具体地说,如果trusted.example.com有JSONP端点,可能会这样被绕过:

1
<script src="//trusted.example.com/jsonp?callback=alert(1)//"></script>

如果此端点将传递给callback参数的用户输入直接反射到回调函数名,则可以用作任意脚本:

1
alert(1)//({});

此外,已知AngularJS也可用于绕过CSP。如果允许托管许多JavaScript文件的域(如CDN),这种绕过可能性变得更加现实。

这样,在白名单中,有时难以安全地操作CSP。为解决此问题,设计了strict-dynamic。以下是使用示例:

1
Content-Security-Policy: script-src 'nonce-secret' 'strict-dynamic'

此CSP意味着将禁用白名单,仅加载具有nonce属性中"secret"字符串的脚本。

1
2
3
4
5
<!-- 这将加载 -->
<script src="//example.com/assets/A.js" nonce="secret"></script>

<!-- 这将不会加载 -->
<script src="//example.com/assets/B.js"></script>

A.js可能希望加载和使用另一个JavaScript。为允许此操作,CSP规范允许在具有正确nonce的js在特定条件下加载另一个js时,无需适当的nonce属性。用规范中的话来说,允许执行非"parser-inserted"脚本元素。

以下是允许执行的JavaScript类型的具体示例:

1
2
3
4
5
6
7
8
9
/* A.js */

//这将加载
var script=document.createElement('script');
script.src='//example.org/dependency.js';
document.body.appendChild(script);

//这将不会加载
document.write("<scr"+"ipt src='//example.org/dependency.js'></scr"+"ipt>");

使用createElement()加载时,它是非"parser-inserted"脚本元素,允许加载。而使用document.write()加载时,它是"parser-inserted"脚本元素,不会被加载。

到此,我大致解释了strict-dynamic。

顺便说一句,strict-dynamic在某些情况下可被绕过。接下来,我将介绍一个已知的strict-dynamic绕过方法。

已知的strict-dynamic绕过

已知如果目标页面使用特定库,strict-dynamic也可被绕过。

由Google的Sebastian Lekies、Eduardo Vela Nava和Krzysztof Kotowicz研究,受影响库列表在此: https://github.com/google/security-research-pocs/blob/master/script-gadgets/bypasses.md

让我们看看此列表中require.js的strict-dynamic绕过。

假设目标页面使用带strict-dynamic的CSP,加载require.js并有简单XSS。在这种情况下,如果插入以下脚本元素,攻击者可以在没有适当nonce的情况下执行任意JavaScript。

1
2
3
4
5
<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<!-- XSS END -->
<script nonce="secret" src="require.js"></script>

当require.js找到带有data-main属性的脚本元素时,它会从等效于以下代码加载data-main属性中指定的脚本:

1
2
3
var node = document.createElement('script');
node.src = 'data:,alert(1)';
document.head.appendChild(node);

如前所述,strict-dynamic允许通过createElement()加载JavaScript而无需适当的nonce。

这样,在某些情况下,你可以利用已加载JavaScript代码的行为绕过CSP strict-dynamic。

Firefox的漏洞就是由require.js的这种行为引起的。在下一节中,我将解释该漏洞。

通用strict-dynamic绕过(CVE-2018-5175)

Firefox使用传统扩展实现某些浏览器功能。传统扩展指的是基于XUL/XPCOM的扩展,在Firefox 57中移除,而非WebExtensions。即使在最新的Firefox 60中,浏览器内部仍然使用此机制。

在此绕过中,我们使用浏览器内部使用的传统扩展资源。在WebExtensions中,通过在清单中设置web_accessible_resources键,列出的资源可从任何网页访问。传统扩展具有名为contentaccessible标志的类似选项。在此绕过中,由于浏览器内部资源的require.js因contentaccessible=yes标志可从任何网页访问,因此可用于绕过CSP。

让我们查看清单。如果你在Windows上使用64位Firefox,可以从以下URL查看清单:

1
jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/chrome.manifest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
content branding browser/content/branding/ contentaccessible=yes
content browser browser/content/browser/ contentaccessible=yes
skin browser classic/1.0 browser/skin/classic/browser/
skin communicator classic/1.0 browser/skin/classic/communicator/
content webide webide/content/
skin webide classic/1.0 webide/skin/
content devtools-shim devtools-shim/content/
content devtools devtools/content/
skin devtools classic/1.0 devtools/skin/
locale branding ja ja/locale/branding/
locale browser ja ja/locale/browser/
locale browser-region ja ja/locale/browser-region/
locale devtools ja ja/locale/ja/devtools/client/
locale devtools-shared ja ja/locale/ja/devtools/shared/
locale devtools-shim ja ja/locale/ja/devtools/shim/
locale pdf.js ja ja/locale/pdfviewer/
overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
override chrome://global/content/license.html chrome://browser/content/license.html
override chrome://global/content/netError.xhtml chrome://browser/content/aboutNetError.xhtml
override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://browser/locale/downloads/settingsChange.dtd
resource search-plugins chrome://browser/locale/searchplugins/
resource usercontext-content browser/content/ contentaccessible=yes
resource pdf.js pdfjs/content/
resource devtools devtools/modules/devtools/resource devtools-client-jsonview resource://devtools/client/jsonview/ contentaccessible=yes
resource devtools-client-shared resource://devtools/client/shared/ contentaccessible=yes

黄色部分使文件可从任何网站访问。这两行用于创建resource: URI。第一行的resource devtools devtools/modules/devtools/将devtools/modules/devtools/目录(存在于jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/devtools/modules/devtools/)映射到resource://devtools/。

我们现在可以通过在Firefox中打开resource://devtools/来访问该目录下的文件。同样,下一行映射到resource://devtools-client-jsonview/。此URL通过contentaccessible=yes标志变得可 web访问,我们现在可以从任何网页加载此目录下的文件。

此目录有一个require.js,可用于绕过CSP。只需将此require.js加载到使用CSP strict-dynamic的页面,即可绕过strict-dynamic。

实际绕过如下:

https://vulnerabledoma.in/fx_csp_bypass_strict-dynamic.html

1
2
3
4
5
<meta http-equiv="Content-Security-Policy" content="default-src 'none';script-src 'nonce-secret' 'strict-dynamic'">
<!-- XSS START -->
<script data-main="data:,alert(1)"></script>
<script src="resource://devtools-client-jsonview/lib/require.js"></script>
<!-- XSS END -->

从此代码中,data: URL将作为JavaScript资源加载,并弹出警报对话框。

你可能会想,“嗯,为什么require.js被加载?它应该被CSP阻止,因为脚本元素没有适当的nonce。”

实际上,无论你设置多么严格的CSP规则,扩展的web可访问资源都会忽略CSP加载。此行为在CSP规范中提到:

https://www.w3.orgTR/CSP3/#extensions

对资源强制实施的策略不应干扰用户代理功能(如插件、扩展或书签)的操作。这些功能通常将用户的优先级置于页面作者之上,如[HTML-DESIGN]中所阐述。

Firefox的resource: URI也有此规则。因此,用户可以在设置CSP的页面上按预期使用扩展功能,但另一方面,此特权有时可用于绕过CSP,如此错误的情况。

当然,此问题不仅限于浏览器内部资源。即使在一般浏览器扩展上,如果存在可用于绕过CSP的web可访问资源,也会发生同样的情况。

似乎Firefox团队通过将页面的CSP应用于resource: URI修复了此错误。

文章结尾

我写了关于Firefox的CSP strict-dynamic绕过漏洞。

仅供参考,我在寻找我制作的Cure53 CNY XSS Challenge 2018第三级的另一种解决方案时发现了此问题。在此挑战中,我使用了另一种技巧来绕过strict-dynamic。如果你感兴趣,请查看。

此外,我创建了此XSS挑战的不同版本,仍在等待你的答案:)

最后,我要感谢Google的研究让我注意到此错误。谢谢!

发布者:Masato Kinugawa

时间:上午2:02

标签:CSP, Firefox, 安全, XSS

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