在本文中,我們將重點(diǎn)分析如何繞過(guò)Firefox內(nèi)容安全策略中的“Strict-Dynamic”限制。該漏洞詳情請(qǐng)參考: https://www.mozilla.org/en-US/security/advisories/mfsa2018-11/#CVE-2018-5175 。該漏洞將繞過(guò)內(nèi)容安全策略(CSP)的保護(hù)機(jī)制,而在該機(jī)制中包含一個(gè)“嚴(yán)格動(dòng)態(tài)限制”的Script-src策略。如果目標(biāo)網(wǎng)站中存在HTTP注入漏洞,攻擊者可以將一個(gè)引用注入到require.js庫(kù)的一個(gè)副本中,這個(gè)庫(kù)位于Firefox開(kāi)發(fā)人員工具之中,攻擊者隨后便可以使用已知技術(shù),利用該庫(kù)繞過(guò)CSP限制,從而執(zhí)行注入腳本。
各位讀者可能已經(jīng)閱讀過(guò)內(nèi)容安全策略的規(guī)范( https://www.w3.org/TR/CSP3/#strict-dynamic-usage ),但在這里,我還是有必要先對(duì)“Strict-Dynamic”(嚴(yán)格動(dòng)態(tài)限制)進(jìn)行解釋。如果讀者已經(jīng)完全掌握相關(guān)知識(shí),可以跳過(guò)本節(jié)的閱讀。
眾所周知的內(nèi)容安全策略(CSP)限制,其原理是通過(guò)將域名列入白名單來(lái)限制資源的加載。舉例來(lái)說(shuō),下面的CSP設(shè)置僅允許從其自身的來(lái)源和trusted.example.com域名加載JavaScript:
Content-Security-Policy: script-src 'self' trusted.example.com
由于這個(gè)內(nèi)容安全策略的存在,即使在頁(yè)面中存在XSS漏洞,該頁(yè)面也無(wú)法通過(guò)內(nèi)聯(lián)腳本或evil.example.org的JavaScript文件來(lái)執(zhí)行JavaScript腳本。這一策略看起來(lái)確實(shí)足夠安全,但是,如果在trusted.example.org中存在任何繞過(guò)內(nèi)容安全策略的腳本,那么就仍然可以執(zhí)行JavaScript。更具體地說(shuō),如果在trusted.example.com中存在一個(gè)JSONP端點(diǎn),那么就有可能被繞過(guò),如下所示:
<script src="http://trusted.example.com/jsonp?callback=alert(1)//"></script>
如果此端點(diǎn)直接將用戶(hù)輸入的參數(shù)傳遞給callback函數(shù),那么就可以執(zhí)行任意腳本,示例中的腳本如下:
alert(1)//({});
另外,目前已知AngularJS也可以用于繞過(guò)內(nèi)容安全策略( https://github.com/cure53/XSSChallengeWiki/wiki/H5SC-Minichallenge-3:-%22Sh*t,-it%27s-CSP!%22#127-bytes )。這種繞過(guò)方式的利用可能會(huì)更為實(shí)際,特別適用于允許托管許多JavaScript文件(如CDN)的域名。
這樣一來(lái),即使在白名單中,有時(shí)也很難通過(guò)內(nèi)容安全策略來(lái)保障安全性。為了解決這一問(wèn)題,就設(shè)計(jì)了“Strict-Dynamic”的限制。其用法示例如下:
Content-Security-Policy: script-src 'nonce-secret' 'strict-dynamic'
這就意味著白名單將被禁用,并且只有在nonce屬性中具有“secret”字符串的腳本才會(huì)被加載。
<!-- This will load -->
<script src="http://example.com/assets/A.js" nonce="secret"></script>
<!-- This will not load -->
<script src="http://example.com/assets/B.js"></script>
在這里,A.js可能想要加載并使用另一個(gè)JavaScript。為了實(shí)現(xiàn)這一點(diǎn),內(nèi)容安全策略規(guī)范中允許具有正確nonce屬性的JavaScript,在特定條件下加載沒(méi)有正確nonce屬性的JavaScript。使用規(guī)范中的關(guān)鍵詞,就可以允許非解析型腳本(Parser-Inserted Script)元素執(zhí)行JavaScript。
示例如下:
/* A.js */
//This will load
var script=document.createElement('script');
script.src='//example.org/dependency.js';
document.body.appendChild(script);
//This will not load
document.write("<scr"+"ipt src='//example.org/dependency.js'></scr"+"ipt>");
當(dāng)使用createElement()加載時(shí),它是一個(gè)非解析型腳本元素,該加載動(dòng)作被允許。另一個(gè)反例是,使用document.write()加載時(shí),它是一個(gè)解析型腳本元素(Parser-Inserted Script Element),所以不會(huì)被加載。
到目前為止,我已經(jīng)大致地解釋了“Strict-Dynamic”。順便要提一句,“Strict-Dynamic”在某些情況下是可以被繞過(guò)的。下面我就介紹一種已知的“Strict-Dynamic”的繞過(guò)方式。
如果在目標(biāo)頁(yè)面中使用特定的庫(kù),那么Strict-Dynamic就可以被繞過(guò)。
該繞過(guò)方式已經(jīng)由Google的Sebastian Lekies、Eduardo Vela Nava、Krzysztof Kotowicz進(jìn)行測(cè)試,受影響的庫(kù)請(qǐng)參見(jiàn): https://github.com/google/security-research-pocs/blob/master/script-gadgets/bypasses.md 。
接下來(lái),我們來(lái)看看這個(gè)列表中借助require.js實(shí)現(xiàn)Strict-Dynamic繞過(guò)的方法。
假設(shè)目標(biāo)頁(yè)面使用了Strict-Dynamic的內(nèi)容安全策略,并且加載require.js,同時(shí)具有簡(jiǎn)單的XSS漏洞。在這種情況下,如果輸入以下腳本元素,攻擊者就可以在沒(méi)有正確的nonce的情況下執(zhí)行任意JavaScript。
<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>
當(dāng)require.js找到一個(gè)具有data-main屬性的腳本元素時(shí),它會(huì)加載data-main屬性中指定的腳本,其等效代碼如下:
var node = document.createElement('script');
node.url = 'data:,alert(1)';
document.head.appendChild(node);
如前所述,Strict-Dynamic允許從createElement()加載沒(méi)有正確nonce的JavaScript腳本。這樣一來(lái),就可以借助某些已經(jīng)加載的JavaScript代碼行為,在某種情況下繞過(guò)內(nèi)容安全策略的Strict-Dynamic。而在Firefox中的漏洞,正是由于require.js的這種情況引起的。
Firefox使用一些傳統(tǒng)的擴(kuò)展實(shí)現(xiàn)了部分瀏覽器功能。在Firefox 57版本中,移除了基于XUL/XPCOM的擴(kuò)展,但沒(méi)有移除WebExtensions。即使是在最新的60版本中,瀏覽器內(nèi)部仍然使用這種機(jī)制。
要利用這一漏洞,我們首先要借助瀏覽器內(nèi)部使用的傳統(tǒng)擴(kuò)展資源。在WebExtensions中,通過(guò)在manifest中設(shè)置web_accessible_resources項(xiàng)( https://developer.mozilla.org/en/Add-ons/WebExtensions/manifest.json/web_accessible_resources ),就可以從任何網(wǎng)頁(yè)中訪問(wèn)所列出的資源。傳統(tǒng)擴(kuò)展中有一個(gè)名為contentaccessible標(biāo)志的類(lèi)似選項(xiàng)( https://developer.mozilla.org/ja/docs/Mozilla/Chrome_Registration#contentaccessible )。我們這一漏洞,正是通過(guò)將contentaccessible標(biāo)志設(shè)置為yes,從而讓瀏覽器內(nèi)部資源的require.js可以被任意Web頁(yè)面訪問(wèn),最終實(shí)現(xiàn)內(nèi)容安全策略的繞過(guò)。
接下來(lái),我們具體分析一下manifest。如果是Windows環(huán)境下的64位Firefox,我們可以通過(guò)以下URL查看到manifest:
jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/chrome.manifest
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
上面的倒數(shù)第2、3行,就是使文件可以從任意Web站點(diǎn)訪問(wèn)的部分。這兩行用于創(chuàng)建一個(gè)resource: URI( https://developer.mozilla.org/en-US/docs/Mozilla/Chrome_Registration#resource )。倒數(shù)第三行中,resource devtools 會(huì)將devtools/modules/devtools/目錄映射到resource://devtools/,該目錄存在于jar:file:///C:/Program%20Files%20(x86)/Mozilla%20Firefox/browser/omni.ja!/chrome/devtools/modules/devtools/ 。
現(xiàn)在,我們可以使用Firefox,通過(guò)resource://devtools/來(lái)訪問(wèn)目錄下的文件。同理,倒數(shù)第二行是映射到resource://devtools-client-jsonview/ 。該URL可以通過(guò)contentaccessible=yes標(biāo)志來(lái)實(shí)現(xiàn)Web訪問(wèn),我們現(xiàn)在可以從任意Web頁(yè)面加載放在該目錄下的文件。
在該目錄中,有一個(gè)用于繞過(guò)內(nèi)容安全策略的require.js。只需要將該require.js加載到使用內(nèi)容安全策略Strict-Dynamic的頁(yè)面中,即可實(shí)現(xiàn)Strict-Dynamic的繞過(guò)。
實(shí)際繞過(guò)操作如下:
https://vulnerabledoma.in/fx_csp_bypass_strict-dynamic.html
<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資源加載,并且會(huì)彈出一個(gè)警告對(duì)話框。
各位讀者可能會(huì)想,為什么會(huì)加載require.js?由于腳本元素沒(méi)有正確的nonce,理論上它應(yīng)該會(huì)被內(nèi)容安全策略所阻止。
實(shí)際上,無(wú)論對(duì)內(nèi)容安全策略設(shè)置多么嚴(yán)格的規(guī)則,擴(kuò)展程序的Web可訪問(wèn)資源都會(huì)在忽略?xún)?nèi)容安全策略的情況下被加載。這種行為在內(nèi)容安全策略的規(guī)范中也有所提及:
https://www.w3.org/TR/CSP3/#extensions
“Policy enforced on a resource SHOULD NOT interfere with the operation of user-agent features like addons, extensions, or bookmarklets. These kinds of features generally advance the user’s priority over page authors, as espoused in [HTML-DESIGN].”
“對(duì)資源執(zhí)行的策略不應(yīng)該干擾用戶(hù)代理功能(如插件、擴(kuò)展或書(shū)簽)進(jìn)行的操作。這些類(lèi)型的功能通常會(huì)提高用戶(hù)的優(yōu)先級(jí),正如[HTML-DESIGN]中所提到的?!?/p>
Firefox的resource: URI也存在這一規(guī)則。受此影響,用戶(hù)甚至可以在設(shè)置了內(nèi)容安全策略的頁(yè)面上使用擴(kuò)展的功能,但另一方面,這一特權(quán)有時(shí)會(huì)被用于繞過(guò)內(nèi)容安全策略,本文所提及的漏洞就是如此。
當(dāng)然,這個(gè)問(wèn)題不僅僅出現(xiàn)在瀏覽器內(nèi)部資源。即使在通用瀏覽器擴(kuò)展中,如果有可以用于繞過(guò)內(nèi)容安全策略的Web可訪問(wèn)資源,也會(huì)發(fā)生同樣的情況。
根據(jù)推測(cè),F(xiàn)irefox的開(kāi)發(fā)人員是通過(guò)將頁(yè)面的內(nèi)容安全策略應(yīng)用到resource: URI中,從而實(shí)現(xiàn)對(duì)這一漏洞的修復(fù)。
在本文中,我們對(duì)于Firefox的內(nèi)容安全策略Strict-Dynamic漏洞進(jìn)行了分析。該漏洞是我在Cure53 CNY XSS Challenge 2018競(jìng)賽( https://github.com/cure53/XSSChallengeWiki/wiki/CNY-Challenge-2018 )的第三級(jí)題目解題過(guò)程中發(fā)現(xiàn)的。在該競(jìng)賽中,我使用了另一個(gè)技巧來(lái)繞過(guò)Strict-Dynamic,如果各位讀者有興趣,可以詳細(xì)查看。此外,我還創(chuàng)建了這個(gè)XSS挑戰(zhàn)賽的另一個(gè)版本( https://twitter.com/kinugawamasato/status/984014228469280768 ),也期待有興趣的同學(xué)能夠參與。
最后,感謝Google團(tuán)隊(duì)進(jìn)行的研究,從而讓我關(guān)注到這一漏洞。謝謝!